访问者模式是不是比受控使用 RTTI 更好?

Posted

技术标签:

【中文标题】访问者模式是不是比受控使用 RTTI 更好?【英文标题】:Is the visitor pattern a better option than controlled use of RTTI?访问者模式是否比受控使用 RTTI 更好? 【发布时间】:2020-09-08 05:40:59 【问题描述】:

我经常发现自己尝试使用 boost/QT 信号解耦对象。实现这一点的简单方法是针对我要通信的每个具体类型,创建一个新的信号和插槽签名并连接所有相关对象。这导致了访问者模式,理想情况下我想发出一个访问者并让所有的监听类接收访问者并执行一个动作。接口如下所示:

class IVisitor

public:
     Visit(IListener* Listener);
     Visit(ConcreteListener1* Listener);
     Visit(ConcreteListener2* Listener);
     //And so on from here 
;

同样,如果我想要多个命令,我需要让多个访问者:

class IListener

public:
     Visit(IVisitor* Listener);
     Visit(ConcreteVisitor1* Listener);
     Visit(ConcreteVisitor2* Listener);
     //And so on from here
;

对我来说,这似乎违反了打开/关闭原则,因为每次我想连接一个新的监听器或实现一个新的访问者时,我总是不得不返回更新我的接口。理想情况下,这将使用双重分派,并且如果没有专门的接口存在,则能够使基类保持不变,仅根据他们使用基类接口接受的访问者来更改派生类。我知道这在 C++ 中是不可能的,因为函数重载和参数类型是基于编译时信息的。

一般来说,这就是在一个不支持它的程序中重新实现多重分派。

我看到了很多关于访问者模式的争论,这似乎是人们使用和讨厌的模式。它似乎是它的访问者模式或dynamic_cast?我已经实现了一个模板化的帮助类,它可以在使用 dynamic_cast 以更好地维护时自动执行可怕的 if-else 逻辑。所以我的问题是......当逻辑维护在很大程度上自动化时,使用 dynamic_cast 的陷阱是否比访问者模式中提到的陷阱更糟糕?

编辑:

std::visit 确实似乎是解决多分派问题的好方法。我能够使用以下一个衬垫创建一个简单的消息传递系统:

std::visit(overloaded [&](auto arg) Listener->Recieve(arg);  , pCommand->AsVariant());

【问题讨论】:

为什么每个访问者都必须了解每个听众,反之亦然?我会坚持只连接每种类型所需的信号和插槽。 由于访问者模式的工作方式,如果您有一个公共访问者基类,则基类必须定义一个公共接口,该接口接受子类的所有侦听器类。 【参考方案1】:

使用访客模式,

当向IVisitor 添加新的侦听器时,您可以保证现有访问者必须处理该新侦听器。

使用简单的dynamic_cast,未处理的侦听器的可能性更大。 取决于(每个,(因此没有统一的行为))类如何实现它,您可能会抛出不受支持的侦听器,或者回退到“默认实现”(什么都不做)。

dynamic_cast 的替代方法是 std::variant 用法,对于访问者而言,它需要了解所有侦听器类型。

std::variant 有一个 std::visit,它甚至可以进行多次调度 :-)

所以,类似:

using ListenerVariant = std::variant<ConcreteListener1*, ConcreteListener2* /*..*/>;

class IListener

public:
    virtual ListenerVariant AsVariant() = 0;
// ...
;

然后

std::visit(overloaded[](ConcreteListener1* l)/*..*/,
                      [](ConcreteListener2* l)/*..*/,
           listener.AsVariant());

您可以保证所有案件都得到处理,(您甚至可以有后备)。

【讨论】:

以上是关于访问者模式是不是比受控使用 RTTI 更好?的主要内容,如果未能解决你的问题,请参考以下文章

PHP CURL api 是不是比使用流进行 HTTP/HTTPS 访问更干净/更快/更好?

如何跨模式和数据库对存储在表中的数据提供数据隔离/受控访问

.ix() 是不是总是比 .loc() 和 .iloc() 更好,因为它更快并且支持整数和标签访问?

.ix() 是不是总是比 .loc() 和 .iloc() 更好,因为它更快并且支持整数和标签访问?

使用一次时,显示列表是不是比即时模式更好?

访问类的私有属性