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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值