在使用工厂模式时,我应该避免以任何方式进行下转换吗
Should I avoid downcasting by any means when using factory pattern?
我正在处理一个实现专有协议的服务器项目。服务器是用C++中的工厂模式实现的,我们现在面临着下转换的问题。
我正在研究的协议是为在慢速网络上进行自动控制而设计的,如RS485、ZigBee、窄带PLC等。我们设计了工厂模式的主服务器。当接收到一个新帧时,我们首先识别该帧的相关设备类型,调用工厂方法来生成一个新的"解析器"实例,并将该帧调度到解析器实例。
我们的专有协议是以纯二进制实现的,我们可能需要的每一个信息都记录在帧本身中,因此可以尽可能简单地定义基本接口。我们还将为我们的工厂实现自动注册方法(此处省略了与std::map操作相关的详细代码):
// This is our "interface" base-class
class parser
{
public:
virtual int parse(unsigned char *) = 0;
virtual ~parser() { }
};
// The next two classes are used for factory pattern
class instance_generator
{
public:
virtual parser *generate() = 0;
};
class parser_factory
{
private:
static std::map<int,instance_generator*> classDB;
public:
static void add(int id, instance_generator &genrator);
parser *get_instance(int id);
};
// the two template classes are implementations of "auto-regisrtation"
template <class G, int ID> class real_generator : public instance_generator
{
public:
real_generator() { parser_factory::add(ID,this); }
parser *generate() { return new G; }
};
template <class T, int N> class auto_reg : virtual public parser
{
private:
static real_generator<T,N> instance;
public:
auto_reg() { instance; }
};
template <class T, int N> parser_generator<T,N> auto_reg<T,N>::instance;
// And these are real parser implementations for each device type
class light_sensor : public auto_reg<light_sensor,1>
{
public:
int parse(unsigned char *str)
{
/* do something here */
}
};
class power_breaker : public auto_reg<power_breaker,2>
{
public:
int parse(unsigned char *str)
{
/* do something here */
}
};
/* other device parser */
这种工厂模式运行得很好,而且很容易扩展新的设备类型。
然而,最近我们正在尝试与现有的控制系统接口,以提供类似的功能。目标系统相当陈旧,它只提供一个基于ASCII的、类似AT命令的串行接口。我们已经设法解决了PTY的通信问题,但现在要解决的问题是解析器的实现。
目标系统的命令界面非常有限。我不能只是等待和收听即将到来的内容,我必须轮询州,并且我必须轮询两次——第一次轮询报头,第二次轮询有效载荷——才能获得完整的命令。这对我们的实现来说是个问题,因为我必须将两个帧传递到解析器实例中,这样它才能工作:
class legacy_parser : virtual public parser
{
public:
legacy_parser() { }
int parse(unsigned char *str)
{
/* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */
}
virtual int parse(unsigned char *header, unsigned char *payload) = 0;
};
class legacy_IR_sensor :
public legacy_parser,
public auto_reg<legacy_IR_sensor,20>
{
public:
legacy_IR_sensor(){ }
int parse(unsigned char *header, unsigned char *payload)
{
/* Now we can finally parse the complete frame */
}
};
换句话说,我们需要调用派生类的方法,而该方法没有在基类中定义。我们使用工厂模式来生成派生类的实例。
现在我们有几个选择:
简单地将两个字符串连接为一个字符串是行不通的。这两个字符串都包含一些设备指定的信息,它们应该单独解析。如果我们采用这种方法,在连接字符串之前,我们将对解析器实例进行一些"预解析"。我们认为这不是一个好主意。
将parser_filter::get_instance()的返回向下转换为legacy_parser。
创建另一个独立的工厂,该工厂仅包含从legacy_parser派生的类。
更改instance_generator和parser_factory的定义,使它们也可以生成(legacy_parser*),同时保持所有现有代码不受影响:
class instance_generator { public: virtual parser *generate() = 0; virtual legacy_parser *generate_legacy() { return NULL; } }; class extended_parser_factory : public parser_factory { public: legacy_parser *get_legacy_instance(int id); };
使用Visitor模式实现"智能指针"以处理从legacy_parser:派生的实例
class smart_ptr { public: virtual void set(parser *p) = 0; virtual void set(legacy_parser *p) = 0; }; class parser { public: parser() { } virtual int parse(unsigned char *) = 0; virtual void copy_ptr(smart_ptr &sp) // implement "Visitor" pattern { sp.set(this); } virtual ~parser() { } }; class legacy_parser : virtual public parser { public: legacy_parser() { } void copy_ptr(smart_ptr &sp) // implement "Visitor" pattern { sp.set(this); } int parse(unsigned char *str) { /* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */ } virtual int parse(unsigned char *header, unsigned char *payload) = 0; }; class legacy_ptr : public smart_ptr { private: parser *miss; legacy_parser *hit; public: legacy_ptr& operator=(parser *rhv) { rhv->copy_ptr(*this); return *this; } void set(parser* ptr) { miss=ptr; /* ERROR! Do some log or throw exception */ } void set(legacy_parser *ptr) { hit = ptr; } legacy_parser& operator*() { return *hit; } ~legacy_ptr() { if(miss) { delete miss; } if(hit) { delete hit; } } };
很明显,使用dynamic_cast<>的Downcasting对我们来说,这是最简单的方法,但我们都不喜欢这个想法,因为我们都觉得贬低某些东西是"邪恶的"。然而,没有人能够确切地解释为什么它是"邪恶的"
在我们做出决定之前,我希望听到更多关于这些选项的评论。
http://en.wikipedia.org/wiki/Circle-ellipse_problem是你的第一个邪恶的例子。如果你发现你可以做一些违反基本原则的事情,那么你应该发明另一个轮子或尝试另一顶帽子:http://en.wikipedia.org/wiki/Six_Thinking_Hats
下行,尤其是在工厂模式实现中,对我来说非常有意义。它实际上很好地符合"程序到接口"的思想。不知道为什么人们觉得沮丧是不好的。请检查方差,因为这就是您所看到的。
问题是legacy_parser需要两个帧,而不是原始解析器中的一个。因此,一个可能的解决方案是稍微更改您的原始解析器,使其能够处理多个帧。例如,如果解析器想要更多的帧,parse可能会返回一个预定义的常量,然后legacy_parser可以这样实现:
class legacy_parser : public parser {
public:
int parse(unsigned char *str) {
if (parse_header_) {
// store str in header_
parse_header_ = false;
return kExpectMoreFrames;
} else {
return parse(header_, str);
}
}
private:
int parse(unsigned char *header, unsigned char *parload) {
// ...
}
bool parse_header_;
unsigned char *header_;
};
如果现有的解析器代码没有意外地使用为"更多帧"的含义定义的值,那么它们就不应该受到影响。
- 防止主数据类型C++的隐式转换
- 模板参数替换失败,并且未完成隐式转换
- 努力将整数转换为链表。不知道我在这里做错了什么
- HEX值到wchar_t字符(UTF-8)的转换
- lambda参数转换为constexpr技巧,然后获取带链接的数组
- 将 Qvector<uint8_t> 转换为 QString
- 如何在cuSparse中使用cusparseXcoo2csr从coo转换为csc
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 在c++中使用nlohmann从类到json的转换
- 从"int*"强制转换为"unsigned int"会丢失精度错误
- 将Integer转换为4字节的unsined字符矢量(按大端字节顺序)
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 如何使用OpenCV将RBG图像转换为HSV,并将H、S和V值保存为C++中的3个独立图像
- 复制列表初始化的隐式转换的等级是多少
- 正在将指针转换为范围
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- 将"打开的CV图像"中的"颜色"转换为整数格式
- 是否可以从int转换为enum类类型
- 了解 GLM- openGL 中的相机转换
- 将无符号char*转换为std::istream*C++