C++设计模式——Visitor访问者模式
一,访问者模式的定义
访问者模式是一种行为型设计模式,它允许开发者定义一系列操作,这些操作可以应用于同一个对象结构中的不同元素。访问者模式将算法与对象的结构分离,通过这种方式,访问者模式可以在不改变原有对象的前提下,定义新的操作。
访问者模式使得操作可以独立于数据结构而变化。
访问者模式在现实生活中的抽象实例:
游客参观:在旅游景区中,游客可以作为访问者,景区的各个景点作为被访问的元素。游客根据个人兴趣对不同的景点进行参观和了解。
医生查房:医生作为访问者根据病人的病情和需要,对不同的病人进行检查和治疗。
酒店服务员:服务员作为访问者,根据客户的需求和要求,对不同的客房进行打扫和服务。
财务审计员:财务审计员作为访问者,根据企业的财务情况和政策要求,对不同的部门和账目进行审计和核对。
二,访问者模式的结构
访问者模式主要包含以下组件:
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