gpt4 book ai didi

c++ - 在使用工厂模式时,我是否应该以任何方式避免向下转换?

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:13:05 27 4
gpt4 key购买 nike

我正在从事一个实现专有协议(protocol)的服务器项目。服务端是用C++工厂模式实现的,现在面临向下转型的问题。

我正在研究的协议(protocol)是为慢速网络自动控制而设计的,例如RS485、ZigBee、窄带PLC等。我们用工厂模式设计了主服务器。当接收到一个新的帧时,我们首先识别该帧的关联设备类型,调用工厂方法生成一个新的“解析器”实例,并将该帧分派(dispatch)给解析器实例。

我们的专有协议(protocol)是用纯二进制实现的,我们可能需要的所有信息都记录在框架本身中,因此可以尽可能简单地定义基本接口(interface)。我们还将为我们的工厂实现自动注册方法(此处省略与 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 命令的串行接口(interface)。我们已经设法解决了与 PTY 的通信问题,但现在要解决的问题是解析器的实现。

目标系统的命令接口(interface)非常有限。我不能只是等待并监听传入的内容,我必须轮询状态,而且我必须轮询两次——第一次轮询 header ,第二次轮询有效负载——以获得完整的命令。这对我们的实现来说是个问题,因为我必须将两个帧传递给解析器实例,这样它才能工作:

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 */
}
};

换句话说,我们需要调用派生类的一个方法,而该方法并没有在基类中定义。我们正在使用工厂模式来生成派生类的实例。

现在我们有几个选择:

  1. 简单地将两个字符串连接成一个是行不通的。两个字符串都包含一些设备指定的信息,需要分别解析。如果我们采用这种方法,我们将在连接字符串之前从解析器实例中执行一些“预解析”。我们认为这不是一个好主意。

  2. 将 parser_factory::get_instance() 的返回向下转换为 legacy_parser。

  3. 创建另一个独立工厂,它只包含从 legacy_parser 派生的类。

  4. 更改 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);
    };
  5. 使用访问者模式实现“智能指针”以处理从 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<> 向下转型对我们来说是最简单的方法,但我们都不喜欢这个想法,因为我们都觉得向下转型是“邪恶的”。然而,没有人能准确解释为什么它是“邪恶的”。

在我们做出决定之前,我希望听到更多关于这些选项的评论。

最佳答案

http://en.wikipedia.org/wiki/Circle-ellipse_problem是你的第一个邪恶的例子。如果你发现你可以做一些违反基本原则的事情,那么你应该发明另一个轮子或尝试另一顶帽子:http://en.wikipedia.org/wiki/Six_Thinking_Hats

关于c++ - 在使用工厂模式时,我是否应该以任何方式避免向下转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6088411/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com