c/c++设计模式---访问者模式
访问者(Visitor)模式:访问器模式,行为型模式。
//(1)一个具体范例的逐渐演化
//阿司匹林肠溶片:改善血液流通,预防血栓形成,血栓形成就产生阻塞,人就会直接面临危险;
//氟伐他汀钠缓释片:降血脂。因为血脂高意味着血流慢,营养无法运输到身体各部位,还很可能引发心脑血管疾病;
//黛力新:医生说我的心脏没有器质性病变,是神经出现了紊乱,出现的症状是因为神经的误报,因此开此药治疗植物神经功能紊乱以及身心疾病伴有的焦虑症状;
//缴费,取药。
//访问者模式:包含 访问者(收费人员、取药人员、营养师、健身教练),被访问者(元素)(药品单中的三种药品) 两个重要角色
//访问:a)串门。b)查看一个东西
#include <iostream> #include <list> #ifdef _DEBUG //只在Debug(调试)模式下 #ifndef DEBUG_NEW #define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符 #define new DEBUG_NEW #endif #endif //#include <boost/type_index.hpp> using namespace std; //#pragma warning(disable : 4996) namespace _nmsp1 { //药品父类 class Medicine { public: virtual string getMdcName() = 0; //药品名称 virtual float getPrice() = 0; //药品总价格,单位:元 virtual ~Medicine() {} //做父类应该有个虚析构函数 }; //药品:阿司匹林肠溶片 class M_asplcrp :public Medicine { public: virtual string getMdcName() { return "阿司匹林肠溶片"; } virtual float getPrice() { return 46.8f; //药品总价 } }; //药品:氟伐他汀钠缓释片 class M_fftdnhsp :public Medicine { public: virtual string getMdcName() { return "氟伐他汀钠缓释片"; } virtual float getPrice() { return 111.3f; //药品总价 } }; //药品:黛力新 class M_dlx :public Medicine { public: virtual string getMdcName() { return "黛力新"; } virtual float getPrice() { return 122.0f; //药品总价 } }; //---------------- //针对药品的处理相关类 class MedicineProc { public: //增加药品到药品列表中 void addMedicine(Medicine* p_mdc) { m_mdclist.push_back(p_mdc); } //针对费用缴纳和取药所做的处理动作 void procAction(string strvisitor) //strvisitor代表拿到了药品单的人,不同的人拿到药品单所要做的处理不同 { if (strvisitor == "收费人员") //收费人员要根据药品单向我(患者)收取费用 { float totalcost = 0.0f; //总费用 for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter) { float tmpprice = (*iter)->getPrice(); cout << "收费人员累计药品\"" << (*iter)->getMdcName() << "\"的价格:" << tmpprice << endl; totalcost += tmpprice; }//end for cout << "所有药品的总价为:" << totalcost << ",收费人员收取了我的费用!" << endl; } else if (strvisitor == "取药人员") //取药人员要根据药品单为我拿药 { for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter) { cout << "取药人员将药品\"" << (*iter)->getMdcName() << "\"拿给了我!" << endl; } } else if (strvisitor == "营养师") { cout << "营养师建议:主食中多搭配粗粮,适当食用肉类!" << endl; } else if (strvisitor == "健身教练") { cout << "健身教练建议:多做有氧运动比如慢跑(35分钟即可),慢跑前后要热身,忌焦虑,忌熬夜,以22:30之前卧床休息为宜!" << endl; } } private: list <Medicine*> m_mdclist; //药品列表,记录着药品单上的所有药品。 }; //------------------- //访问者父类 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; //访问元素:黛力新 //virtual void Visit(M_asplcrp* pelem) = 0; //访问元素:阿司匹林肠溶片 //virtual void Visit(M_fftdnhsp* pelem) = 0; //访问元素:氟伐他汀钠缓释片 //virtual void Visit(M_dlx* pelem) = 0; //访问元素:黛力新 }; //收费人员访问者子类 class Visitor_SFRY : public Visitor { 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 { 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; } }; } int main() { _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);//程序退出时检测内存泄漏并显示到“输出”窗口 _nmsp1::Medicine* pm1 = new _nmsp1::M_asplcrp(); _nmsp1::Medicine* pm2 = new _nmsp1::M_fftdnhsp(); _nmsp1::Medicine* pm3 = new _nmsp1::M_dlx(); _nmsp1::MedicineProc mdcprocobj; mdcprocobj.addMedicine(pm1); mdcprocobj.addMedicine(pm2); mdcprocobj.addMedicine(pm3); mdcprocobj.procAction("收费人员"); mdcprocobj.procAction("取药人员"); //释放资源 delete pm1; delete pm2; delete pm3; return 0; }
(2)引入访问者(Visitor)模式
//定义/实现意图:提供一个作用于某对象结构中的各元素的操作表示,使可以在不改变各元素的前提下定义(扩展)作用域这些元素的新操作。
//解释:允许一个或者多个操作应用到一组对象上,使对象本身和操作解耦。
#include <iostream> #include <list> #ifdef _DEBUG //只在Debug(调试)模式下 #ifndef DEBUG_NEW #define DEBUG_NEW new(_NORMAL_BLOCK,__FILE__,__LINE__) //重新定义new运算符 #define new DEBUG_NEW #endif #endif //#include <boost/type_index.hpp> using namespace std; //#pragma warning(disable : 4996) namespace _nmsp1 { class Visitor; //类前向声明 //药品父类 class Medicine { public: virtual string getMdcName() = 0; //药品名称 virtual float getPrice() = 0; //药品总价格,单位:元 virtual void Accept(Visitor* pvisitor) = 0; //这里的形参是访问和父类指针。 virtual ~Medicine() {} //做父类应该有个虚析构函数 }; //药品:阿司匹林肠溶片 class M_asplcrp :public Medicine { public: virtual string getMdcName() { return "阿司匹林肠溶片"; } virtual float getPrice() { return 46.8f; //药品总价 } virtual void Accept(Visitor* pvisitor); }; //药品:氟伐他汀钠缓释片 class M_fftdnhsp :public Medicine { public: virtual string getMdcName() { return "氟伐他汀钠缓释片"; } virtual float getPrice() { return 111.3f; //药品总价 } virtual void Accept(Visitor* pvisitor); }; //药品:黛力新 class M_dlx :public Medicine { public: virtual string getMdcName() { return "黛力新"; } virtual float getPrice() { return 122.0f; //药品总价 } virtual void Accept(Visitor* pvisitor); }; /* //---------------- //针对药品的处理相关类 class MedicineProc { public: //增加药品到药品列表中 void addMedicine(Medicine* p_mdc) { m_mdclist.push_back(p_mdc); } //针对费用缴纳和取药所做的处理动作 void procAction(string strvisitor) //strvisitor代表拿到了药品单的人,不同的人拿到药品单所要做的处理不同 { if (strvisitor == "收费人员") //收费人员要根据药品单向我(患者)收取费用 { float totalcost = 0.0f; //总费用 for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter) { float tmpprice = (*iter)->getPrice(); cout << "收费人员累计药品\"" << (*iter)->getMdcName() << "\"的价格:" << tmpprice << endl; totalcost += tmpprice; }//end for cout << "所有药品的总价为:" << totalcost << ",收费人员收取了我的费用!" << endl; } else if (strvisitor == "取药人员") //取药人员要根据药品单为我拿药 { for (auto iter = m_mdclist.begin(); iter != m_mdclist.end(); ++iter) { cout << "取药人员将药品\"" << (*iter)->getMdcName() << "\"拿给了我!" << endl; } } else if (strvisitor == "营养师") { cout << "营养师建议:主食中多搭配粗粮,适当食用肉类!" << endl; } else if (strvisitor == "健身教练") { cout << "健身教练建议:多做有氧运动比如慢跑(35分钟即可),慢跑前后要热身,忌焦虑,忌熬夜,以22:30之前卧床休息为宜!" << endl; } } private: list <Medicine*> m_mdclist; //药品列表,记录着药品单上的所有药品。 }; */ //------------------- //访问者父类 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; //访问元素:黛力新 //virtual void Visit(M_asplcrp* pelem) = 0; //访问元素:阿司匹林肠溶片 //virtual void Visit(M_fftdnhsp* pelem) = 0; //访问元素:氟伐他汀钠缓释片 //virtual void Visit(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 { 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 ObjectStructor { 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() { _nmsp1::Visitor_SFRY visitor_sf; //收费人员访问者子类,里面承载着向我(患者)收费的算法 _nmsp1::M_asplcrp mdc_asplcrp; _nmsp1::M_fftdnhsp mdc_fftdnhsp; _nmsp1::M_dlx m_dc_dlx; //各个元素子类调用Accept接受访问者的访问,就可以实现访问者要实现的功能 mdc_asplcrp.Accept(&visitor_sf); //累加 阿司匹林肠溶片....价格 mdc_fftdnhsp.Accept(&visitor_sf); //累加 氟伐他汀钠缓释片....价格 m_dc_dlx.Accept(&visitor_sf); //累加 黛力新....价格 cout << "所有药品的总价为:" << visitor_sf.getTotalCost() << ",收费人员收取了我的费用!" << endl; _nmsp1::Visitor_QYRY visitor_qy; //取药人员访问者子类,里面承载着向我发放药品的算法 mdc_asplcrp.Accept(&visitor_qy); //我取得阿司匹林肠溶片 mdc_fftdnhsp.Accept(&visitor_qy); //我取得氟伐他汀钠缓释片. m_dc_dlx.Accept(&visitor_qy); //我取得黛力新 _nmsp1::Visitor_YYS visitor_yys; //营养师访问者子类,里面承载着为我配置营养餐的算法 mdc_asplcrp.Accept(&visitor_yys); //营养师针对治疗预防血栓的药 阿司匹林肠溶片 给出的对应的营养餐建议 mdc_fftdnhsp.Accept(&visitor_yys); //营养师针对降低血脂的药 氟伐他汀钠缓释片 给出的对应的营养餐建议 m_dc_dlx.Accept(&visitor_yys); //营养师针对治疗神经紊乱的药 黛力新 给出的对应的营养餐建议 _nmsp1::ObjectStructor objstruc; objstruc.addMedicine(&mdc_asplcrp); objstruc.addMedicine(&mdc_fftdnhsp); objstruc.addMedicine(&m_dc_dlx); objstruc.procAction(&visitor_yys); //将一个访问者对象visitor_yys应用到一批元素上,以实现对一批元素进行同一种操作(营养方面的); return 0; }
//角色
//a)Visitor(抽象访问者)
//b)ConcreteVisitor(具体访问者)
//c)Element(抽象元素)。指Medicine类。
//d)ConcreteElement(具体元素):
//e)ObjectStructor(对象结构):
第二种:
//(3)访问者模式深入理解
//双分派/二次分派/双重分派(Double-dispatch)调用机制。
//双分派表示所要执行的操作取决于被访问者(元素)的类型和访问者的种类。
//访问者模式优缺点:
//a)增加新访问者容易,增加被访问者(元素)困难
//b)与操作有关的代码都放到了访问者子类中
//c)要求具体元素类的接口比较丰富。但是这也会导致元素类暴露太多公共操作接口而破坏封装性。
//比较适合的场景
//a)需要对元素对象结构进行很多不同的操作。 将对象自身与针对对象的操作分离的目的。
//b)元素对象结构所属的类很少发生变动,但在元素对象结构上经常需要增加新操作。
//c)一般访问者模式用的不太多,但一旦在需要的场合该模式也会显得无可替代。
再举个例子
在C++中实现访问者模式需要一些基本的设计和代码结构。访问者模式主要涉及到元素(Element)、具体元素(ConcreteElement)、访问者(Visitor)、具体访问者(ConcreteVisitor)和对象结构(ObjectStructure)等组件。
示例实现
假设我们有一个图书馆管理系统,其中有不同类型的图书(如小说、期刊等),我们希望能够为这些图书实现不同的操作(如借阅统计、归还处理等),而不改变图书类本身的结构。
1. 元素接口定义
// 元素接口 class Element { public: virtual ~Element() {} virtual void accept(class Visitor &visitor) = 0; };
2. 具体元素类定义
// 具体元素类:小说 class Novel : public Element { public: void accept(Visitor &visitor) override { visitor.visitNovel(this); } // 其他小说特有的方法和属性 }; // 具体元素类:期刊 class Journal : public Element { public: void accept(Visitor &visitor) override { visitor.visitJournal(this); } // 其他期刊特有的方法和属性 };
3. 访问者接口定义
// 访问者接口 class Visitor { public: virtual ~Visitor() {} virtual void visitNovel(Novel *novel) = 0; virtual void visitJournal(Journal *journal) = 0; };
4. 具体访问者类定义
// 具体访问者类:借阅统计访问者 class BorrowingStatisticsVisitor : public Visitor { public: void visitNovel(Novel *novel) override { // 统计借阅小说的操作 cout << "统计小说:" << novel->getTitle() << endl; } void visitJournal(Journal *journal) override { // 统计借阅期刊的操作 cout << "统计期刊:" << journal->getTitle() << endl; } }; // 具体访问者类:归还处理访问者 class ReturnProcessingVisitor : public Visitor { public: void visitNovel(Novel *novel) override { // 处理小说归还的操作 cout << "归还小说:" << novel->getTitle() << endl; } void visitJournal(Journal *journal) override { // 处理期刊归还的操作 cout << "归还期刊:" << journal->getTitle() << endl; } };
5. 对象结构类定义
// 对象结构类 class Library { public: void attach(Element *element) { elements.push_back(element); } void detach(Element *element) { elements.remove(element); } void accept(Visitor &visitor) { for (auto elem : elements) { elem->accept(visitor); } } private: list<Element *> elements; };
6. 示例使用
int main() { Library library; // 添加一些图书 Novel novel1; Journal journal1; library.attach(&novel1); library.attach(&journal1); // 创建具体访问者 BorrowingStatisticsVisitor borrowingVisitor; ReturnProcessingVisitor returnVisitor; // 不同的访问者对图书进行不同操作 library.accept(borrowingVisitor); library.accept(returnVisitor); return 0; }
解释
-
元素(Element):在示例中是
Element
抽象类,定义了accept
方法,由具体的图书类(如Novel
、Journal
)继承并实现accept
方法。 -
访问者(Visitor):定义了对不同类型图书的访问方法,如
visitNovel
和visitJournal
。 -
具体访问者(ConcreteVisitor):实现了
Visitor
接口,定义了具体的访问操作,如BorrowingStatisticsVisitor
和ReturnProcessingVisitor
。 -
对象结构(ObjectStructure):在示例中是
Library
类,管理了图书的集合,可以接受不同访问者的访问。
注意事项
- 访问者模式适用于当一个对象结构中的元素类很少变化,但经常需要在此对象结构上定义新的操作时。
- 在示例中,为了简化代码,省略了图书类的详细实现,实际应用中可能还需要考虑图书的具体属性和方法。
这个示例展示了如何在C++中实现访问者模式,通过访问者模式可以方便地增加新的操作,同时避免修改现有的元素类