设计模式 -- 访问者模式(Visitor)
写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:
- 初识访问者模式,包括: 定义、结构、参考实现
- 体会访问者模式,包括: 场景问题、不用模式的解决方案、使用模式的解决方案
- 理解访问者模式,包括: 认识访问者模式、操作组合对象结构、谁负责遍历所有元素对象、访问者模式的优缺点
- 思考访问者模式,包括: 访问者模式的本质、何时选用
参考内容:
1、《研磨设计模式》 一书,作者:陈臣、王斌
--------------------------------------------------------------------
1、初识访问者模式
1.1、定义
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
如何理解:
(1)首先,要明确访问者就是操作,换句话说,访问者就是功能。
(2)其次,某对象结构中的各元素。比如,树形结构中的各个元素
(3)最后,使用访问模式的目的是给某对象结构中的各元素添加功能,但是又不需要改变各元素的类的定义。
1.2、结构和说明
(
ps:
圆圈1里面就是定义的访问者,理论上可以有N个访问者(也就是具体的功能);
圆圈2里面就是定义的某对象结构,对象结构中的元素理论上也可以有N个
ObjectStructure是用来方便客户端使用时遍历某对象结构中全部的元素。
)
访问者模式结构说明:
- Visitor: 访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。
- ConcreteVisitor: 具体的访问者实现对象,实现要真正被添加到对象结构中的功能。
- Element: 抽象的元素对象,对象结构的顶层接口,定义接受访问的操作。
- ConcreteElement: 具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
- ObjectStructure: 对象结构,通常包含多个被访问的对象,它可以遍历多个被访问的对象,也可以让访问者访问它的元素。
1.3、参考实现
1 (1)首先定义一个接口来代表要新加入的功能,把它称作访问者 2 3 /** 4 * 访问者接口 5 */ 6 public interface Visitor { 7 /** 8 * 访问元素A,相当于给元素A添加访问者的功能 9 * @param elementA 元素A的对象 10 */ 11 public void visitConcreteElementA(ConcreteElementA elementA); 12 13 /** 14 * 访问元素B,相当于给元素B添加访问者的功能 15 * @param elementA 元素B的对象 16 */ 17 public void visitConcreteElementB(ConcreteElementB elementB); 18 } 19 20 (2)抽象的元素对象定义 21 /** 22 * 被访问的元素的接口 23 */ 24 public abstract class Element { 25 /** 26 * 接受访问者的访问 27 * @param visitor 访问者 28 */ 29 public abstract void accept(Visitor visitor); 30 } 31 32 (3)具体的元素A和元素B 33 /** 34 * 具体元素的实现对象 35 */ 36 public class ConcreteElementA extends Element { 37 38 @Override 39 public void accept(Visitor visitor) { 40 //回调访问者对象的相应方法 41 visitor.visitConcreteElementA(this); 42 } 43 44 /** 45 * 已有的功能 46 */ 47 public void opertionA(){ 48 49 } 50 } 51 52 /** 53 * 具体元素的实现对象 54 */ 55 public class ConcreteElementB extends Element { 56 57 @Override 58 public void accept(Visitor visitor) { 59 //回调访问者对象的相应方法 60 visitor.visitConcreteElementB(this); 61 } 62 63 public void opertionB(){ 64 //已有的功能实现 65 } 66 } 67 68 69 (4)访问者的具体实现 70 /** 71 * 具体的访问者实现 72 */ 73 public class ConcreteVisitor1 implements Visitor { 74 75 @Override 76 public void visitConcreteElementA(ConcreteElementA elementA) { 77 //把要访问ConcreteElementA时,需要执行的功能实现在这里 78 //可能需要访问元素已有的功能,比如: 79 elementA.opertionA(); 80 } 81 82 @Override 83 public void visitConcreteElementB(ConcreteElementB elementB) { 84 //把要访问ConcreteElementB时,需要执行的功能实现在这里 85 //可能需要访问元素已有的功能,比如: 86 elementB.opertionB(); 87 } 88 } 89 90 public class ConcreteVisitor2 implements Visitor { 91 92 @Override 93 public void visitConcreteElementA(ConcreteElementA elementA) { 94 //把要访问ConcreteElementA时,需要执行的功能实现在这里 95 //可能需要访问元素已有的功能,比如: 96 elementA.opertionA(); 97 } 98 99 @Override 100 public void visitConcreteElementB(ConcreteElementB elementB) { 101 //把要访问ConcreteElementB时,需要执行的功能实现在这里 102 //可能需要访问元素已有的功能,比如: 103 elementB.opertionB(); 104 } 105 } 106 107 (5)ObjectStructure的实现 108 import java.util.ArrayList; 109 import java.util.Collection; 110 111 /** 112 * 对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素 113 */ 114 public class ObjectStructure { 115 /** 116 * 对象结构,可以是一个组合结构或是集合 117 */ 118 private Collection<Element> col = new ArrayList<Element>(); 119 120 /** 121 * 提供给客户端操作的高层接口 122 * @param visitor 客户端需要使用的访问者 123 */ 124 public void handleRequest(Visitor visitor){ 125 //循环对象结构中的元素,接受访问 126 for(Element ele : col){ 127 ele.accept(visitor); 128 } 129 } 130 131 /** 132 * 组件对象结构,向对象结构中添加元素 133 * @param ele 加入到对象结构的元素 134 */ 135 public void addElement(Element ele){ 136 this.col.add(ele); 137 } 138 } 139 140 (6)客户端 141 public class Client { 142 public static void main(String[] args) { 143 //创建ObjectStructure 144 ObjectStructure os = new ObjectStructure(); 145 146 //创建要加入对象结构的元素 147 Element eleA = new ConcreteElementA(); 148 Element eleB = new ConcreteElementB(); 149 150 //把元素加入对象结构 151 os.addElement(eleA); 152 os.addElement(eleB); 153 154 //创建访问者 155 Visitor visitor = new ConcreteVisitor1(); 156 //调用业务处理方法 157 os.handleRequest(visitor); 158 } 159 }
--------------------------------------------------------------------
2、体会访问者模式
2.1、扩展客户管理的功能
考虑这样一个应用:扩展客户管理的功能。既然是扩展功能,那么肯定是已经存在一定的功能了,先看看目前已经的功能,公司的可分为:企业客户、个人客户,目前的功能很简单,就是让客户提出服务申请。
目前的程序结构如下图:
目前的程序代码如下:
1 /** 2 * 各种客户的父类 3 */ 4 public abstract class Customer { 5 /** 6 * 客户编号 7 */ 8 private String customerId; 9 /** 10 * 客户名称 11 */ 12 private String name; 13 14 /** 15 * 客户提出服务请求的方法 16 */ 17 public abstract void serviceRequest(); 18 19 public String getCustomerId() { 20 return customerId; 21 } 22 23 public void setCustomerId(String customerId) { 24 this.customerId = customerId; 25 } 26 27 public String getName() { 28 return name; 29 } 30 31 public void setName(String name) { 32 this.name = name; 33 } 34 } 35 36 /** 37 * 企业客户 38 */ 39 public class EnterpriseCustomer extends Customer { 40 /** 41 * 联系人 42 */ 43 private String linkman; 44 /** 45 * 联系电话 46 */ 47 private String linkTelephone; 48 /** 49 * 企业注册地址 50 */ 51 private String registerAddress; 52 53 /** 54 * 企业客户提出服务请求的方法 55 */ 56 @Override 57 public void serviceRequest() { 58 System.out.println(this.getName()+" 企业提出服务请求"); 59 } 60 61 public String getLinkman() { 62 return linkman; 63 } 64 65 public void setLinkman(String linkman) { 66 this.linkman = linkman; 67 } 68 69 public String getLinkTelephone() { 70 return linkTelephone; 71 } 72 73 public void setLinkTelephone(String linkTelephone) { 74 this.linkTelephone = linkTelephone; 75 } 76 77 public String getRegisterAddress() { 78 return registerAddress; 79 } 80 81 public void setRegisterAddress(String registerAddress) { 82 this.registerAddress = registerAddress; 83 } 84 } 85 86 87 /** 88 * 个人客户 89 */ 90 public class PersonalCustomer extends Customer { 91 92 /** 93 * 联系电话 94 */ 95 private String telephone; 96 97 /** 98 * 年龄 99 */ 100 private int age; 101 102 /** 103 * 个人客户提出服务请求 104 */ 105 @Override 106 public void serviceRequest() { 107 System.out.println("个人客户:" +this.getName() + " 提出服务请求"); 108 } 109 110 public String getTelephone() { 111 return telephone; 112 } 113 114 public void setTelephone(String telephone) { 115 this.telephone = telephone; 116 } 117 118 public int getAge() { 119 return age; 120 } 121 122 public void setAge(int age) { 123 this.age = age; 124 } 125 }
从代码中可以看出,目前功能很少,但是现在随着业务的发展,需要加强对客户管理的功能,假设需要增加以下功能:
- 客户对公司产品的偏好分析。针对企业客户和个人客户有不同的分析策略,主要是根据以往购买的历史、潜在购买意向等进行分析,对于企业客户还要添加上客户所在行业的发展趋势、客户的发展预期等分析。
- 客户价值分析。针对企业客户和个人客户,有不同的分析方法和策略。主要是根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析。
除了上面2个功能外,还有许多潜在的功能待实现。
2.2、不用模式的解决方案
看了上面的新增功能,既然不同类型的客户操作是不同的,那么在不同类型的客户中分别实现这些功能就可以了。
程序结构图如下:
程序代码如下:
1 /** 2 * 各种客户的父类 3 */ 4 public abstract class Customer { 5 /** 6 * 客户编号 7 */ 8 private String customerId; 9 /** 10 * 客户名称 11 */ 12 private String name; 13 14 /** 15 * 客户提出服务请求的方法 16 */ 17 public abstract void serviceRequest(); 18 19 /** 20 * 客户对公司产品的偏好分析 21 */ 22 public abstract void predilectionAnalyze(); 23 24 /** 25 * 客户价值分析 26 */ 27 public abstract void worthAnalyze(); 28 29 public String getCustomerId() { 30 return customerId; 31 } 32 33 public void setCustomerId(String customerId) { 34 this.customerId = customerId; 35 } 36 37 public String getName() { 38 return name; 39 } 40 41 public void setName(String name) { 42 this.name = name; 43 } 44 } 45 46 /** 47 * 企业客户 48 */ 49 public class EnterpriseCustomer extends Customer { 50 /** 51 * 联系人 52 */ 53 private String linkman; 54 /** 55 * 联系电话 56 */ 57 private String linkTelephone; 58 /** 59 * 企业注册地址 60 */ 61 private String registerAddress; 62 63 /** 64 * 企业客户提出服务请求的方法 65 */ 66 @Override 67 public void serviceRequest() { 68 System.out.println(this.getName()+" 企业提出服务请求"); 69 } 70 71 /** 72 * 客户对公司产品的偏好分析 73 */ 74 @Override 75 public void predilectionAnalyze() { 76 System.out.println("对企业客户: " + this.getName() +" 进行公司产品偏好分析"); 77 } 78 79 /** 80 * 客户价值分析 81 */ 82 @Override 83 public void worthAnalyze() { 84 System.out.println("对企业客户: " + this.getName() +" 进行价值分析"); 85 } 86 87 public String getLinkman() { 88 return linkman; 89 } 90 91 public void setLinkman(String linkman) { 92 this.linkman = linkman; 93 } 94 95 public String getLinkTelephone() { 96 return linkTelephone; 97 } 98 99 public void setLinkTelephone(String linkTelephone) { 100 this.linkTelephone = linkTelephone; 101 } 102 103 public String getRegisterAddress() { 104 return registerAddress; 105 } 106 107 public void setRegisterAddress(String registerAddress) { 108 this.registerAddress = registerAddress; 109 } 110 } 111 112 /** 113 * 个人客户 114 */ 115 public class PersonalCustomer extends Customer { 116 117 /** 118 * 联系电话 119 */ 120 private String telephone; 121 122 /** 123 * 年龄 124 */ 125 private int age; 126 127 /** 128 * 个人客户提出服务请求 129 */ 130 @Override 131 public void serviceRequest() { 132 System.out.println("个人客户:" +this.getName() + " 提出服务请求"); 133 } 134 135 /** 136 * 客户对公司产品的偏好分析 137 */ 138 @Override 139 public void predilectionAnalyze() { 140 System.out.println("对个人客户: " + this.getName() +" 进行公司产品偏好分析"); 141 } 142 143 /** 144 * 客户价值分析 145 */ 146 @Override 147 public void worthAnalyze() { 148 System.out.println("对个人客户: " + this.getName() +" 进行价值分析"); 149 } 150 151 public String getTelephone() { 152 return telephone; 153 } 154 155 public void setTelephone(String telephone) { 156 this.telephone = telephone; 157 } 158 159 public int getAge() { 160 return age; 161 } 162 163 public void setAge(int age) { 164 this.age = age; 165 } 166 } 167 168 import java.util.ArrayList; 169 import java.util.Collection; 170 171 public class Client { 172 public static void main(String[] args) { 173 //准备一些测试数据 174 Collection<Customer> colCustomer = preparedTestData(); 175 176 for(Customer c : colCustomer){ 177 //进行偏好分析 178 c.predilectionAnalyze(); 179 //进行价值分析 180 c.worthAnalyze(); 181 } 182 } 183 184 public static Collection<Customer> preparedTestData(){ 185 Collection<Customer> colCustomer = new ArrayList<Customer>(); 186 187 Customer cm1 = new EnterpriseCustomer(); 188 cm1.setName("ABC集团"); 189 colCustomer.add(cm1); 190 191 Customer cm2 = new EnterpriseCustomer(); 192 cm2.setName("QWE公司"); 193 colCustomer.add(cm2); 194 195 Customer cm3 = new PersonalCustomer(); 196 cm3.setName("李四"); 197 colCustomer.add(cm3); 198 199 return colCustomer; 200 } 201 } 202 203 运行结果: 204 对企业客户: ABC集团 进行公司产品偏好分析 205 对企业客户: ABC集团 进行价值分析 206 对企业客户: QWE公司 进行公司产品偏好分析 207 对企业客户: QWE公司 进行价值分析 208 对个人客户: 李四 进行公司产品偏好分析 209 对个人客户: 李四 进行价值分析
2.3、有何问题
分析上面的实现代码,可以发现两个问题:
- 在企业客户和个人客户的类中,都分别实现了提出服务请求、进行产品偏好分析、进行客户价值分析等功能,也就是说,这些功能的实现代码是混杂在同一个类中的;而且相同的功能分散到了不同的类中去实现,会导致整个系统难以理解、难以维护。
- 如果要给客户扩展新功能,每次都要改动企业客户的类和个人客户的类。
2.4、使用访问者模式来解决问题
(1)解决问题的思路
对于客户这个对象结构,不想改变类,又要添加新的功能,很明显就需要一种动态的方式,在运行期间把功能动态地添加到对象结构中去,应用访问者模式来解决问题的思路大致如下:
- 定义一个接口来代表要新加入的功能。
- 在对象结构上添加一个方法,作为通用的功能方法,也就是可以代表被添加的功能,在这个方法中传入具体的实现新功能的对象。在对象结构的具体实现对象中实现这个方法,回调传入具体的实现新功能的对象,就相当于调用到新功能上了。
- 提供实现新功能的对象
- 提供一个能够循环访问整个对象结构的类,让这个类来提供符合客户端业务需求的方法,来满足客户端调用的需要
这样的话,只要提供实现新功能的对象给对象结构,就可以为这些对象添加新的功能,由于在对象结构中定义的方法是通用的功能方法,所以什么新功能都可以加入。
(ps: 可能有人会想起用装饰模式,装饰模式可以实现为一个对象透明地添加功能,但装饰模式基本上是在现有的基础之上进行功能增加,实际上是对现有功能的加强或者改造,并不是在现有功能不改动的情况下,为对象添加新的功能。)
(2)使用访问者模式来解决问题
首先按照访问者模式的结构,分离出两个类层次来: 一个是对应于元素的类层次,一个是对应于访问者的类层次。
对应于元素的类层次,就是客户的对象层次;对应于访问者的类层次,需要先定义一个访问者接口,然后把每种业务实现成为一个单独的访问者对象。
下图是结构示意图:
示例代码如下:
1 (1)先来看看Customer的代码,Customer相当于访问者模式中的Element 2 /** 3 * 各种客户的父类 4 */ 5 public abstract class Customer { 6 /** 7 * 客户编号 8 */ 9 private String customerId; 10 /** 11 * 客户名称 12 */ 13 private String name; 14 15 /** 16 * 接受访问者的访问 17 * @param visitor 访问者 18 */ 19 public abstract void accept(Visitor visitor); 20 21 public String getCustomerId() { 22 return customerId; 23 } 24 25 public void setCustomerId(String customerId) { 26 this.customerId = customerId; 27 } 28 29 public String getName() { 30 return name; 31 } 32 33 public void setName(String name) { 34 this.name = name; 35 } 36 } 37 (2)企业客户和个人客户 38 /** 39 * 企业客户 40 */ 41 public class EnterpriseCustomer extends Customer { 42 /** 43 * 联系人 44 */ 45 private String linkman; 46 /** 47 * 联系电话 48 */ 49 private String linkTelephone; 50 /** 51 * 企业注册地址 52 */ 53 private String registerAddress; 54 55 @Override 56 public void accept(Visitor visitor) { 57 //回调访问者对象对应的方法 58 visitor.visitEnterpriseCustomer(this); 59 } 60 61 public String getLinkman() { 62 return linkman; 63 } 64 65 public void setLinkman(String linkman) { 66 this.linkman = linkman; 67 } 68 69 public String getLinkTelephone() { 70 return linkTelephone; 71 } 72 73 public void setLinkTelephone(String linkTelephone) { 74 this.linkTelephone = linkTelephone; 75 } 76 77 public String getRegisterAddress() { 78 return registerAddress; 79 } 80 81 public void setRegisterAddress(String registerAddress) { 82 this.registerAddress = registerAddress; 83 } 84 } 85 86 /** 87 * 个人客户 88 */ 89 public class PersonalCustomer extends Customer { 90 /** 91 * 联系电话 92 */ 93 private String telephone; 94 95 /** 96 * 年龄 97 */ 98 private int age; 99 100 @Override 101 public void accept(Visitor visitor) { 102 //回调访问者对象对应的方法 103 visitor.visitPersonalCustomer(this); 104 } 105 106 public String getTelephone() { 107 return telephone; 108 } 109 110 public void setTelephone(String telephone) { 111 this.telephone = telephone; 112 } 113 114 public int getAge() { 115 return age; 116 } 117 118 public void setAge(int age) { 119 this.age = age; 120 } 121 } 122 (3)访问者接口定义 123 /** 124 * 访问者接口 125 */ 126 public interface Visitor { 127 128 /** 129 * 访问企业客户,相当于给企业客户添加访问者的功能 130 * @param ec 企业客户的对象 131 */ 132 public void visitEnterpriseCustomer(EnterpriseCustomer ec); 133 134 /** 135 * 访问个人客户,相当于给个人客户添加访问者的功能 136 * @param pc 137 */ 138 public void visitPersonalCustomer(PersonalCustomer pc); 139 } 140 (4)具体访问者的实现 141 /** 142 * 具体的访问者,实现客户提出服务请求的功能 143 */ 144 public class ServiceRequestVisitor implements Visitor { 145 146 @Override 147 public void visitEnterpriseCustomer(EnterpriseCustomer ec) { 148 //企业客户提出的具体服务请求 149 System.out.println(ec.getName() + "企业提出服务请求"); 150 } 151 152 @Override 153 public void visitPersonalCustomer(PersonalCustomer pc) { 154 //个人客户提出的具体服务请求 155 System.out.println("客户" + pc.getName() + " 提出服务请求"); 156 } 157 } 158 159 /** 160 * 具体的访问者,实现对客户的偏好分析 161 */ 162 public class PredilectionAnalyzeVisitor implements Visitor { 163 164 @Override 165 public void visitEnterpriseCustomer(EnterpriseCustomer ec) { 166 //根据以往购买的历史、潜在购买意向 167 //以及客户所在行业的发展趋势、客户的发展预期等的分析 168 System.out.println("现在对企业客户 : " + ec.getName() + "进行产品偏好分析"); 169 } 170 171 @Override 172 public void visitPersonalCustomer(PersonalCustomer pc) { 173 //根据以往购买的历史、潜在购买意向 174 //以及客户所在行业的发展趋势、客户的发展预期等的分析 175 System.out.println("现在对个人客户 : " + pc.getName() + "进行产品偏好分析"); 176 } 177 } 178 (5)ObjectStructure实现 179 import java.util.ArrayList; 180 import java.util.Collection; 181 182 /** 183 * 要操作的客户集合 184 */ 185 public class ObjectStructure { 186 /** 187 * 要操作的客户集合 188 */ 189 private Collection<Customer> col = new ArrayList<Customer>(); 190 191 /** 192 * 提供给客户端操作的高层接口,具体的功能由客户端传入的访问者决定 193 * @param visitor 客户端需要使用的访问者 194 */ 195 public void handleRequest(Visitor visitor){ 196 //循环对象结构中的元素,接受访问 197 for(Customer cm : col){ 198 cm.accept(visitor); 199 } 200 } 201 202 /** 203 * 组件对象结构,向对象结构中添加元素 204 * @param ele 加入到对象结构的元素 205 */ 206 public void addElement(Customer ele){ 207 this.col.add(ele); 208 } 209 } 210 211 (6)客户端 212 public class Client { 213 public static void main(String[] args) { 214 //创建ObjectStructure 215 ObjectStructure os = new ObjectStructure(); 216 217 //准备些测试数据,创建客户对象,并加入ObjectStructure 218 Customer cm1 = new EnterpriseCustomer(); 219 cm1.setName("ABC集团"); 220 os.addElement(cm1); 221 222 Customer cm2 = new EnterpriseCustomer(); 223 cm2.setName("TYU公司"); 224 os.addElement(cm2); 225 226 Customer cm3 = new PersonalCustomer(); 227 cm3.setName("李四"); 228 os.addElement(cm3); 229 230 //客户提出服务请求,传入服务请求的Visitor 231 ServiceRequestVisitor srVisitor = new ServiceRequestVisitor(); 232 os.handleRequest(srVisitor); 233 234 //对客户偏好进行分析 235 PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor(); 236 os.handleRequest(paVisitor); 237 } 238 } 239 240 运行结果: 241 ABC集团企业提出服务请求 242 TYU公司企业提出服务请求 243 客户李四 提出服务请求 244 现在对企业客户 : ABC集团进行产品偏好分析 245 现在对企业客户 : TYU公司进行产品偏好分析 246 现在对个人客户 : 李四进行产品偏好分析
上面示例代码使用访问者模式重新实现了功能,把各类相同的功能放在单独的访问者对象中,使得代码不再杂乱。上面的代码中还有一个功能"对客户进行价值分析"没有实现,接下来就看看如何把这个功能增加到已有的系统中。(在访问者模式中药给对象结构增加新的功能,只需要把新的功能实现称为访问者,然后在客户端调用的时候使用这个访问者对象来访问对象结构就可以了。)
代码如下:
1 /** 2 * 具体的访问者,实现对客户价值分析 3 */ 4 public class WorthAnalyzeVisitor implements Visitor { 5 6 @Override 7 public void visitEnterpriseCustomer(EnterpriseCustomer ec) { 8 //根据购买金额的大小、购买的产品和服务的多少、购买的频率等进行分析 9 System.out.println("现在对企业客户 : " + ec.getName() + "进行价值分析"); 10 } 11 12 @Override 13 public void visitPersonalCustomer(PersonalCustomer pc) { 14 //根据购买金额的大小、购买的产品和服务的多少、购买的频率等进行分析 15 System.out.println("现在对个人客户 : " + pc.getName() + "进行价值分析"); 16 } 17 }
3、理解访问者模式
3.1、认识访问者模式
(1)访问者的功能
访问者模式能给一系列对象透明地添加新功能,从而避免在维护期间对这一系列对象进行修改,而且还能变相实现复用访问者所具有的功能。
(2)调用通路
访问者之所以能实现"为一系列对象透明地添加新功能",注意是透明的,也就是这一系列对象是不知道被添加功能的。重要的就是依靠通用方法,访问者提供一个方法(如:visit),对象提供一个方法(如:accept),这两个方法并不代表任何具体的功能,只是构成一个调用的通路,在accept方法里面回调visit方法,从而回调到访问者的具体实现还是那个,而这个访问者的具体实现的方法就是要添加的新的功能。
(3)两次分发技术
在访问者模式中,当客户端调用ObjectStructure的时候,会遍历ObjectStructure中所有的元素,调用这些元素的accpet方法,让这些元素来接收访问,这是请求的第一次分发;在具体的元素对象中实现accept方法的时候,会回调访问者的visit方法,等于请求被第二次分发了,请求被分发给访问者来处理,真正实现功能的正式访问者的visit方法。
两次分发技术具体的调用过程如下图:
(4)空的访问方法
并不是所有的访问方法都需要实现,由于访问者模式默认的是访问对象结构中的所有元素,因此在实现某些功能的时候,如果不需要涉及到某些元素的访问方法,这些方法可以实现成为空的。比如:这个访问者只想要处理组合对象,那么访问叶子对象的方法就可以为空,虽然还是需要访问所有的元素对象。
3.2、操作组合对象结构
访问者模式一个很常见的应用,就是和组合模式结合使用,通过访问者模式来给由组合模式构建的对象结构增加功能。
组合模式目前存在的问题:对于使用组合构建的组合对象结构,对外有一个统一的外观,要想添加新的功能只要在组件的接口上定义新的功能就可以了,但这样一来的话,需要修改所有的子类,而且,每次添加一个新功能,都需要修改组件接口,然后修改所有的子类。
访问者模式和组合模式组合使用的思路:首先把组合对象结构中的功能方法分离出来,然后把这些功能方法分别实现成访问者对象,通过访问者模式添加到组合对象结构中去。
下面通过访问者模式和组合模式组合来实现:输出服装商品树的功能。
示例的整体结构:
示例代码如下:
1 (1)先来定义访问者接口 2 /** 3 * 访问组合对象结构的访问者接口 4 */ 5 public interface Visitor { 6 /** 7 * 访问组合对象,相当于给组合对象添加访问者的功能 8 * @param composite 组合对象 9 */ 10 public void visitComposite(Composite composite); 11 12 /** 13 * 访问叶子对象,相当于给叶子对象添加访问者的功能 14 * @param leaf 叶子对象 15 */ 16 public void visitLeaf(Leaf leaf); 17 } 18 (2)组合对象的定义 19 /** 20 * 抽象的组件对象,相当于访问者模式中的元素对象 21 */ 22 public abstract class Component { 23 24 /** 25 * 接受访问者的访问 26 * @param visitor 访问者对象 27 */ 28 public abstract void accept(Visitor visitor); 29 30 /** 31 * 向组合对象中加入组件对象 32 * @param child 被加入组合对象中的组件对象 33 */ 34 public void addChild(Component child){ 35 //默认实现,抛出例外 36 throw new UnsupportedOperationException("对象不支持这个功能"); 37 } 38 39 /** 40 * 从组合对象中移出某个组件对象 41 * @param child 被移出的组件对象 42 */ 43 public void removeChild(Component child){ 44 //默认实现,抛出例外 45 throw new UnsupportedOperationException("对象不支持这个功能"); 46 } 47 48 /** 49 * 返回某个索引对应的组件对象 50 * @param index 需要获取的组件对象的索引,索引从0开始 51 * @return 索引对应的组件对象 52 */ 53 public Component getChildren(int index){ 54 //默认实现,抛出例外 55 throw new UnsupportedOperationException("对象不支持这个功能"); 56 } 57 } 58 (3)实现组合对象和叶子对象 59 import java.util.ArrayList; 60 import java.util.List; 61 62 /** 63 * 组合对象,可以包含其他组合对象或者叶子对象 64 * 相当于访问者模式中具体的Element实现对象 65 */ 66 public class Composite extends Component { 67 68 /** 69 * 用来存储组合对象中包含的子组件对象 70 */ 71 private List<Component> childComponents = new ArrayList<Component>(); 72 73 /** 74 * 组合对象的名称 75 */ 76 private String name = ""; 77 78 /** 79 * 构造方法 80 * @param name 组合对象的名称 81 */ 82 public Composite(String name){ 83 this.name = name; 84 } 85 86 @Override 87 public void accept(Visitor visitor) { 88 //回调用访问者对象的相应方法 89 visitor.visitComposite(this); 90 91 //循环子元素,让子元素也接受访问 92 for(Component c : childComponents){ 93 //调用子对象接受访问,变相实现递归 94 c.accept(visitor); 95 } 96 } 97 98 @Override 99 public void addChild(Component child) { 100 childComponents.add(child); 101 } 102 103 public String getName() { 104 return name; 105 } 106 } 107 108 /** 109 * 叶子对象,相当于访问者模式的具体Element实现对象 110 */ 111 public class Leaf extends Component { 112 113 /** 114 * 叶子对象的名称 115 */ 116 private String name = ""; 117 118 /** 119 * 构造方法 120 * @param name 叶子对象的名称 121 */ 122 public Leaf(String name){ 123 this.name = name; 124 } 125 126 @Override 127 public void accept(Visitor visitor) { 128 //回调访问者对象的相应方法 129 visitor.visitLeaf(this); 130 } 131 132 public String getName() { 133 return name; 134 } 135 } 136 (4)访问者对象 137 /** 138 * 具体的访问者,实现:输出对象的名称 139 */ 140 public class PrintNameVisitor implements Visitor { 141 142 @Override 143 public void visitComposite(Composite composite) { 144 //访问到组合对象的数据 145 System.out.println("节点: " + composite.getName()); 146 } 147 148 @Override 149 public void visitLeaf(Leaf leaf) { 150 //访问到叶子对象的数据 151 System.out.println("叶子: " + leaf.getName()); 152 } 153 } 154 (5)访问所有元素对象的对象--ObjectStructure 155 /** 156 * 对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素 157 */ 158 public class ObjectStructure { 159 160 /** 161 * 表示对象结构,可以是一个组合结构 162 */ 163 private Component root = null; 164 165 /** 166 * 提供给客户端操作的高层接口 167 * @param visitor 客户端需要使用的访问者 168 */ 169 public void handleRequest(Visitor visitor){ 170 //让组合对象结构中的根元素 接受访问 171 //让组合对象结构中已经实现了元素的遍历 172 if(root != null){ 173 root.accept(visitor); 174 } 175 } 176 177 public void setRoot(Component ele){ 178 this.root = ele; 179 } 180 } 181 (6)客户端 182 public class Client { 183 public static void main(String[] args) { 184 //定义所有的组合对象 185 Component root = new Composite("服装"); 186 Component c1 = new Composite("男装"); 187 Component c2 = new Composite("女装"); 188 189 //定义所有的叶子对象 190 Component leaf1 = new Leaf("衬衣"); 191 Component leaf2 = new Leaf("夹克"); 192 Component leaf3 = new Leaf("裙子"); 193 Component leaf4 = new Leaf("套装"); 194 195 //按照树的结构来组合组合对象和叶子对象 196 root.addChild(c1); 197 root.addChild(c2); 198 199 c1.addChild(leaf1); 200 c1.addChild(leaf2); 201 c2.addChild(leaf3); 202 c2.addChild(leaf4); 203 204 //创建ObjectStructure 205 ObjectStructure os = new ObjectStructure(); 206 os.setRoot(root); 207 208 Visitor visitor = new PrintNameVisitor(); 209 os.handleRequest(visitor); 210 } 211 } 212 213 运行结果: 214 节点: 服装 215 节点: 男装 216 叶子: 衬衣 217 叶子: 夹克 218 节点: 女装 219 叶子: 裙子 220 叶子: 套装
3.3、谁负责遍历所有元素对象
在访问者模式中,访问者必须要能够访问到对象结构中的每个对象,因为访问者要为每个对象添加功能,为此特别在模式中定义一个ObjectStructure,然后由ObjectStructure负责遍历访问一系列对象中的每个对象。
(1)在ObjectStructure迭代所有的元素时,又分成以下两种情况
- 元素的对象结构是通过集合来组织的,因此直接在ObjectStructure中对集合进行迭代,然后对每一个元素调用accept就可以了。
- 元素的对象结构是通过组合模式来组织的,通常可以构成对象树,这种情况一般就不需要在ObjectStructure中迭代了。
(2)不需要ObjectStructure的时候
在实际开发中, 有一种典型的情况可以不需要ObjectStructure对象,那就是只有一个被访问对象的时候。
3.4、访问者模式的优缺点
优点:
- 好的扩展性,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
- 好的复用性,可以通过访问者来定义整个对象结构通用的功能
- 分离无关行为,可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一
缺点:
- 对象结构变化很困难,不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变。
- 破坏封装性,访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性
----------------------------------------------------------------------
4、思考访问者模式
4.1、访问者模式的本质
本质: 预留通路,回调实现。
回过头来看看访问者模式,它的实现主要是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发技术,利用预先定义好的通路,回调到访问者具体的实现上。
4.2、何时选用访问者模式
- 如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式。
- 如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作时。
- 如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作时。