设计模式之访问者模式


【原文地址】http://blog.csdn.net/hityct1/article/details/4164393

访问者模式(Visitor Pattern)的c++实现示例

访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类或类群(即被访问者)增加新的操作(即访问者)而无需为它们作任何修改。访问者模式属于行为型模式。

为什么要使用访问者模式?
如何扩展一个现有的类层次结构来实现新行为?一般的方法是给类添加新的方法。但是万一新行为和现有对象模型不兼容怎么办?还有,类层次结构设计人员可能无法预知以后开发过程中将会需要哪些功能。以及,如果已有的类层次结构不允许修改代码,怎么能扩展行为呢?
答案是在类层次结构设计中使用访问者模式。

访问者模式涉及的角色:
1)访问者(Visitor)
访问者抽象接口,通过visit(Element)方法访问Element(数据结构),完成对Element的操作行为。
2)具体访问者(ConcreteVisitor)
访问者的具体实现类。
3)元素(Element),也就是被访问者
通过accept(Visitor)方法接受Visitor的访问。
4)具体元素(ConcreteElement)
元素的具体实现类。
5)对象结构(ObjectStructure)
拥有一组元素的组合对象。ObjectStructure本身也可以作为被访问者。

访问者模式的结构图(网上下载的):

当一个应用满足以下条件时,我们可以使用Visitor设计模式:
1)一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

优点:
1)易于添加那些目前尚未考虑到的方法。(这也是使用访问者的原因:扩展功能)
2)可以使类更加小巧,因为那些很少使用的方法,可以在外部定义。(意味着如果一个方法经常使用,最好定义在类中;当然在第一次定义中没有考虑到此方法除外)

缺点:
1)访问者角色不适合具体元素角色经常发生变化的情况。(如:增加新具体元素类,访问者接口就需要改变了。)
2)访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,这就破坏了元素角色的封装性。访问者和被访问的对象的耦合性很大。
3)元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。(因为访问者不能直接访问元素的私有数据)

示例:

[cpp] view plaincopy
  1. #include<iostream>
  2. #include<list>
  3. #include<string>
  4. usingnamespacestd;
  5. classCPerson;
  6. classCStudent;
  7. classCTeacher;
  8. classCVisitor;
  9. classCPrinter;
  10. //元素(被访问者)
  11. classCPerson//纯虚类
  12. {
  13. protected:
  14. stringname;//或者改成数组
  15. intgender;
  16. CPerson()
  17. {
  18. }
  19. public:
  20. virtualvoidAccept(CVisitor&)//=0;//纯虚函数
  21. {
  22. }
  23. public:
  24. voidSetName(conststring&Name)
  25. {
  26. name=Name;
  27. }
  28. stringGetName()const
  29. {
  30. returnname;
  31. }
  32. voidSetGender(constint&Gender)
  33. {
  34. gender=Gender;
  35. }
  36. intGetGender()const
  37. {
  38. returngender;
  39. }
  40. };
  41. //访问者
  42. classCVisitor
  43. {
  44. public:
  45. virtualvoidVisit(CStudent&)=0;
  46. virtualvoidVisit(CTeacher&)=0;
  47. };
  48. //具体元素
  49. classCStudent:publicCPerson
  50. {
  51. private:
  52. intgrade;//年级
  53. public:
  54. CStudent(stringName,intGender,intGrade)
  55. {
  56. name=Name;
  57. gender=Gender;
  58. grade=Grade;
  59. }
  60. public:
  61. virtualvoidAccept(CVisitor&printer)//虚函数
  62. {
  63. printer.Visit(*this);
  64. }
  65. voidSetGrade(intGrade)
  66. {
  67. grade=Grade;
  68. }
  69. intGetGrade()const
  70. {
  71. returngrade;
  72. }
  73. };
  74. //具体元素
  75. classCTeacher:publicCPerson
  76. {
  77. private:
  78. intservice_time;//工龄
  79. public:
  80. CTeacher(stringName,intGrade,intServiceTime)
  81. {
  82. name=Name;
  83. gender=Grade;
  84. service_time=ServiceTime;
  85. }
  86. public:
  87. virtualvoidAccept(CVisitor&printer)
  88. {
  89. printer.Visit(*this);
  90. }
  91. voidSetServiceTime(intServiceTime)
  92. {
  93. service_time=ServiceTime;
  94. }
  95. intGetServiceTime()const
  96. {
  97. returnservice_time;
  98. }
  99. };
  100. //具体访问者
  101. //这里,这个类打印具体元素的信息(就是显示在屏幕上),可根据需要打印不同的内容。
  102. //具体访问者是一种策略,可根据不同需要创建新的具体访问者,而无需修改具体元素(即被访问者)。
  103. classCPrinter:publicCVisitor
  104. {
  105. public:
  106. voidVisit(CStudent&s)
  107. {
  108. cout<<"student:"<<endl;
  109. cout<<"/tName:"<<s.GetName()<<endl;
  110. if(s.GetGender()==0)
  111. cout<<"/tGender:"<<"female"<<endl;
  112. else
  113. cout<<"/tGender:"<<"male"<<endl;
  114. cout<<"/tGrade:"<<s.GetGrade()<<endl;
  115. }
  116. voidVisit(CTeacher&t)
  117. {
  118. cout<<"Teacher:"<<endl;
  119. cout<<"/tName:"<<t.GetName()<<endl;
  120. if(t.GetGender()==0)
  121. cout<<"/tGender:"<<"female"<<endl;
  122. else
  123. cout<<"/tGender:"<<"male"<<endl;
  124. cout<<"/tServiceTime:"<<t.GetServiceTime()<<endl;
  125. }
  126. };
  127. //对象结构
  128. //这里,Organization是个组织(如植树节组成一个团队,去植树),有若干老师,若干学生。
  129. classOrganization
  130. {
  131. private:
  132. typedeflist<CPerson*>CMemberList;
  133. CMemberListmember_list;
  134. public:
  135. voidAdd(CPerson*person)//增加人员。只是示例,没有考虑人员重复等情况。
  136. {
  137. //注意:person指向的空间必须是由new操作符申请的空间
  138. member_list.push_back(person);
  139. }
  140. //只是示例,所以删除成员等操作略去
  141. voidPrintMembers(CPrinter&printer)//输出成员名单
  142. {
  143. CMemberList::iteratoritr=member_list.begin();
  144. for(;itr!=member_list.end();++itr)
  145. {
  146. (*itr)->Accept(printer);
  147. }
  148. }
  149. ~Organization()
  150. {
  151. //删除申请的空间;c++比较麻烦
  152. CMemberList::iteratoritr=member_list.begin();
  153. for(;itr!=member_list.end();++itr)
  154. {
  155. delete*itr;
  156. }
  157. }
  158. };
  159. intmain()
  160. {
  161. OrganizationPlanting;//植树组织
  162. Planting.Add(newCTeacher("Johnny",1,10));
  163. Planting.Add(newCStudent("Catherine",0,1));
  164. Planting.Add(newCStudent("peter",1,2));
  165. CPrinterprinter;//访问者,也可看成一种策略
  166. Planting.PrintMembers(printer);
  167. return0;
  168. }

后话:
使用了访问者模式以后,对于原来的类层次增加新的操作,仅仅需要实现一个具体访问者角色就可以了,而不必修改整个类层次。而且这样符合“开闭原则”的要求。而且每个具体的访问者角色都对应于一个相关操作,因此如果一个操作的需求有变,那么仅仅修改一个具体访问者角色,而不用改动整个类层次。但是“开闭原则”的遵循总是片面的。如果系统中的类层次发生了变化,会对访问者模式产生什么样的影响呢?你必须修改访问者角色和每一个具体访问者角色。
模式设计教材书经常提及的一句话:发现变化并封装之。是否采用访问者模式,就要看(或预见)“变化”是什么了。访问者模式中,“变化”是主要是具体访问者,其次是对象结构。但如果(具体)元素也改变,就万万不能用访问者模式,因为“牵一发动全身”,维护性就太差了。
再唠叨一下为什么使用访问者模式的个人看法:
对象结构使用的了(具体)元素(即被访问者),而(具体)元素的功能不全,而直接在元素中添加功能不太好(如:不允许修改元素;或者增加不常用的功能会使类太臃肿等等),那么我们就定一个辅助类(就是访问者)来完成这个功能。于是,作为预见性,我们为元素(即被访问者)增加一个accept(Visitor)方法作为接口,以作不时之需。为元素增加新的功能,只需增加新的访问者类。


参考文献:
1.《设计模式初学者指南》,徐迎晓等译,机械工业出版社
2.网上,东抄一下,西抄一下。


优质内容筛选与推荐>>
1、Spars Coding理论
2、Zend Studio 7 下载,Zend Studio 7注册机,Zend Studio 注册码
3、Ubuntu下安装Nginx,PHP5(及PHP-FPM),MySQL
4、karma配置文件参数介绍
5、python注释、脚本参数、字节码


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn