首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >访问者模式,替换对象

访问者模式,替换对象
EN

Software Engineering用户
提问于 2016-03-24 23:17:00
回答 1查看 1.6K关注 0票数 6

我有一个将DSL转换为C++的程序,它在中间表示上使用访问者模式。

我经常需要将当前处理的节点替换为另一种类型(例如,用类型定义替换“未解决的类型”)。

做这种事的好模式是什么?

我试过:

  1. 存储指向引用当前对象的位置的指针非常难看,但保留了“标准”访问者模式,访问者本身将其所有状态作为成员变量保存,而visit方法不返回值。这里的困难在于,这是相当容易出错的--如果我忘记在下降到树中之前存储指针,我可能会覆盖错误的引用。
  2. 从访问方法返回替换对象下一层负责用新返回的对象替换当前对象--删除由返回空指针表示,而返回旧对象则不表示任何更改。这要容易得多,因为如果我不小心掉了一个返回代码,我就可以得到诊断,但我认为它还是有点难看。

有没有更好的选择?

EN

回答 1

Software Engineering用户

发布于 2016-03-26 12:11:33

visit()方法在C++中描述时通常是无效的,这只是一个实现细节。这绝不是访客模式的中心部分。在Gamma,Helm,Johnson,Vlisside的“Design”一书中,代码示例通常在C++和Smalltalk中提供。Smalltalk是一种非常动态的语言。该书中Smalltalk中访问者模式的示例代码实际上返回值!这是一个正则表达式的评估器。我将把代码翻译成JavaScript,以使其更容易理解。

对象结构(正则表达式)由四个类组成,它们都有一个以访问者为参数的accept()方法。在类SequenceExpression中,accept方法是函数接受(AVisitor){返回aVisitor.visitSequence(this);} … ConcreteVisitor类是REMatchingVisitor。…其方法…返回表达式将匹配的一组流以标识当前状态。函数visitSequence(sequenceExp) { this.inputState =序列this.表达式1.接受(此);返回序列this.表达式2.接受(此);}.函数visitAlternation(alternateExp) { var originalState = this.inputState;var finalState =交替执行;this.inputState = originalState;this.inputState返回finalState;}函数visitLiteral(literalExp) { var finalState =新集合();this.inputState.foreach(函数(流){ var tStream = stream.copy();if (tStream.nextAvailable(literalExp.value.size) stream.copy)){en19#(););返回finalState;}

那么为什么没有在C++中完成呢?为什么所有的visit()方法都无效?因为虚拟accept()方法不知道返回什么。每个访问者可能返回不同的类型。虽然accept方法应该只是传递该值,但C++需要精确的类型。这可以用模板表示,但是虚拟方法不能是模板化的方法。(虚拟方法的要点是,实现只能在运行时(后期绑定)得到解析,而模板必须在编译时完全评估。由于在编译时不知道将选择哪种方法,因此不会调用模板)。

通常的解决方法是将返回值存储在访问者对象的实例字段中。这很尴尬,并且要么需要默认的可构造返回值,要么需要指针间接。虽然这在语义上等同于直接返回值,但这使得使用访问者非常尴尬,以至于我常常创建一个包装器函数来运行访问者:

代码语言:javascript
复制
class Visitor;

class Base {
public:
  virtual void accept(Visitor&) const = 0;
};

class A;
class B;

class Visitor {
public:
  virtual void visitA(A const&) = 0;
  virtual void visitB(B const&) = 0;
};

class A { … };
class B { … };

class ConcreteVisitor : public Visitor final {
  int mResult;
public:
  ConcreteVisitor() : mResult(0) {}
  void visitA(A const& a) override { mResult = 1; }
  void visitB(B const& a) override { mResult = 2; }
  int result() const { return mResult; }
};

int runConcreteVisitor(Base const& base) {
  ConcreteVisitor v;
  base.accept(v);
  return v.result();
}

每个访问者在给定的输入层次结构上有效地表现为一个(多态)函数,或者类似于扩展方法。通过将附加参数和返回值填充到成员变量中,我们可以在C++中建模任意函数签名。(要访问的元素以外的函数参数对应于访问者的构造函数参数)。

但这只是个解决办法。如果您的所有访问者都有一个有效的签名Expression visitor(Expression const&),那么您可以将所有接受方法写成

代码语言:javascript
复制
 virtual Expression accept(Visitor& v) {
   return v.acceptAddition(*this);
 }

对于AST操作,这有时就是您所需要的。但是,一旦访问者有了不同的“返回”类型,就需要复制其他类型的所有接受/访问方法(基本上是手动模板),或者必须使用访问者成员变量解决方案。在这种情况下,从一开始就使用解决办法可能是明智的。通过使用runVisitor(…)帮助程序,这会稍微减少错误的发生,因为您不必记住检索返回值,而是直接给出一个。如果您的访问者是递归的,这也意味着您应该避免在访问者中直接发生不必要的可变状态,因为每次接受/访问调用都会创建一个新的访问者。

票数 2
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/313783

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档