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++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

C++读写word文档(.docx)DuckX库的使用详解

《C++读写word文档(.docx)DuckX库的使用详解》DuckX是C++库,用于创建/编辑.docx文件,支持读取文档、添加段落/片段、编辑表格,解决中文乱码需更改编码方案,进阶功能含文本替换... 目录一、基本用法1. 读取文档3. 添加段落4. 添加片段3. 编辑表格二、进阶用法1. 文本替换2

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

C++右移运算符的一个小坑及解决

《C++右移运算符的一个小坑及解决》文章指出右移运算符处理负数时左侧补1导致死循环,与除法行为不同,强调需注意补码机制以正确统计二进制1的个数... 目录我遇到了这么一个www.chinasem.cn函数由此可以看到也很好理解总结我遇到了这么一个函数template<typename T>unsigned

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

深入解析C++ 中std::map内存管理

《深入解析C++中std::map内存管理》文章详解C++std::map内存管理,指出clear()仅删除元素可能不释放底层内存,建议用swap()与空map交换以彻底释放,针对指针类型需手动de... 目录1️、基本清空std::map2️、使用 swap 彻底释放内存3️、map 中存储指针类型的对象

C++ STL-string类底层实现过程

《C++STL-string类底层实现过程》本文实现了一个简易的string类,涵盖动态数组存储、深拷贝机制、迭代器支持、容量调整、字符串修改、运算符重载等功能,模拟标准string核心特性,重点强... 目录实现框架一、默认成员函数1.默认构造函数2.构造函数3.拷贝构造函数(重点)4.赋值运算符重载函数

C++ vector越界问题的完整解决方案

《C++vector越界问题的完整解决方案》在C++开发中,std::vector作为最常用的动态数组容器,其便捷性与性能优势使其成为处理可变长度数据的首选,然而,数组越界访问始终是威胁程序稳定性的... 目录引言一、vector越界的底层原理与危害1.1 越界访问的本质原因1.2 越界访问的实际危害二、基

C#和Unity中的中介者模式使用方式

《C#和Unity中的中介者模式使用方式》中介者模式通过中介者封装对象交互,降低耦合度,集中控制逻辑,适用于复杂系统组件交互场景,C#中可用事件、委托或MediatR实现,提升可维护性与灵活性... 目录C#中的中介者模式详解一、中介者模式的基本概念1. 定义2. 组成要素3. 模式结构二、中介者模式的特点