行为型设计模式——访问者模式

文章目录

访问者模式

场景

集合是在面向对象编程中广泛使用的数据类型。通常,集合包含不同类型的对象,在这种情况下,必须在不知道类型的情况下对所有集合元素执行某些操作。

比如,我们想要操作某一类型的数据对象时,通常的做法是通过使用instanceof结合if来确定是否是想要的对象。这种方法不灵活 且根本不是面向对象的 ,在这一点上,我们应该考虑开闭原则 ,我们可以用抽象类替换 if 块,并且每个具体类将实现自己的操作。

  • 旅游:去安徽可以爬黄山,去山东可以爬泰山,去陕西可以爬华山
    • 不同的地点,人爬的山是不一样的
  • 卖保险:如果是老百姓推销医疗保险,如果是银行推销失窃保险,如果是商铺推销火灾和水灾保险。
    • 不同的受众,保险推销员推销的产品是不一样的
  • 小鬼子奇葩的盖章文化:Boss的章是正的,其他下属职位越低盖章的时候就得越倾斜,如果不这样就表示对上司有意见
    • 不同等级的职员,盖章的方式是不一样的。

以上三个例子中前者是对象,后者就是算法 ,如果用访问者模式处理上边列举的场景就需要使用后者来访问前者

为了解决这类问题,就产生了访问者模式(Visitor Pattern)。
> 访问者设计模式是一种将算法与其运行的对象结构分开的方法。这种分离的实际结果是** 能够在不修改结构的情况下向现有对象结构添加新操作。提供一个作用于某对象结构中的各元素的操作表示** ,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。 >

在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为*"对象结构"*,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。

访问者模式在遍历 对象结构的时候可以采用迭代器实现。

结构

在访问者模式结构图中包含如下几个角色:

  • Visitor(抽象访问者 ):接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
  • Concrete Visitor (具体访问者):实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
  • Element(抽象元素):接口声明了一个方法来 "接收" 访问者。 该方法必须有一个参数被声明为访问者接口类型。
  • Concrete Element(具体元素):必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。
  • Object Structure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。

具体执行过程

  1. 调用具体元素类的accept()方法,并将Visitor子类对象作为其参数;
  2. 在具体元素类accept()方法内部调用传入的Visitor对象的visit()方法,将当前具体元素类对
    象this作为参数,如visitor.visit(this);
  3. 执行Visitor对象的visit()方法,在其中还可以调用具体元素对象的业务方法,如
    e.operation()。

这种调用机制也称为"双重分派 ",正因为使用了双重分派机制,使得增加新的访问者无须修改现有类库代码,只需将新的访问者对象作为参数传入具体元素对象的accept()方法,程序运行时将回调在新增Visitor类中定义的visit()方法,从而增加新的元素访问方式。
```cpp class ObjectStructure { private: / 定义一个集合用于存储元素对象 vector

实现

:::color4

访问者模式适用于数据结构比较稳定的系统,对于上面的例子而言就是指草帽团成员:只有男性和女性(不会再出现其它性别)。在剥离出的行为状态类中针对男性和女性提供了相对应的 doing 方法。这种模式的优势就是可以方便的给对象添加新的状态和处理动作,也就是添加新的 AbstractAction 子类(算法类),在需要的时候让这个子类去访问某个成员对象,访问者模式的最大优势就是使算法的增加变得更加容易维护。

如果不按照性别进行划分,草帽团一共9个成员就需要在行为状态类中给每个成员提供一个 doing 方法,当草帽团又添加了新的成员,状态类中也需要给新成员再添加一个对应的 doing 方法,这就破坏了设计模式的开放 -- 封闭原则。

:::

cpp 复制代码
// 抽象的成员类
class AbstractMember
{
public:
    AbstractMember(string name) : m_name(name){}
    string getName()
    {
        return m_name;
    }
    // 接受状态对象的访问
    virtual void accept(行为/动作类* action) = 0;
    virtual ~AbstractMember() {}
protected:
    string m_name;
};
cpp 复制代码
// 男性成员
class MaleMember : public AbstractMember
{
public:
    AbstractMember::AbstractMember;
    void accept(AbstractAction* action) override
    {
        action->maleDoing(this);
    }
};

// 女性成员
class FemaleMember : public AbstractMember
{
public:
    AbstractMember::AbstractMember;
    void accept(AbstractAction* action) override
    {
        action->femalDoing(this);
    }
};

🔊**accept() 函数是一个双分派操作,它得到执行的操作不仅取决于传入的状态类的具体状态,还取决于它访问的人的类别。**

cpp 复制代码
// Visitor.h
// 类声明
class MaleMember;
class FemaleMember;
// 抽象的动作类
class AbstractAction
{
public:
    // 访问男人
    virtual void maleDoing(MaleMember* male) = 0;
    // 访问女人
    virtual void femalDoing(FemaleMember* female) = 0;
    virtual ~AbstractAction() {}
};
cpp 复制代码
// 愤怒
class Anger : public AbstractAction
{
public:
    void maleDoing(MaleMember* male) override;
    void femalDoing(FemaleMember* female) override;
    void warning();
    void fight();
};

// 恐惧
class Horror : public AbstractAction
{
public:
    void maleDoing(MaleMember* male) override;
    void femalDoing(FemaleMember* female) override;
    void help();
    void thinking();
};

#include <iostream>
#include "Visitor.h"
#include "Member.h"
#include <list>
#include <vector>
using namespace std;

void Anger::maleDoing(MaleMember* male)
{
    cout << "我是草帽海贼团的" << male->getName() << endl;
    fight();
}

void Anger::femalDoing(FemaleMember* female)
{
    cout << "我是草帽海贼团的" << female->getName() << endl;
    warning();
}

void Anger::warning()
{
    cout << "大家块逃,我快顶不住了, 不要管我!!!" << endl;
}

void Anger::fight()
{
    cout << "只要还活着就得跟这家伙血战到底!!!" << endl;
}

void Horror::maleDoing(MaleMember* male)
{
    cout << "我是草帽海贼团的" << male->getName() << endl;
    thinking();
}

void Horror::femalDoing(FemaleMember* female)
{
    cout << "我是草帽海贼团的" << female->getName() << endl;
    help();
}

void Horror::help()
{
    cout << "这个大熊太厉害, 太可怕了, 快救救我。。。" << endl;
}

void Horror::thinking()
{
    cout << "得辅助同伴们一块攻击这个家伙, 不然根本打不过呀!!!" << endl;
}
cpp 复制代码
// Visitor.cpp
// 草帽团
class CaoMaoTeam
{
public:
    CaoMaoTeam()
    {
        m_actions.push_back(new Anger);
        m_actions.push_back(new Horror);
    }
    void add(AbstractMember* member)
    {
        m_members.push_back(member);
    }
    void remove(AbstractMember* member)
    {
        m_members.remove(member);
    }
    void display()
    {
        for (const auto& item : m_members)
        {
            int index = rand() % 2;
            item->accept(m_actions[index]);
        }
    }
    ~CaoMaoTeam()
    {
        for (const auto& item : m_members)
        {
            delete item;
        }
        for (const auto& item : m_actions)
        {
            delete item;
        }
    }
private:
    list<AbstractMember*> m_members;
    vector<AbstractAction*> m_actions;
};
cpp 复制代码
int main()
{
    srand(time(NULL));
    vector<string> names{
        "路飞", "索隆","山治", "乔巴", "弗兰奇", "乌索普", "布鲁克"
    };
    CaoMaoTeam* caomao = new CaoMaoTeam;
    for (const auto& item : names)
    {
        caomao->add(new MaleMember(item));
    }
    caomao->add(new FemaleMember("娜美"));
    caomao->add(new FemaleMember("罗宾"));
    caomao->display();
    delete caomao;
    return 0;
}

特点

由于访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。当系统中存在一个较为复杂的对象结构,且不同访问者对其所采取的操作也不相同时,可以考虑使用访问者模式进行设计。在XML文档解析、编译器的设计、复杂集合对象的处理等领域访问者模式得到了一定的应用。

⚠️**访问者不是常用的设计模式, 因为它不仅复杂, 应用范围也比较狭窄。**

主要优点

  • **增加新的访问操作很方便。**使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合"开闭原则"。
  • **将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。**类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
  • 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

主要缺点

  • **增加新的元素类很困难。**在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了"开闭原则"的要求。
  • **破坏封装。**访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

适用环境

  • **一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。**在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  • **需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。**访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
相关推荐
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充19 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯1 天前
GoF设计模式——解释器模式
java·后端·spring·设计模式