【问题标题】:Using enums and switch instead of visitor pattern使用枚举和 switch 代替访问者模式
【发布时间】:2016-08-03 09:46:42
【问题描述】:

我有一个表示数学表达式的树,我想为了计算表达式树的值,我会实现访问者模式,但是在 C++ 中这涉及到很多重复,因为方法接受访问者必须在每个子类上,因为即使方法相同,类型也不相同。

class Node {
  virtual void Acccept(Visitor *visitor) = 0;
};
class ConstantNode : Node {
  virtual void Accept(Visitor *visitor) {
    visitor->visit( this );
  }
};

class VariableNode : Node {
  virtual void Accept( Visitor *visitor) {
    visitor->visit( this );
  }
};

class Visitor {
  virtual void visit(ConstantNode *node) = 0;
  virtual void visit(VariableNode *node) = 0;
};
class CalculateVisitor : Visitor {
  virtual void visit(ConstantNode *node) { /* code */ }
  virtual void visit(VariableNode *node) { /* code */ }
};

这也有一个问题,因为方法是虚拟的,所以不能有模板节点。

拥有枚举似乎要容易得多,每个节点都有一个案例,您只需在方法中而不是访问者模式中打开枚举。

class Node {
  enum NodeType {
    Constant,
    Variable
  }
  Node(NodeType type) : m_nodeType(type) {}
  NodeType m_nodeType;
};

class ConstantNode {
  ConstantNode() : Node(Constant) {}
};
class VariableNode {
  VariableNode() : Node(Variable) {}
};

int calculate(Node* node) {
  switch (node->m_nodeType) {
    case Constant:
      //...
    case Variable:
      //...
  }
}

我知道枚举版本需要动态转换,但总体而言,这似乎更可取的是必须重复大量相同的代码,这意味着它将允许模板节点,例如 (BinOpNode<std::plus>)。

或者有什么方法可以改进 C++ 中的访问者模式,使其不需要所有这些重复?

(直接在stackoverflow中输入代码,如有错误请见谅,但这是基于真实代码)

编辑:对于 BinOpNode:

template<class T>
class BinOpNode {
  T m_op = T();
  BinOpNode() : Node(BinOp) {}
};

//in calculate:
case BinOpNode:
  BinOpNode* n = dynamic_cast<BinOpNode*>(node);
  return n->m_op(calculate(n->m_left), calculate(n->m_right));

【问题讨论】:

  • 这似乎并不容易。重复的数量是相同的(案例标签与虚函数)。你也没有解释你打算如何处理模板。 case BinOp: 现在呢?
  • 重复次数更少,因为它消除了对所有 Accept 函数的需要。在任何一种情况下,节点都会有构造函数。
  • 添加新访客不需要重复。并避免忘记与 switch/enum 大小写相反的类型。
  • "不再需要所有 Accept 函数" CRTP 是您的朋友。还要查找“非循环访问者”。
  • 不,这是我不熟悉的一些变体。它似乎建立在每个类的唯一标签上(这正是您的类型枚举)。对于原始的非循环访问者,请搜索 Robert C Martin 的论文。

标签: c++ enums visitor-pattern


【解决方案1】:

您可以使用模板删除重复:

// Classes implementing the mechanism

class Node {
  virtual void Accept(Visitor *visitor) = 0;
};

template<typename NodeType> class ConcreteNode
{
  virtual void Accept(Visitor* visitor)
  {
    visits<NodeType>* v = visitor;
    v->visit((NodeType*)this);
  }
};

template<typename NodeType> class visits
{
  virtual void visit(NodeType* node) = 0;
};

// The actual visitors/visited classes

class Visitor:
  visits<ConstantNode>,
  visits<VariableNode>
{
};

class ConstantNode : ConcreteNode<ConstantNote> {};
class VariableNode : ConcreteNode<VariableNode> {};

class CalculateVisitor : Visitor {
  virtual void visit(ConstantNode *node) { /* code */ }
  virtual void visit(VariableNode *node) { /* code */ }
};

请注意,您的模板节点代码将工作,因为以下行不能工作:

BinOpNode* n = dynamic_cast<BinOpNode*>(node);

BinOpNode你定义为一个模板,你不能有一个指向模板的指针,只有一个指向类的指针。例如,您可能有一个指向从BinOpNode 生成的特定 类的指针,例如

BinOpNode<int>* n = dynamic_cast<BinOpNode<int>*>(node);

但这也可以由访问者模式完美处理;使用上面的模板代码:

class Visitor:
  public visits<BinOpNode<int> >,
  public visits<BinOpNode<double> >,
  ...
{
};

template<typename T> class BinOpNode:
  public ConcreteNode<BinOpNode<T> >
{
  ...
};

【讨论】:

  • 我看不出这是一种改进。您现在拥有多重继承,而之前您不需要拥有它。除非我误解了,否则这似乎是“关于这是否真的是一种改进,而不是在不需要时使情况变得更加复杂,这是值得商榷的”。这样做有什么好处?
猜你喜欢
  • 1970-01-01
  • 2010-11-02
  • 1970-01-01
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-02
  • 2016-12-15
相关资源
最近更新 更多