一,访问者模式的定义
访问者模式是一种行为型设计模式,它允许开发者定义一系列操作,这些操作可以应用于同一个对象结构中的不同元素。访问者模式将算法与对象的结构分离,通过这种方式,访问者模式可以在不改变原有对象的前提下,定义新的操作。
访问者模式使得操作可以独立于数据结构而变化。
访问者模式在现实生活中的抽象实例:
游客参观:在旅游景区中,游客可以作为访问者,景区的各个景点作为被访问的元素。游客根据个人兴趣对不同的景点进行参观和了解。
医生查房:医生作为访问者根据病人的病情和需要,对不同的病人进行检查和治疗。
酒店服务员:服务员作为访问者,根据客户的需求和要求,对不同的客房进行打扫和服务。
财务审计员:财务审计员作为访问者,根据企业的财务情况和政策要求,对不同的部门和账目进行审计和核对。
二,访问者模式的结构
访问者模式主要包含以下组件:
1.访问者(Visitor):
访问者声明了访问对象结构的统一方法,该方法接收一个元素对象作为参数。
2.具体访问者(Concrete Visitor):
具体访问者是实现访问者接口的具体类,它包含了访问操作的实现细节,通过调用不同的具体访问者,可以实现不同的访问操作。
3.元素(Element):
元素声明了接受访问者访问时的操作。
4.具体元素(Concrete Element):
具体元素是实现元素接口的具体类,它包含了接收访问时所进行的具体操作细节。
5.对象结构(Object Structure):
对象结构是元素的集合,它提供了访问元素的统一接口,使访问者可以遍历所有的元素。
访问者和元素的关系:访问者定义了可以访问和操作元素的方法,而元素则提供了一个接受访问者的方法。
组件之间的工作步骤如下:
1.客户端通过调用访问者的方法来访问对象结构。
2.对象结构将自身作为参数传递给访问者。
3.访问者根据需要调用元素的方法进行操作。
4.元素执行与访问者相关的操作,也可以将自身作为参数传递给访问者。
对应UML类图:
三,访问者模式代码样例
伪代码样例:
class concreteElement_1;
class concreteElement_2;
class visitor
{
public:
virtual void visit(concreteElement_1& el) = 0;
virtual void visit(concreteElement_2& el) = 0;
};
class concreteVisitor : public visitor
{
public:
virtual void visit(concreteElement_1& el) override
{
// Do something
};
virtual void visit(concreteElement_2& el) override
{
// Do something
};
};
class element
{
public:
virtual void accept(visitor& v) = 0;
};
class concreteElement_1 : public element
{
public:
virtual void accept(visitor& v) override
{
v.visit(*this);
}
};
class concreteElement_2 : public element
{
public:
virtual void accept(visitor& v) override
{
v.visit(*this);
}
};
完整代码样例:
#include <iostream>
class ElementA;
class ElementB;
class Visitor {
public:
virtual void visit(ElementA* element) = 0;
virtual void visit(ElementB* element) = 0;
};
class Element {
public:
virtual void accept(Visitor* visitor) = 0;
};
class ElementA: public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}
void operationA() {
std::cout << "OperationA called on ElementA." << std::endl;
}
};
class ElementB: public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}
void operationB() {
std::cout << "OperationB called on ElementB." << std::endl;
}
};
class ConcreteVisitor: public Visitor{
public:
void visit(ElementA* element) override {
std::cout << "visits ElementA." << std::endl;
element->operationA();
}
void visit(ElementB* element) override {
std::cout << "visits ElementB." << std::endl;
element->operationB();
}
};
int main() {
ElementA elementA;
ElementB elementB;
ConcreteVisitor visitor;
Element* elements[] = { &elementA, &elementB };
for (Element* element : elements) {
element->accept(&visitor);
}
return 0;
}
运行结果:
visits ElementA.
OperationA called on ElementA.
visits ElementB.
OperationB called on ElementB.
四,访问者模式的应用场景
XML解析:XML文档的元素可以有各种各样的类型,可以编写一个通用的遍历函数,对所有类型的元素进行一致的操作。
图形处理:开发一个通用的处理器可以针对所有图元(矩形、圆形、线条等)进行统一的操作。
游戏状态管理:基于访问者模式开发一个状态机来管理各种角色的行为。
软件架构解耦:当组件的操作依赖于具体的数据类型时,访问者模式可以帮助降低组件之间的耦合度。
编译器开发:在词法分析阶段,使用访问者模式遍历源代码,可以根据不同的语法结构进行相应的处理。
五,访问者模式的优缺点
访问者模式的优点:
有助于将算法的关注点与其操作的对象的结构分开。
符合"开闭原则", 引入新的操作(访问者)时,无需修改被访问元素的现有代码。
使用具体的类来封装特定的操作,方便维护。
使得被调用的方法可以在代码运行期间动态修改。
访问者模式的缺点:
如果为简单的操作定义单独的类,会使得代码更复杂。
存在安全隐患,被访问的元素可能会向访问者公开其内部结构。
当访问操作很多且频繁时,性能开销大。
六,代码实战
Demo1:模拟的XML解析器
#include <iostream>
#include <vector>
class Element;
class Text;
class Visitor {
public:
virtual void visit(Element& element) = 0;
virtual void visit(Text& text) = 0;
};
class Node {
public:
virtual void accept(Visitor& visitor) = 0;
};
class Element : public Node{
public:
std::string name;
Element(const std::string& n) : name(n) {}
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
};
class Text: public Node{
public:
std::string content;
Text(const std::string& c) : content(c) {}
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
};
class XmlDocument {
public:
std::vector<Node*> nodes;
void addNode(Node* node) {
nodes.push_back(node);
}
void accept(Visitor& visitor) {
for (auto node : nodes) {
node->accept(visitor);
}
}
};
class ElementCounter : public Visitor {
public:
int elementCount = 0;
void visit(Element& element) override {
std::cout << "Found element: " << element.name << std::endl;
elementCount++;
}
void visit(Text& text) override {
std::cout << "Found text: " << text.content << std::endl;
}
};
int main() {
XmlDocument document;
Element element1("div");
Text text("Hello, world!");
Element element2("p");
document.addNode(&element1);
document.addNode(&text);
document.addNode(&element2);
ElementCounter counter;
document.accept(counter);
std::cout << "Total elements found: " << counter.elementCount << std::endl;
return 0;
}
运行结果:
Found element: div
Found text: Hello, world!
Found element: p
Total elements found: 2
Demo2:模拟的AST抽象语法树
#include <iostream>
class Number;
class BinaryOperation;
class ExpressionVisitor {
public:
virtual double visit(Number& number) = 0;
virtual double visit(BinaryOperation& binaryOperation) = 0;
};
class Expression {
public:
virtual double accept(ExpressionVisitor& visitor) = 0;
};
class Number : public Expression {
public:
double value;
Number(double v) : value(v) {}
double accept(ExpressionVisitor& visitor) override {
return visitor.visit(*this);
}
};
class BinaryOperation : public Expression {
public:
char operation;
Expression* left;
Expression* right;
BinaryOperation(char op, Expression* l, Expression* r) :
operation(op), left(l), right(r) {
}
double accept(ExpressionVisitor& visitor) override {
return visitor.visit(*this);
}
};
class ExpressionEvaluator : public ExpressionVisitor {
public:
double visit(Number& number) override {
return number.value;
}
double visit(BinaryOperation& binaryOperation) override {
double left = binaryOperation.left->accept(*this);
double right = binaryOperation.right->accept(*this);
switch (binaryOperation.operation) {
case '+':
return left + right;
case '-':
return left - right;
case '*':
return left * right;
case '/':
if (right != 0) {
return left / right;
}
else {
std::cerr << "Division by zero" << std::endl;
return 0;
}
default:
std::cerr << "Invalid operation: "
<< binaryOperation.operation
<< std::endl;
return 0;
}
}
};
int main() {
Number num1(5.0);
Number num2(3.0);
Number num3(2.0);
BinaryOperation expr1('+', &num1, &num2);
BinaryOperation expr2('*', &expr1, &num3);
ExpressionEvaluator evaluator;
double result = expr2.accept(evaluator);
std::cout << "Result: " << result << std::endl;
return 0;
}
运行结果:
Result: 16
七,参考阅读
https://cpppatterns.com/
https://www.geeksforgeeks.org/visitor-method-design-patterns-in-c/
https://www.modernescpp.com/index.php/the-visitor-pattern/
https://softwarepatterns.com/cpp/visitor-software-pattern-cpp-example