C++ 设计模式——访问者模式

2024-09-01 11:44

本文主要是介绍C++ 设计模式——访问者模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • C++ 设计模式——访问者模式
      • 1. 主要组成成分
      • 2. 逐步构建访问者模式
        • 步骤1: 创建元素接口和具体元素
        • 步骤2: 创建抽象访问者和具体访问者
        • 步骤3:创建对象结构
        • 步骤4: 客户端使用访问者模式
      • 3. 访问者模式 UML 图
        • UML 图解析
      • 4. 访问者模式的优点
      • 5. 访问者模式的缺点
      • 6. 访问者模式适用场景
      • 总结
      • 完整代码

C++ 设计模式——访问者模式

访问者模式(Visitor Pattern)是一种行为设计模式,其目的是将数据结构与数据操作分离,使得在不修改已有程序代码的情况下,可以添加新的操作。这种模式通过定义一个访问者类,来改变一个元素类的执行算法。访问者模式使得你能够在不改变元素类的前提下,定义作用于这些元素的新操作。

引人“访问者”模式的定义(实现意图):提供一个作用于某对象结构中的各元素的操作表示,便可以在不改变各元素类的前提下定义(扩展)作用于这些元素的新操作。

1. 主要组成成分

  1. 抽象访问者(Visitor): 提供一个访问元素的接口,声明了一系列访问具体元素的方法。
  2. 具体访问者(Concrete Visitor): 实现抽象访问者中声明的操作,定义对每个元素的具体处理逻辑。
  3. 元素接口(Element): 声明一个接受访问者的方法(accept)。
  4. 具体元素(Concrete Element): 实现元素接口,通过接受访问者的方式允许访问者对其进行操作。
  5. 对象结构(Object Structure): 能枚举它的元素,可以提供一个高层的接口以允许访问者访问其元素。

2. 逐步构建访问者模式

该示例代码将展示如何实现一个药品管理系统,使用访问者模式来处理不同的操作,如收费、取药和营养建议。

步骤1: 创建元素接口和具体元素

首先,定义一个Medicine类作为所有药品的基类,它包含一个名为accept的方法,该方法用于接受一个访问者对象。然后,创建具体的药品类,如M_asplcrp(阿司匹林肠溶片),M_fftdnhsp(氟伐他汀钠缓释片)和M_dlx(黛力新),它们都继承自Medicine并实现accept方法。

class Visitor; //类前向声明
//药品父类
class Medicine
{
public:virtual void Accept(Visitor* pvisitor) = 0;//这里的形参是访问者父类指针
public:virtual string getMdcName() = 0;  //药品名称virtual float getPrice() = 0;     //药品总价格,单位:元
};//药品:阿司匹林肠溶片
class M_asplcrp : public Medicine
{
public:virtual string getMdcName(){return "阿司匹林肠溶片";}virtual float getPrice(){return 46.8f;    //为简化代码,直接给出药品总价而不单独强调药品数量了,比如该药品医生给开了两盒,一盒是23.4,那么这里直接返回两盒的价格}public:virtual void Accept(Visitor* pvisitor);};
//药品:氟伐他汀钠缓释片
class M_fftdnhsp : public Medicine
{
public:virtual string getMdcName(){return "氟伐他汀钠缓释片";}virtual float getPrice(){return 111.3f;    //三盒的价格}public:virtual void Accept(Visitor* pvisitor);};
//药品:黛力新
class M_dlx : public Medicine
{
public:virtual string getMdcName(){return "黛力新";}virtual float getPrice(){return 122.0f;    //两盒的价格}public:virtual void Accept(Visitor* pvisitor);};//各个药品子类Accept方法的实现体代码
void M_asplcrp::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_asplcrp(this);
}
void M_fftdnhsp::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_fftdnhsp(this);
}
void M_dlx::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_dlx(this);
}
步骤2: 创建抽象访问者和具体访问者

然后,定义Visitor类作为抽象访问者,它包含对每种类型药品的访问方法。接着实现具体访问者类如Visitor_SFRY(收费人员),Visitor_QYRY(取药人员)和Visitor_YYS(营养师),分别定义它们如何处理每种药品。

//访问者父类
class Visitor
{
public:virtual ~Visitor() {} //做父类时析构函数应该为虚函数virtual void Visit_elm_asplcrp(M_asplcrp* pelem) = 0;//访问元素:阿司匹林肠溶片virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem) = 0;//访问元素:氟伐他汀钠缓释片virtual void Visit_elm_dlx(M_dlx* pelem) = 0; //访问元素:黛力新
};//收费人员访问者子类
class Visitor_SFRY : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}virtual void Visit_elm_dlx(M_dlx* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}//返回总费用float getTotalCost(){return m_totalcost;}
private:float m_totalcost = 0.0f;  //总费用
};//取药人员访问者子类
class Visitor_QYRY : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}virtual void Visit_elm_dlx(M_dlx* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}
};//营养师访问者子类
class Visitor_YYS : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){cout << "营养师建议:“多吃粗粮少吃油,可以有效的预防血栓”!" << endl;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){cout << "营养师建议:“多吃蘑菇、洋葱、,猕猴桃,可以有效的降低血脂”!" << endl;}virtual void Visit_elm_dlx(M_dlx* pelem){cout << "营养师建议:“多出去走走呼吸新鲜空气,多晒太阳,多去人多热闹的地方,保持乐观开朗的心情”!" << endl;}
};
步骤3:创建对象结构

接着,定义一个ObjectStructure类来管理药品集合,并允许将访问者应用于这些集合。这个类负责维护药品列表,并提供一个方法来执行访问者的操作。对象结构是连接元素和访问者的桥梁,使得不同的访问者可以对元素集合执行不同的操作。

//对象结构
class ObjectStructure
{
public://增加药品到药品列表中void addMedicine(Medicine* p_mdc){m_mdclist.push_back(p_mdc);}void procAction(Visitor* pvisitor){for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter){(*iter)->Accept(pvisitor);}}
private:list <Medicine*> m_mdclist;  //药品列表
};
步骤4: 客户端使用访问者模式

在客户端代码中,创建ObjectStructure对象,添加药品元素,并创建访问者对象。通过调用ObjectStructure的方法,将访问者应用到所有药品上,以实现具体的功能,如收费、取药或提供营养建议。

int main()
{Visitor_SFRY visitor_sf; //收费人员访问者子类,里面承载着向我(患者)收费的算法M_asplcrp mdc_asplcrp;M_fftdnhsp mdc_fftdnhsp;M_dlx mdc_dlx;//各个元素子类调用Accept接受访问者的访问,就可以实现访问者要实现的功能mdc_asplcrp.Accept(&visitor_sf); //累加“阿司匹林肠溶片”的价格mdc_fftdnhsp.Accept(&visitor_sf);//累加“氟伐他汀钠缓释片”的价格mdc_dlx.Accept(&visitor_sf);     //累加“黛力新”的价格cout << "所有药品的总为:" << visitor_sf.getTotalCost() << ",收费人员收取了我的费用!" << endl;//----Visitor_QYRY visitor_qy; //取药人员访问者子类,里面承载着向我发放药品的算法mdc_asplcrp.Accept(&visitor_qy); //我取得“阿司匹林肠溶片”mdc_fftdnhsp.Accept(&visitor_qy);// 我取得“氟伐他汀钠缓释片”mdc_dlx.Accept(&visitor_qy);     //我取得“黛力新”//-----Visitor_YYS visitor_yys;  //营养师访问者子类,里面承载着为我配置营养餐的算法mdc_asplcrp.Accept(&visitor_yys); //营养师针对治疗预防血栓药“阿司匹林肠溶片”给出的对应的营养餐建议mdc_fftdnhsp.Accept(&visitor_yys);//营养师针对降血脂药“氟伐他汀钠缓释片”给出的对应的营养餐建议mdc_dlx.Accept(&visitor_yys);     //营养师针对治疗神经紊乱药“黛力新”给出的对应的营养餐建议//---------ObjectStructure objstruc;objstruc.addMedicine(&mdc_asplcrp);objstruc.addMedicine(&mdc_fftdnhsp);objstruc.addMedicine(&mdc_dlx);objstruc.procAction(&visitor_yys); //将一个访问者对象(visitor_yys)应用到一批元素上,以实现对一批元素进行同一种(营养方面的)操作return 0;
}

3. 访问者模式 UML 图

访问者模式 UML 图

UML 图解析

访问者模式的 UML图中包含如下5种角色。

  • Visitor (抽象访问者):为对象结构中的每个元素子类(例如M_asplcrpM_fftdnhspM_dlx)声明一个访问操作(以Visit开头的方法)。通过这些操作的名称或参数类型,可以明确知道要访问的元素子类的类型。具体的访问者子类需要实现这些访问操作。此处指的是Visitor类。
  • ConcreteVisitor (具体访问者):实现由抽象访问者声明的每个访问操作。每个操作用于访问对象结构中的一种特定类型的元素。这里指的是Visitor_SFRYVisitor_QYRYVisitor_YYS类。
  • Element (抽象元素):定义了一个Accept方法,该方法通常接受一个指向抽象访问者类型的指针作为形参。这里指的是Medicine类。
  • ConcreteElement (具体元素):实现Accept方法,在该方法中调用访问者子类中的访问操作(以Visit开头的方法),以便访问者能够对元素执行操作。这里指的是M_asplcrpM_fftdnhspM_dlx类。
  • ObjectStructure (对象结构):包含多个元素的集合,用于存储元素对象并提供遍历其内部元素的接口,通常用于对一组元素执行统一操作。

4. 访问者模式的优点

  1. 增加新操作容易:可以通过添加新的访问者类来增加新的操作,这符合开闭原则,允许系统易于扩展和维护。
  2. 聚合操作:访问者模式使得可以将相关的操作集中到一个访问者中,而不是分散在各个元素类中,这有助于组织和集中管理相关操作,减少系统的复杂性。
  3. 累积状态:访问者可以在访问元素时累积状态,而不需要将这些状态存储在元素之中,这有助于避免元素类变得臃肿,同时可以轻松地添加新的累积逻辑。

5. 访问者模式的缺点

  1. 破坏封装:访问者模式通常需要元素暴露一些原本应为私有的实现细节给访问者,这违反了面向对象的封装原则。
  2. 难以维护:如果经常添加新的元素类,每次添加都需要修改所有访问者类以添加新的访问操作,这可能导致代码难以维护。
  3. 复杂度增加:使用访问者模式会增加系统的复杂度,学习和理解系统的难度增加,特别是在具有大量元素和访问者的系统中。

6. 访问者模式适用场景

  1. 操作频繁变化:当系统的数据结构相对稳定,但操作经常变化时,使用访问者模式可以使这些操作易于修改和扩展。
  2. 需要对复合对象执行多种不相关的操作:当需要对一组对象结构执行很多不相关的操作时,而你又希望避免这些操作“污染”这些对象的类。
  3. 区分多种类型的元素:当一个对象结构包含多种类型的元素,并且你希望对这些元素执行一些依赖于其具体类型的操作时。

总结

在实际应用中,访问者模式特别适用于数据结构相对稳定,但操作经常变化的场景,例如在多种不同类型的对象上执行多种不相关的操作。通过将操作逻辑封装在访问者中,可以保持元素类的简洁并易于管理和扩展。总之,访问者模式是一种非常有用的设计工具,可以有效地帮助开发者管理和扩展复杂系统中的操作。

完整代码

#include <iostream>
#include <list>using namespace std;class Visitor; //类前向声明
//药品父类
class Medicine
{
public:virtual void Accept(Visitor* pvisitor) = 0;//这里的形参是访问者父类指针
public:virtual string getMdcName() = 0;  //药品名称virtual float getPrice() = 0;     //药品总价格,单位:元
};//药品:阿司匹林肠溶片
class M_asplcrp : public Medicine
{
public:virtual string getMdcName(){return "阿司匹林肠溶片";}virtual float getPrice(){return 46.8f;    //为简化代码,直接给出药品总价而不单独强调药品数量了,比如该药品医生给开了两盒,一盒是23.4,那么这里直接返回两盒的价格}public:virtual void Accept(Visitor* pvisitor);};
//药品:氟伐他汀钠缓释片
class M_fftdnhsp : public Medicine
{
public:virtual string getMdcName(){return "氟伐他汀钠缓释片";}virtual float getPrice(){return 111.3f;    //三盒的价格}public:virtual void Accept(Visitor* pvisitor);};
//药品:黛力新
class M_dlx : public Medicine
{
public:virtual string getMdcName(){return "黛力新";}virtual float getPrice(){return 122.0f;    //两盒的价格}public:virtual void Accept(Visitor* pvisitor);};//----------------
//访问者父类
class Visitor
{
public:virtual ~Visitor() {} //做父类时析构函数应该为虚函数virtual void Visit_elm_asplcrp(M_asplcrp* pelem) = 0;//访问元素:阿司匹林肠溶片virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem) = 0;//访问元素:氟伐他汀钠缓释片virtual void Visit_elm_dlx(M_dlx* pelem) = 0; //访问元素:黛力新
};//收费人员访问者子类
class Visitor_SFRY : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}virtual void Visit_elm_dlx(M_dlx* pelem){float tmpprice = pelem->getPrice();cout << "收费人员累计药品“" << pelem->getMdcName() << "”的价格:" << tmpprice << endl;m_totalcost += tmpprice;}//返回总费用float getTotalCost(){return m_totalcost;}
private:float m_totalcost = 0.0f;  //总费用
};//取药人员访问者子类
class Visitor_QYRY : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}virtual void Visit_elm_dlx(M_dlx* pelem){cout << "取药人员将药品“" << pelem->getMdcName() << "”拿给了我!" << endl;}
};//营养师访问者子类
class Visitor_YYS : public Visitor
{
public:virtual void Visit_elm_asplcrp(M_asplcrp* pelem){cout << "营养师建议:“多吃粗粮少吃油,可以有效的预防血栓”!" << endl;}virtual void Visit_elm_fftdnhsp(M_fftdnhsp* pelem){cout << "营养师建议:“多吃蘑菇、洋葱、,猕猴桃,可以有效的降低血脂”!" << endl;}virtual void Visit_elm_dlx(M_dlx* pelem){cout << "营养师建议:“多出去走走呼吸新鲜空气,多晒太阳,多去人多热闹的地方,保持乐观开朗的心情”!" << endl;}
};//各个药品子类Accept方法的实现体代码
void M_asplcrp::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_asplcrp(this);
}
void M_fftdnhsp::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_fftdnhsp(this);
}
void M_dlx::Accept(Visitor* pvisitor)
{pvisitor->Visit_elm_dlx(this);
}//-------------//对象结构
class ObjectStructure
{
public://增加药品到药品列表中void addMedicine(Medicine* p_mdc){m_mdclist.push_back(p_mdc);}void procAction(Visitor* pvisitor){for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter){(*iter)->Accept(pvisitor);}}
private:list <Medicine*> m_mdclist;  //药品列表
};int main()
{Visitor_SFRY visitor_sf; //收费人员访问者子类,里面承载着向我(患者)收费的算法M_asplcrp mdc_asplcrp;M_fftdnhsp mdc_fftdnhsp;M_dlx mdc_dlx;//各个元素子类调用Accept接受访问者的访问,就可以实现访问者要实现的功能mdc_asplcrp.Accept(&visitor_sf); //累加“阿司匹林肠溶片”的价格mdc_fftdnhsp.Accept(&visitor_sf);//累加“氟伐他汀钠缓释片”的价格mdc_dlx.Accept(&visitor_sf);     //累加“黛力新”的价格cout << "所有药品的总为:" << visitor_sf.getTotalCost() << ",收费人员收取了我的费用!" << endl;//----Visitor_QYRY visitor_qy; //取药人员访问者子类,里面承载着向我发放药品的算法mdc_asplcrp.Accept(&visitor_qy); //我取得“阿司匹林肠溶片”mdc_fftdnhsp.Accept(&visitor_qy);// 我取得“氟伐他汀钠缓释片”mdc_dlx.Accept(&visitor_qy);     //我取得“黛力新”//-----Visitor_YYS visitor_yys;  //营养师访问者子类,里面承载着为我配置营养餐的算法mdc_asplcrp.Accept(&visitor_yys); //营养师针对治疗预防血栓药“阿司匹林肠溶片”给出的对应的营养餐建议mdc_fftdnhsp.Accept(&visitor_yys);//营养师针对降血脂药“氟伐他汀钠缓释片”给出的对应的营养餐建议mdc_dlx.Accept(&visitor_yys);     //营养师针对治疗神经紊乱药“黛力新”给出的对应的营养餐建议//---------ObjectStructure objstruc;objstruc.addMedicine(&mdc_asplcrp);objstruc.addMedicine(&mdc_fftdnhsp);objstruc.addMedicine(&mdc_dlx);objstruc.procAction(&visitor_yys); //将一个访问者对象(visitor_yys)应用到一批元素上,以实现对一批元素进行同一种(营养方面的)操作return 0;
}

这篇关于C++ 设计模式——访问者模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1126845

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)