目录
C++ 设计模式——访问者模式
访问者模式(Visitor Pattern)是一种行为设计模式,其目的是将数据结构与数据操作分离,使得在不修改已有程序代码的情况下,可以添加新的操作。这种模式通过定义一个访问者类,来改变一个元素类的执行算法。访问者模式使得你能够在不改变元素类的前提下,定义作用于这些元素的新操作。
引人“访问者”模式的定义(实现意图):提供一个作用于某对象结构中的各元素的操作表示,便可以在不改变各元素类的前提下定义(扩展)作用于这些元素的新操作。
1. 主要组成成分
- 抽象访问者(Visitor): 提供一个访问元素的接口,声明了一系列访问具体元素的方法。
- 具体访问者(Concrete Visitor): 实现抽象访问者中声明的操作,定义对每个元素的具体处理逻辑。
- 元素接口(Element): 声明一个接受访问者的方法(
accept
)。 - 具体元素(Concrete Element): 实现元素接口,通过接受访问者的方式允许访问者对其进行操作。
- 对象结构(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图中包含如下5种角色。
- Visitor (抽象访问者):为对象结构中的每个元素子类(例如
M_asplcrp
、M_fftdnhsp
、M_dlx
)声明一个访问操作(以Visit
开头的方法)。通过这些操作的名称或参数类型,可以明确知道要访问的元素子类的类型。具体的访问者子类需要实现这些访问操作。此处指的是Visitor
类。 - ConcreteVisitor (具体访问者):实现由抽象访问者声明的每个访问操作。每个操作用于访问对象结构中的一种特定类型的元素。这里指的是
Visitor_SFRY
、Visitor_QYRY
和Visitor_YYS
类。 - Element (抽象元素):定义了一个
Accept
方法,该方法通常接受一个指向抽象访问者类型的指针作为形参。这里指的是Medicine
类。 - ConcreteElement (具体元素):实现
Accept
方法,在该方法中调用访问者子类中的访问操作(以Visit
开头的方法),以便访问者能够对元素执行操作。这里指的是M_asplcrp
、M_fftdnhsp
和M_dlx
类。 - ObjectStructure (对象结构):包含多个元素的集合,用于存储元素对象并提供遍历其内部元素的接口,通常用于对一组元素执行统一操作。
4. 访问者模式的优点
- 增加新操作容易:可以通过添加新的访问者类来增加新的操作,这符合开闭原则,允许系统易于扩展和维护。
- 聚合操作:访问者模式使得可以将相关的操作集中到一个访问者中,而不是分散在各个元素类中,这有助于组织和集中管理相关操作,减少系统的复杂性。
- 累积状态:访问者可以在访问元素时累积状态,而不需要将这些状态存储在元素之中,这有助于避免元素类变得臃肿,同时可以轻松地添加新的累积逻辑。
5. 访问者模式的缺点
- 破坏封装:访问者模式通常需要元素暴露一些原本应为私有的实现细节给访问者,这违反了面向对象的封装原则。
- 难以维护:如果经常添加新的元素类,每次添加都需要修改所有访问者类以添加新的访问操作,这可能导致代码难以维护。
- 复杂度增加:使用访问者模式会增加系统的复杂度,学习和理解系统的难度增加,特别是在具有大量元素和访问者的系统中。
6. 访问者模式适用场景
- 操作频繁变化:当系统的数据结构相对稳定,但操作经常变化时,使用访问者模式可以使这些操作易于修改和扩展。
- 需要对复合对象执行多种不相关的操作:当需要对一组对象结构执行很多不相关的操作时,而你又希望避免这些操作“污染”这些对象的类。
- 区分多种类型的元素:当一个对象结构包含多种类型的元素,并且你希望对这些元素执行一些依赖于其具体类型的操作时。
总结
在实际应用中,访问者模式特别适用于数据结构相对稳定,但操作经常变化的场景,例如在多种不同类型的对象上执行多种不相关的操作。通过将操作逻辑封装在访问者中,可以保持元素类的简洁并易于管理和扩展。总之,访问者模式是一种非常有用的设计工具,可以有效地帮助开发者管理和扩展复杂系统中的操作。
完整代码
#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;
}