2017-03-16 111 views
4

這是對象設計模式專家的問題。返回不同類型/類的方法的設計模式

讓我們假設我有一個Parser類負責讀取/解析數據流(包含不同類型的數據包的數據包)。這些數據包中的每一個都有不同類型的信息,所以理想情況下,我將爲每種類型的數據包設置一個類(PacketTypeAPacketTypeB,...每個數據包都有自己的接口)。然後

class Parser { 

public: 

    /* ctor */ 
    /* dtor */ 


    void read_packet(/* arguments */); 

    // methods... 
private: 
    // more methods... 
} 

方法Parser::read_packet將經過流和類(或指針或引用的類)返回到相應的數據包類型。

你會使用void指針嗎?通用類(PacketBasicInterface)如何提供一個通用(部分)接口來查詢數據包的類型(以便可以在運行時作出任何決定)?

// Pure virtual (abstract) class to provide a common (and partial) interface 
class PacketBasicInterface { 
public: 

    std::string whoAmI() const = 0; 
    bool amIofType(const std::string& type) const = 0; 

} 

// Class to access data of type A packet 
class PacketTypeA : public PacketBasicInterface { 

public: 
    // methodA_1() 
    // methodA_2(), ... 
} 

// Class to access data of type A packet 
class PacketTypeB : public PacketBasicInterface { 

public: 
    // methodB_1() 
    // methodB_2(), ... 
} 

任何想法或反饋將非常感激!

非常感謝!

+1

不知道到底你需要什麼,但某種抽象工廠,或複合的? –

回答

5

這是std::variant的用途。

我會定義一個枚舉類,即列舉所有可能的數據包類型:

enum class packet_type {initialization_packet, confirmation_type, ... }; 

而且具有read_packet返回packet_type的元組和一個變體:

typedef std::variant<...> packet_info; 

std::tuple<packet_type, packet_info> read_packet(); 

不要真的需要一個正式枚舉,但它更容易找出如何處理變體。

在此一般方法的一些變化包括:

  1. 使用不透明std::string,而不是一個固定的枚舉,以指定數據包類型。使用std::any而不是正式的std::variant

  2. 而不是使用簡單的枚舉或不透明的令牌,如std::string,使用一個稍微不平凡的類來定義數據包類型,類的方法將變體元數據作爲參數,並封裝可以執行的操作在數據包上完成。

當然,正如引用鏈接中所述,std::variant需要C++ 17。對於更新編譯器來說,這會是一個很好的參數:您可以通過一種簡單的方法來實現完全類型安全的方法。

+0

這不是一個對象設計模式。 – 0xDEFACED

+0

@SantlagoVarela的確,它好多了 –

+0

它可能表現更好,它可能更加優化,但是它作爲一個合適的設計模式更具可維護性,可讀性和內聚性?這是一個很好的解決方案,但它仍然不適合標籤「設計模式」和麪向對象的方法。 – 0xDEFACED

1

你會使用void指針嗎?

No.

通用類(PacketBasicInterface)如何提供通用(部分)接口來查詢有關數據包類型(以便可以在運行時作出任何決定)?

這對我最有意義。

讓我細化一下。是的,擁有一個通用基類將是一件好事。但是,解析流以構造基類的子類型時,不要依賴於類型方法。而是使用工廠模式。讓各個工廠根據構建正確的對象類型,我假設這些鍵可以從正在分析的數據中獲得。

如果您在數據中遇到字符串"PacketTypeA",則預計PacketTypeAFactory將負責構建該對象。

FWIW,這種方法可以擴展許多基類的子類型。我們在工作中使用這種方法,並且在二十多年的時間裏爲我們提供了很好的服務。


下面的代碼基礎,我想的骨骼結構:


的類。

class PacketBasicInterface { }; 

class PacketTypeA : public PacketBasicInterface { }; 

class PacketTypeB : public PacketBasicInterface { }; 

工廠的接口。

// PacketFactory.h 
class PacketFactory 
{ 
    public: 

     static PacketBasicInterface* makePacket(std::string const& packetData); 

     static void registerFactory(std::string const& key, PacketFactory* factory); 

     virtual PacketBasicInterface* make(std::string const& packetData) = 0; 

     virtual ~PacketFactory() {} 
}; 

實施,使工廠的工作框架。

// PacketFactory.cpp 

#include "PacketFactory.h" 

namespace PacketBasicInterface_Impl 
{ 
    using PacketFactoryMap = std::map<std::string, PacketFactory*>; 

    PacketFactoryMap& getPacketFactoryMap() 
    { 
     static PacketFactoryMap theMap; 
     return theMap; 
    } 
}; 

uisng namespace PacketBasicInterface_Impl; 

PacketBasicInterface* PacketFactory::makePacket(std::string const& packetData) 
{ 
    std::string key = extractKey(packetData); 
    PacketFactoryMap& theMap = getPacketFactoryMap(); 
    PacketFactoryMap::iterator iter = theMap.find(key); 
    if (iter == theMap.end()) 
    { 
     return nullptr; 
    } 

    return iter->second->make(packetData); 
} 

void registerFactory(std::string const& key, PacketFactory* factory) 
{ 
    getPacketFactoryMap()[key] = factory; 
} 

代碼使用工廠模式使型PacketTypeA的對象。

// PacketTypeAFactory.cpp 

#include "PacketFactory.h" 
#include "PacketTypeA.h" 

class PacketTypeAFactory : public PacketFactory 
{ 
    public: 

     virtual PacketBasicInterface* make(std::string const& packetData) 
     { 
     PacketTypeA* packet = new PacketTypeA(); 

     // Flesh out packet with data pulled from packetData 
     // ... 
     // 

     return packet; 
     } 

     struct Initializer 
     { 
     Initializer() { PacketFactory::registerFactory("PacketTypeA", new PacketTypeAFactory); } 
     }; 
}; 


// Constructing this object at static initialization time makes sure 
// that PacketTypeAFactory is registered with PacketFactory when the 
// stream data need to be parsed. 
static PacketTypeAFactory::Initializer initializer; 

製備型PacketTypeB的對象的代碼非常類似於 代碼使用工廠模式使型PacketTypeA的對象。

// PacketTypeBFactory.cpp 


#include "PacketFactory.h" 
#include "PacketTypeB.h" 

class PacketTypeBFactory : public PacketFactory 
{ 
    public: 

     virtual PacketBasicInterface* make(std::string const& packetData) 
     { 
     PacketTypeA* packet = new PacketTypeA(); 

     // Flesh out packet with data pulled from packetData 
     // ... 
     // 

     return packet; 
     } 

     struct Initializer 
     { 
     Initializer() { PacketFactory::registerFactory("PacketTypeB", new PacketTypeBFactory); } 
     }; 
}; 


// Constructing this object at static initialization time makes sure 
// that PacketTypeBFactory is registered with PacketFactory when the 
// stream data need to be parsed. 
static PacketTypeBFactory::Initializer initializer; 

客戶端代碼。

std::string packetData; 
while (getPacketData(packetData)) 
{ 
    PacketBasicInterface* packet = PacketFactory::makePacket(packetData); 
    if (packet == nullptr) 
    { 
     // Deal with error. 
    } 
    else 
    { 
     // Use packet 
    } 
} 
+0

與使用_interface查詢packet_類型相比,雙重調度是更清晰和更面向對象的方法嗎?我的意思是,使用多態性,並有這樣的方法聞起來有點,不是嗎?目的是什麼?當你找到一個匹配時是否使用'dynamic_cast'?衛生署! – skypjack

+0

@skypjack,好點。我更新了答案。希望這對大家更有意義。 –

+0

當然。同時我添加了我自己的答案,沒有人明確提到雙重調度,問題標記爲_design-patterns_。 ;-) – skypjack

1

雙調度可以去,如果你正在尋找從面向對象編程領域中的設計模式的方式。
它遵循最小,工作示例:

#include<iostream> 

struct Visitor; 

struct PacketBasicInterface { 
    virtual void accept(Visitor &) = 0; 
}; 

struct PacketTypeA: PacketBasicInterface { 
    void accept(Visitor &) override; 
}; 

struct PacketTypeB: PacketBasicInterface { 
    void accept(Visitor &) override; 
}; 

struct Visitor { 
    void visit(PacketTypeA) { 
     std::cout << "PacketTypeA" << std::endl; 
    } 

    void visit(PacketTypeB) { 
     std::cout << "PacketTypeB" << std::endl; 
    } 
}; 

void PacketTypeA::accept(Visitor &visitor) { 
    visitor.visit(*this); 
} 

void PacketTypeB::accept(Visitor &visitor) { 
    visitor.visit(*this); 
} 

struct Parser { 
    PacketBasicInterface * read_packet() { 
     return new PacketTypeB{}; 
    } 
}; 

int main() { 
    Visitor visitor; 
    auto *packet = Parser{}.read_packet(); 
    packet->accept(visitor); 
    delete packet; 
}