2017-03-16 145 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; 
}