我正在开发一个实现专有协议的服务器项目。服务器是用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_factory :: 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);
};
使用访问者模式实现“智能指针”以处理从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&lt;&gt;进行向下转换对我们来说这是最简单的方法,但我们都不喜欢这个想法,因为我们都觉得向某些人倾斜是“邪恶的”。然而,没有人能够准确地解释为什么它是“邪恶的”。
在我们做出决定之前,我希望听到更多关于这些选择的评论。
答案 0 :(得分:2)
http://en.wikipedia.org/wiki/Circle-ellipse_problem是你邪恶的第一个例子。 如果你发现你可以做违反基本原则的事情,那么你应该发明另一个轮子或尝试另一个帽子:http://en.wikipedia.org/wiki/Six_Thinking_Hats
答案 1 :(得分:0)
向下倾斜,特别是在工厂模式实施中,对我来说非常有意义。它实际上与“程序到界面”的意识形态很好地配合。不确定为什么人们认为向下倾斜是坏事。查看contravariance,因为这正是您所看到的。
答案 2 :(得分:0)
问题是legacy_parser需要两个帧而不是原始解析器中的一个帧。因此,一种可能的解决方案是稍微更改原始解析器并使其适用于多个帧。 例如,如果解析器需要更多帧,则解析可以返回预定义常量,然后可以像这样实现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_;
};
如果现有的解析器代码不会意外地使用为“更多帧”的含义定义的值,则不应该受到影响。