设计模式之访问者模式

目录

1.概述

2.结构

3.简单实现

4.基于可变参数模板实现

5.使用场景

6.总结


1.概述

        在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

        访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

2.结构

        访问者模式(Visitor Pattern)是一种将数据操作与数据结构分离的设计模式。这种设计模式允许你在不改变数据结构的前提下定义新的操作。在访问者模式中,通常有两个主要的组件:元素(Element)和访问者(Visitor)。

        UML结构图如下所示:

角色定义:

抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口,用来代表对象结构添加的功能。

具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。

抽象节点(Element)角色:抽象的元素对象,对象结构的顶层接口,定义接收访问的操作。

具体节点(ConcreteElement)角色:具体元素对象,对象结构中具体的对象,也是被访问的对象,通常回调访问者的真实功能,同时开放自身的数据提供访问者使用。

结构对象(ObjectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。 

调用顺序:

3.简单实现

下面是一个简单的C++访问者模式的示例:

#include <iostream>
#include <string>
#include <functional>
#include <map>
#include <vector>
using namespace std;

class Element;
class CPU;
class VideoCard;
class MainBoard;

class Visitor {
public:
    Visitor(const std::string& name) : m_visitorName(name) { }
    virtual void visit( CPU* cpu )  = 0 ;
    virtual void visit( VideoCard* videoCard )  = 0;
    virtual void visit( MainBoard* mainBoard )  = 0;

    std::string getName() const {
        return this->m_visitorName;
    };
private:
    std::string m_visitorName;
};

class Element {
public:
    Element( const std::string& name ) : m_eleName(name) { }
    virtual void accept( Visitor* visitor )  = 0;

    virtual std::string getName() const {
        return this->m_eleName;
    }
private:
    std::string m_eleName;
};

class CPU : public Element {
public:
    CPU(const std::string& name) : Element(name) {}

    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }
};

class VideoCard : public Element {
public:
    VideoCard(const std::string& name) : Element(name) {}

    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }
};

class MainBoard : public Element {
public:
    MainBoard(const std::string& name) : Element(name) {}

    void accept(Visitor* visitor) override {
        visitor->visit(this);
    }
};

class CircuitDetector : public Visitor {
public:
    CircuitDetector(const std::string& name) : Visitor(name) {}

    // checking cpu
    void visit( CPU* cpu ) override {
        std::cout << Visitor::getName() << " is checking CPU's circuits.(" << cpu->getName()<<")" << std::endl;
    }

    // checking videoCard
    void visit( VideoCard* videoCard ) override {
        std::cout << Visitor::getName() << " is checking VideoCard's circuits.(" << videoCard->getName()<<")" << std::endl;
    }

    // checking mainboard
    void visit( MainBoard* mainboard )  override {
        std::cout << Visitor::getName() << " is checking MainBoard's circuits.(" << mainboard->getName() <<")" << std::endl;
    }

};

class FunctionDetector : public Visitor {
public:
    FunctionDetector(const std::string& name) : Visitor(name) {}
    void visit( CPU* cpu ) override {
        std::cout << Visitor::getName() << " is check CPU's function.(" << cpu->getName() << ")"<< std::endl;
    }

    // checking videoCard
    void visit( VideoCard* videoCard ) override {
        std::cout << Visitor::getName() << " is checking VideoCard's function.(" << videoCard->getName()<< ")" << std::endl;
    }

    // checking mainboard
    void visit( MainBoard* mainboard ) override {
        std::cout << Visitor::getName() << " is checking MainBoard's function.(" << mainboard->getName() << ")"<< std::endl;
    }
};


class Computer {
public:
    Computer(CPU* cpu,
             VideoCard* videocard,
             MainBoard* mainboard) {
        m_elementList.push_back(cpu);
        m_elementList.push_back(videocard);
        m_elementList.push_back(mainboard);
    };
    void Accept(Visitor* visitor) {
        for( auto& it : m_elementList)
        {
            it->accept(visitor);
        }
    };
private:
    std::vector<Element*> m_elementList;
};

int main(){
    std::unique_ptr<CPU> cpu(new CPU("Intel CPU"));
    std::unique_ptr<VideoCard> videocard(new VideoCard("XXX video card"));
    std::unique_ptr<MainBoard> mainboard(new MainBoard("HUAWEI mainboard"));
    std::unique_ptr<Computer> myComputer(new Computer(cpu.get(), videocard.get(), mainboard.get()));

    std::unique_ptr<CircuitDetector> Dan(new CircuitDetector("CircuitDetector Dan"));
    std::unique_ptr<FunctionDetector> Tom(new FunctionDetector("FunctionDetector Tom"));

    std::cout << "\nStep 1: Dan is checking computer's circuits." << std::endl;
    myComputer->Accept(Dan.get());
    std::cout << "\nStep 2: Tom is checking computer's functions." << std::endl;
    myComputer->Accept(Tom.get());

    return 0;
}

        在这个示例中,Visitor接口定义了访问CPU、VideoCard和MainBoard的方法。每个具体的元素类(CPU、VideoCard和MainBoard)都实现了accept方法,该方法接受一个Visitor对象并调用其相应的visit方法。Computer 就相当于ObjectStructure,实现了对传入visitor对所有元素的访问。在客户端代码中,我们创建了一个CircuitDetector、FunctionDetector对象和三个具体的元素对象,并使用accept方法将访问者应用于这些元素。

4.基于可变参数模板实现

        GOF《设计模式》一书中也明确指出了Visitor模式需要注意的问题:定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要付出很大的代价。如果对象结构类经常改变,那么还是在这些类中定义这些操作较好。

        也就是说,在访问者模式中被访问者应该是一个稳定的继承体系,如果这个继承体系经常变化,就会导致经常修改Visitor基类,因为在Visitor基类中定义了需要访问的对象类型,每增加一种被访问类型就要增加一个对应的纯虚函数,在上例中,如果需要增加一个新的被访问者ConcreteElement3,则需要在Visitor基类中增加一个纯虚函数:

virtual void Visit(ConcreteElement3* element) = 0;

        根据面向接口编程的原则,我们应该依赖于接口而不应依赖于实现,因为接口是稳定的,不会变化的。而访问者模式的接口不太稳定,这会导致整个系统的不稳定,存在很大的隐患。要解决这个问题,最根本的方法是定义一个稳定的Visitor接口层,即不会因为增加新的被访问者而修改接口层,能否定义一个稳定的Visitor接口层呢?答案是肯定的,通过C++11进行改进,我们就可以实现这个目标。

        通过可变参数模板就可以实现一个稳定的接口层,利用可变参数模板可以支持任意个数的参数的特点,可以让访问者接口层访问任意个数的被访问者,这样就不需要每增加一个新的被访问者就修改接口层,从而使接口层保持稳定。使用C++11改进后的visitor模式代码如下:

template <typename... Types>
class VisitorT;

template <typename T, typename... Types>
class VisitorT<T,Types...> : VisitorT<Types...>{
public:
    using VisitorT<Types...>::Visit;//避免覆盖父类的同名方法
    virtual void Visit(T&) = 0;
};
template <typename T>
class VisitorT<T>{
public:
    virtual void Visit(T&) = 0;
};

        上述代码为每个类型定义了一个纯虚函数Visit。通过"using Visitor<Types...>::Visit;"可以避免影藏基类的同名方法。

        被访问的继承体系使用Visitor访问该继承体系的对象,第3章节的例子代码可以修改为:

class Element;
class CPU;
class VideoCard;
class MainBoard;
class Element;

using VisitorBase = VisitorT<CPU,VideoCard,MainBoard> ;

/*------------------*/

class Visitor : public VisitorBase {
public:
    Visitor(const std::string& name) : m_visitorName(name) { }
    std::string getName() const {
        return this->m_visitorName;
    };
private:
    std::string m_visitorName;
};


class Element {
public:
    Element(const std::string& name ) : m_eleName(name){ }
    virtual void accept( Visitor& visitor ) {};

    virtual std::string getName() {
        return this->eleName;
    }
private:
    std::string m_eleName;
};

/*----------- Elements -------------*/

class CPU : public Element {
public:
    CPU(const std::string& name) : Element(std::move(name)) {}

    void accept(Visitor& visitor) override {
        visitor.Visit(*this);
    }
};

class VideoCard : public Element {
public:
    VideoCard(const std::string& name) : Element(std::move(name)) {}

    void accept(Visitor& visitor) override {
        visitor.Visit(*this);
    }
};

class MainBoard : public Element {
public:
    MainBoard(const std::string& name) : Element(std::move(name)) {}

    void accept(Visitor& visitor) override {
        visitor.Visit(*this);
    }
};

/*----------- ConcreteVisitor -------------*/

class CircuitDetector : public Visitor{
public:
    CircuitDetector(const std::string& name) : Visitor(name) {}

    // checking cpu
    void Visit(CPU& cpu ) override {
        std::cout << Visitor::getName() << " is checking CPU's circuits.(" << cpu.getName()<<")" << std::endl;
    }

    // checking videoCard
    void Visit( VideoCard& videoCard ) override {
        std::cout << Visitor::getName() << " is checking VideoCard's circuits.(" << videoCard.getName()<<")" << std::endl;
    }

    // checking mainboard
    void Visit( MainBoard& mainboard ) override {
        std::cout << Visitor::getName() << " is checking MainBoard's circuits.(" << mainboard.getName() <<")" << std::endl;
    }

};

class FunctionDetector : public Visitor {
public:
    FunctionDetector(const std::string& name) : Visitor(name) {}
    void Visit( CPU& cpu )  override {
        std::cout << Visitor::getName() << " is check CPU's function.(" << cpu.getName() << ")"<< std::endl;
    }

    // checking videoCard
    void Visit( VideoCard& videoCard ) override  {
        std::cout << Visitor::getName() << " is checking VideoCard's function.(" << videoCard.getName()<< ")" << std::endl;
    }

    // checking mainboard
    void Visit( MainBoard& mainboard ) override {
        std::cout << Visitor::getName() << " is checking MainBoard's function.(" << mainboard.getName() << ")"<< std::endl;
    }
};

/*------------------------*/
class Computer {
public:
    Computer(CPU* cpu,
             VideoCard* videocard,
             MainBoard* mainboard) {
        m_elementList.push_back(cpu);
        m_elementList.push_back(videocard);
        m_elementList.push_back(mainboard);
    };
    void Accept(Visitor& visitor) {
        for(auto& it : m_elementList)
        {
            it->accept(visitor);
        }
    };
private:
    std::vector<Element*> m_elementList;
};


int main(){
    std::unique_ptr<CPU> cpu(new CPU("Intel CPU"));
    std::unique_ptr<VideoCard> videocard(new VideoCard("XXX video card"));
    std::unique_ptr<MainBoard> mainboard(new MainBoard("HUAWEI mainboard"));
    std::unique_ptr<Computer> myComputer(new Computer(cpu.get(), videocard.get(), mainboard.get()));

    std::unique_ptr<CircuitDetector> Dan(new CircuitDetector("CircuitDetector Dan"));
    std::unique_ptr<FunctionDetector> Tom(new FunctionDetector("FunctionDetector Tom"));

    std::cout << "\nStep 1: Dan is checking computer's circuits." << std::endl;
    myComputer->Accept(Dan.get());
    std::cout << "\nStep 2: Tom is checking computer's functions." << std::endl;
    myComputer->Accept(Tom.get());

    return 0;
}

测试同上面的一样效果。

5.使用场景

1)对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。 

2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。

3)访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。

6.总结

优点:

  1. 扩展性:访问者模式使得在不修改已有类的情况下,可以轻松地增加新的操作。这符合开放-封闭原则(Open-Closed Principle),即对扩展开放,对修改封闭。

  2. 分离操作与数据结构:访问者模式将操作从数据结构中分离出来,使得数据结构可以独立地变化,而无需修改相关的操作。这有助于减少代码的耦合度,提高系统的可维护性。

  3. 灵活性:访问者模式允许操作以不同的方式实现,而不影响数据结构。这提供了更大的灵活性,可以根据需要选择不同的操作实现。

  4. 双重分派:访问者模式实现了运行时动态地决定调用哪个操作,这种机制称为双重分派,增加了系统的动态性。

  5. 跨类层次结构的操作:当需要在多个类层次结构中执行相同的操作时,访问者模式可以避免在每个类层次结构中重复实现相同的操作。

缺点:

  1. 增加类的数量:对于每个需要访问的数据类,都需要有一个对应的访问者类,这可能导致类的数量显著增加,增加系统的复杂性。

  2. 修改困难:如果数据结构发生变化(例如添加新的子类),可能需要修改所有的访问者类来适应这些变化。这可能导致维护成本的增加。

  3. 破坏封装性:访问者模式需要被访问的类暴露出其内部状态给访问者对象,这可能会破坏类的封装性。

  4. 性能开销:由于访问者模式涉及到多重分派和类型检查,可能会导致一定的性能开销。

  5. 可能过度使用:在某些情况下,过度使用访问者模式可能会导致代码变得复杂且难以理解。如果操作与数据结构紧密相关,或者操作数量有限,则可能不需要使用访问者模式。

        访问者模式在某些场景下非常有用,特别是当需要在不修改类层次结构的情况下添加新的操作时。然而,它也带来了一些缺点,如增加类的数量、修改困难等。因此,在决定是否使用访问者模式时,需要仔细权衡其优缺点,并考虑具体的应用场景和需求。

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
访问者模式是GOF设计模式中的一种行为型模式。它被描述为最复杂且最难以理解的一种模式访问者模式的目的是封装一些作用于某种数据结构中的各元素的操作,可以在不改变这些元素的类的前提下定义这些操作。它通过将数据结构与操作分离,使得可以在不改变数据结构的前提下添加新的操作,提高了代码的灵活性和可扩展性。 访问者模式的构成包括元素(Element)、访问者(Visitor)和对象结构(Object Structure)。元素表示数据结构中的各个元素,它们通常会提供一个接受访问者的方法。访问者则表示对元素的操作,它们可以根据具体的元素类型进行不同的操作。对象结构则是一个容器,用于存放元素,并提供让访问者访问元素的接口。 访问者模式适用于数据结构相对稳定,但其操作经常发生变化的情况。它可以将操作的变化封装在访问者中,而无需改变元素的类。这样一来,当需要新增一种操作时,只需要新增一个访问者,而不需要改变元素的类。 访问者模式的优点包括增加新的操作非常方便,由于具体访问者类与具体元素类之间没有直接的关联,因此增加新的访问者类对原有类库无影响。同时,它也符合开闭原则,对于元素的类库可以在不修改源代码的情况下添加新的操作。 总结起来,访问者模式是一种通过将数据结构与操作分离的设计模式,可以在不改变数据结构的前提下添加新的操作。它的构成包括元素、访问者和对象结构,适用于数据结构相对稳定,但操作经常变化的情况。访问者模式的优点包括增加新的操作方便,符合开闭原则。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [详解设计模式访问者模式](https://blog.csdn.net/weixin_45187434/article/details/128197861)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [设计模式 访问者模式](https://download.csdn.net/download/zjn640322/9685149)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值