设计模式:访问者模式 Visitor

发布于:2025-08-06 ⋅ 阅读:(23) ⋅ 点赞:(0)


前言

访问者是一种行为设计模式,它能将算法与其所作用的对象隔离开来。


问题

假如你的团队开发了一款能够使用巨型图像中地理信息的应用程序。 图像中的每个节点既能代表复杂实体(例如一座城市), 也能代表更精细的对象(例如工业区和旅游景点等)。如果节点代表的真实对象之间存在公路, 那么这些节点就会相互连接。 在程序内部, 每个节点的类型都由其所属的类来表示,每个特定的节点则是一个对象。
在这里插入图片描述
一段时间后, 你接到了实现将图像导出到 XML 文件中的任务。 这些工作最初看上去非常简单。 你计划为每个节点类添加导出函数, 然后递归执行图像中每个节点的导出函数。 解决方案简单且优雅: 使用多态机制可以让导出方法的调用代码不会和具体的节点类相耦合。

但你不太走运,系统架构师拒绝批准对已有节点类进行修改。他认为这些代码已经是产品了, 不想冒险对其进行修改, 因为修改可能会引入潜在的缺陷。
在这里插入图片描述
此外,他还质疑在节点类中包含导出 XML 文件的代码是否有意义。 这些类的主要工作是处理地理数据。 导出 XML 文件的代码放在这里并不合适。

还有另一个原因, 那就是在此项任务完成后, 营销部门很有可能会要求程序提供导出其他类型文件的功能, 或者提出其他奇怪的要求。 这样你很可能会被迫再次修改这些重要但脆弱的类。

解决方案

访问者模式建议将新行为放入一个名为访问者的独立类中,而不是试图将其整合到已有类中。 现在, 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据。

如果现在该操作能在不同类的对象上执行会怎么样呢? 比如在我们的示例中,各节点类导出 XML 文件的实际实现很可能会稍有不同。 因此, 访问者类可以定义一组(而不是一个)方法,且每个方法可接收不同类型的参数。

但我们究竟应该如何调用这些方法(尤其是在处理整个图像方面)呢?这些方法的签名各不相同,因此我们不能使用多态机制。 为了可以挑选出能够处理特定对象的访问者方法,我们需要对它的类进行检查。这是不是听上去像个噩梦呢?

你可能会问, 我们为什么不使用方法重载呢? 就是使用相同的方法名称,但它们的参数不同。不幸的是,即使我们的编程语言(例如 Java 和 C#) 支持重载也不行。 由于我们无法提前知晓节点对象所属的类, 所以重载机制无法执行正确的方法。方法会将 节点 基类作为输入参数的默认类型。

但是, 访问者模式可以解决这个问题。 它使用了一种名为双分派的技巧,不使用累赘的条件语句也可下执行正确的方法。与其让客户端来选择调用正确版本的方法, 不如将选择权委派给作为参数传递给访问者的对象。 由于该对象知晓其自身的类, 因此能更自然地在访问者中选出正确的方法。 它们会“接收”一个访问者并告诉其应执行的访问者方法。

我承认最终还是修改了节点类, 但毕竟改动很小, 且使得我们能够在后续进一步添加行为时无需再次修改代码。

现在, 如果我们抽取出所有访问者的通用接口, 所有已有的节点都能与我们在程序中引入的任何访问者交互。 如果需要引入与节点相关的某个行为, 你只需要实现一个新的访问者类即可。

结构

在这里插入图片描述

代码

#include <iostream>
#include <memory>
using namespace std;

class FactoryElement;
class HouseElement;
class Visitor{
public:
    virtual void visit(FactoryElement* facElemPtr)=0;
    virtual void visit(HouseElement* houElemPtr)=0;
    virtual ~Visitor(){}
};

class Element{
public:
    virtual void accept(shared_ptr<Visitor> v)=0;
    virtual ~Element(){}
};
class FactoryElement:public Element{
public:
    void accept(shared_ptr<Visitor> v) override{
        v->visit(this);
    }
    void featureFac(){
        cout<<"这是工厂区"<<endl;
    }
};
class HouseElement:public Element{
public:
    void accept(shared_ptr<Visitor> v) override{
        v->visit(this);
    }
    void featureHou(){
        cout<<"这是居民区"<<endl;
    }
};

class XmlVisitor:public Visitor{
public:
    void visit(FactoryElement* facElemPtr) override{
        facElemPtr->featureFac();
        cout<<"导出为xml文件"<<endl;
    } 
    void visit(HouseElement* houElemPtr) override{
        houElemPtr->featureHou();
        cout<<"导出为xml文件"<<endl;
    } 
};
class JsonVisitor:public Visitor{
public:
    void visit(FactoryElement* facElemPtr) override{
        facElemPtr->featureFac();
        cout<<"导出为json文件"<<endl;
    } 
    void visit(HouseElement* houElemPtr) override{
        houElemPtr->featureHou();
        cout<<"导出为json文件"<<endl;
    } 
};

int main(){
    auto xmlVisitor=make_shared<XmlVisitor>();
    auto jsonVisitor=make_shared<JsonVisitor>();

    auto facElem = make_unique<FactoryElement>();
    auto houElem = make_unique<HouseElement>();

    facElem->accept(xmlVisitor);
    houElem->accept(xmlVisitor);

    facElem->accept(jsonVisitor);
    houElem->accept(jsonVisitor);

    return 0;
}

网站公告

今日签到

点亮在社区的每一天
去签到