【设计模式】行为型之访问者模式

发布时间 2023-03-24 12:13:45作者: bfstudy

前言

最近在看Solidity编译器代码(C++实现),其中使用到了设计模式中的访问者模式,这里正好学习一下。GoF的设计模式书虽然讲的很详细,但是这里还是结合实际项目中的应用来说一下。

代码中的模式应用

先说下上下文,在编译器中有个重要的概念叫AST(抽象语法树)。它是词法分析、语法分析的产物,用它来转中间代码,进而转换成字节码(不管是虚拟机指令还是机器指令,Solidity是转换成EVM指令,EVM即以太坊虚拟机)。ASTNode代表AST中的节点抽象表示,很多子类继承于它,比如Declaration、Statement等很多AST元素。

这里将AST转换成中间代码或字节码使用到了访问者模式,访问者模式的UML图如下所示:

在此模式中有5种角色:Visitor(抽象访问者)、ConcreteVisitor(具体访问者)、Element(抽象元素)、ConcreteElement(具体元素)和ObjectStructure(对象结构)。

Visitor常常有visitXXX的方法,参数是具体元素,比如在Solidity编译器中的实现(有删减,只保留相关代码):

class ASTConstVisitor
{
public:
	ASTConstVisitor() = default;

	ASTConstVisitor(ASTConstVisitor const&) = delete;
	ASTConstVisitor(ASTConstVisitor&&) = delete;

	ASTConstVisitor& operator=(ASTConstVisitor const&) = delete;
	ASTConstVisitor& operator=(ASTConstVisitor&&) = delete;

	virtual ~ASTConstVisitor() = default;

	virtual bool visit(SourceUnit const& _node) { return visitNode(_node); }
	virtual bool visit(PragmaDirective const& _node) { return visitNode(_node); }
	virtual bool visit(ImportDirective const& _node) { return visitNode(_node); }
	virtual bool visit(ContractDefinition const& _node) { return visitNode(_node); }
	virtual bool visit(IdentifierPath const& _node) { return visitNode(_node); }
	virtual bool visit(InheritanceSpecifier const& _node) { return visitNode(_node); }
	// ...方法很多,省略

	virtual void endVisit(SourceUnit const& _node) { endVisitNode(_node); }
	virtual void endVisit(PragmaDirective const& _node) { endVisitNode(_node); }
	virtual void endVisit(ImportDirective const& _node) { endVisitNode(_node); }
	virtual void endVisit(ContractDefinition const& _node) { endVisitNode(_node); }
	virtual void endVisit(IdentifierPath const& _node) { endVisitNode(_node); }
	virtual void endVisit(InheritanceSpecifier const& _node) { endVisitNode(_node); }
	// ...方法很多,省略

protected:
	/// Generic function called by default for each node, to be overridden by derived classes
	/// if behaviour unspecific to a node type is desired.
	virtual bool visitNode(ASTNode const&) { return true; }
	/// Generic function called by default for each node, to be overridden by derived classes
	/// if behaviour unspecific to a node type is desired.
	virtual void endVisitNode(ASTNode const&) { }
};

ASTConstVisitor即担当抽象访问者的角色。

Element常常有accept方法,参数是抽象访问者(如ASTConstVisitor),比如在Solidity编译器中抽象元素是ASTNode,代码(有删减,只保留相关代码)如下:

class ASTNode
{
public:
	/// Noncopyable.
	ASTNode(ASTNode const&) = delete;
	ASTNode& operator=(ASTNode const&) = delete;

	explicit ASTNode(int64_t _id, SourceLocation _location);
	virtual ~ASTNode() {}

	virtual void accept(ASTConstVisitor& _visitor) const = 0;

	template <class T>
	static void listAccept(std::vector<T> const& _list, ASTConstVisitor& _visitor)
	{
		for (T const& element: _list)
			if (element)
				element->accept(_visitor);
	}
};

其中的virtual void accept(ASTConstVisitor& _visitor) const = 0;即是Element的核心方法。

ConcreteVisitor常常重写visitXXX方法。Solidity中有大量ConcreteVisitor,这里拿ASTConstVisitor的子类ExpressionCompiler来举例,ExpressionCompiler是用来处理Expression,将Expression翻译成中间代码或字节码的,代码(有删减)如下:

class ExpressionCompiler: private ASTConstVisitor
{
public:
	ExpressionCompiler(
		CompilerContext& _compilerContext,
		bool _optimiseOrderLiterals
	):
		m_optimiseOrderLiterals(_optimiseOrderLiterals),
		m_context(_compilerContext)
	{}

	/// Compile the given @a _expression and leave its value on the stack.
	void compile(Expression const& _expression);

private:
	bool visit(Conditional const& _condition) override;
	bool visit(Assignment const& _assignment) override;
	bool visit(TupleExpression const& _tuple) override;
	bool visit(UnaryOperation const& _unaryOperation) override;
	bool visit(BinaryOperation const& _binaryOperation) override;
	bool visit(FunctionCall const& _functionCall) override;
	bool visit(FunctionCallOptions const& _functionCallOptions) override;
	bool visit(NewExpression const& _newExpression) override;
	bool visit(MemberAccess const& _memberAccess) override;
	bool visit(IndexAccess const& _indexAccess) override;
	bool visit(IndexRangeAccess const& _indexAccess) override;
	void endVisit(Identifier const& _identifier) override;
	void endVisit(Literal const& _literal) override;

	CompilerContext& m_context;
};

ExpressionCompiler重载了一些visit方法,比如bool visit(FunctionCall const& _functionCall) override;

ConcreteElement常常重写accept方法,这里拿Expression来举例,代码如下:

class Expression: public ASTNode
{
public:
	explicit Expression(int64_t _id, SourceLocation const& _location): ASTNode(_id, _location) {}

	ExpressionAnnotation& annotation() const override;
};

class FunctionCall: public Expression
{
public:
	FunctionCall(
		int64_t _id,
		SourceLocation const& _location,
		ASTPointer<Expression> _expression,
		std::vector<ASTPointer<Expression>> _arguments,
		std::vector<ASTPointer<ASTString>> _names,
		std::vector<SourceLocation> _nameLocations
	):
		Expression(_id, _location), m_expression(std::move(_expression)), m_arguments(std::move(_arguments)), m_names(std::move(_names)), m_nameLocations(std::move(_nameLocations))
	{
		solAssert(m_nameLocations.size() == m_names.size());
	}

	void accept(ASTVisitor& _visitor) override;
	void accept(ASTConstVisitor& _visitor) const override;

	Expression const& expression() const { return *m_expression; }
	/// @returns the given arguments in the order they were written.
	std::vector<ASTPointer<Expression const>> arguments() const { return {m_arguments.begin(), m_arguments.end()}; }
	/// @returns the given arguments sorted by how the called function takes them.
	std::vector<ASTPointer<Expression const>> sortedArguments() const;
	/// @returns the list of given argument names if this is a named call,
	/// in the order they were written.
	/// If this is not a named call, this is empty.
	std::vector<ASTPointer<ASTString>> const& names() const { return m_names; }
	std::vector<SourceLocation> const& nameLocations() const { return m_nameLocations; }

	FunctionCallAnnotation& annotation() const override;

private:
	ASTPointer<Expression> m_expression;
	std::vector<ASTPointer<Expression>> m_arguments;
	std::vector<ASTPointer<ASTString>> m_names;
	std::vector<SourceLocation> m_nameLocations;
};

FunctionCall是Expression的子类,而Expression是ASTNode的子类,其中FunctionCall重写了accept方法,比如void accept(ASTConstVisitor& _visitor) const override;

进行抽象实现

下面按照UML图将访问者模式用C++实现。

首先是Visitor,因为C++没有接口,这里使用抽象类来实现:

#include <vector>
#include <memory>

using namespace std;

class Element;

class Visitor;

class ConcreteElement;

class Visitor {
public:
    virtual void visit(ConcreteElement &_node) = 0;
};

class Element {
public:
    virtual void accept(Visitor &_visitor) = 0;
};

class ConcreteVisitor : public Visitor {
public:
    virtual void visit(ConcreteElement &_node) override {
        // do something
    }
};

class ConcreteElement : public Element {
public:
    virtual void accept(Visitor &_visitor) override {
        _visitor.visit(*this);
    }
};

class ObjectStructure {
public:
    void listAccept(Visitor &_visitor) {
        for (auto &element: elements) {
            element->accept(_visitor);
        }
    }

private:
    vector<unique_ptr<Element>> elements;
};

总结

如果不使用访问者模式,就需要通过大段的if/else来处理逻辑。使用访问者模式后,如果新增新的元素或访问者,只需要添加新的具体元素或具体访问者即可,对修改关闭,对扩展开放,满足了开闭原则。