返回不同类型/类的方法的设计模式
Design pattern for method returning different types/classes
这是对象设计模式专家的问题。
假设我有一个Parser
类,负责读取/解析数据流(携带不同类型的信息包)。这些数据包中的每一个都携带不同类型的信息,所以理想情况下,我会为每种类型的数据包(PacketTypeA
、PacketTypeB
、...每个都有自己的界面)。
class Parser {
public:
/* ctor */
/* dtor */
void read_packet(/* arguments */);
// methods...
private:
// more methods...
}
然后,该方法Parser::read_packet
将遍历流,并将类(或指向类的指针或引用)返回到相应的数据包类型。
你会为此使用空指针吗?一个泛型类(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(), ...
}
任何想法或反馈将不胜感激!
非常感谢!
这就是 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();
实际上不需要正式的枚举,但它可以更轻松地弄清楚如何处理变体。
此一般方法的一些变体包括:
使用不透明
std::string
而不是固定枚举来指定数据包类型。使用
std::any
而不是正式的std::variant
。不要使用简单的枚举或像
std::string
这样的不透明令牌,而是使用稍微不平凡的类来定义数据包类型,该类的方法将变体元数据作为参数,并封装可以在数据包上执行的操作。
当然,如引用链接中所述,std::variant
需要C++17。这对于您更新编译器来说是一个很好的论据:您可以获得一种实现完全类型安全方法的简单方法。
如果您正在寻找面向对象编程领域的设计模式,那么双重调度可能是要走的路。
它遵循一个最小的工作示例:
#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;
}
你会为此使用空指针吗?
不。
一个泛型类(PacketBasicInterface)怎么样,它将提供一个公共(部分)接口来查询数据包的类型(以便可以在运行时做出任何决定)?
这对我来说最有意义。
让我对此进行完善。是的,拥有一个泛型基类会很好。但是,在分析流以构造基类的子类型时,不要依赖if-else
类型方法。而是使用工厂模式。让各个工厂基于键构造正确的对象类型,我假设可以从正在解析的数据中获得。
如果在数据中遇到字符串"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
}
}
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 资源管理设计模式
- 用于在回调中调用解析器的设计模式
- 在C++中创建观察器设计模式的好方法
- 是否有可以处理方法调用依赖关系的设计模式?
- 设计模式中对象中的过程(方法和操作)的状态
- 设计模式的工厂替代方法:具有不同构造函数的类
- 正确的方法或设计模式,以简化类中的“operatorX”函数,以按给定顺序比较相同类型的属性
- 返回不同类型/类的方法的设计模式
- 不同处理方法的设计模式,这些方法共享一些常见的处理链
- 使用通用方法的最佳实践/设计模式
- 在最终类中调用静态方法的静态基构造函数的设计模式
- 如何在不使用非构造函数的方法的情况下应用singleton设计模式来返回类对象
- 寻找减少虚方法重载的设计模式
- 这是哪种设计模式:工厂方法还是抽象工厂
- 为返回对象和NRVO的方法设计模式
- 设计模式-使用工厂方法合并不同的类(c++)
- C++设计模式:可以将继承的方法私有为派生类
- 在遵循pimpl设计模式的类中实现move语义的正确方法是什么?
- 设计模式 - C++哪里需要本地扩展或外部方法?