设计模式 -- 访问者模式(Visitor)

写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:

  1. 初识访问者模式,包括: 定义、结构、参考实现
  2. 体会访问者模式,包括: 场景问题、不用模式的解决方案、使用模式的解决方案
  3. 理解访问者模式,包括: 认识访问者模式、操作组合对象结构、谁负责遍历所有元素对象、访问者模式的优缺点
  4. 思考访问者模式,包括: 访问者模式的本质、何时选用

参考内容:

1、《研磨设计模式》 一书,作者:陈臣、王斌

-------------------------------------------------------------------- 

1、初识访问者模式                                                                

1.1、定义

  表示一个作用于某对象结构中的各元素操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

如何理解:

  (1)首先,要明确访问者就是操作,换句话说,访问者就是功能。

  (2)其次,某对象结构中的各元素。比如,树形结构中的各个元素

  (3)最后,使用访问模式的目的是给某对象结构中的各元素添加功能,但是又不需要改变各元素的类的定义。

1.2、结构和说明

  ps:

  圆圈1里面就是定义的访问者,理论上可以有N个访问者(也就是具体的功能);

  圆圈2里面就是定义的某对象结构,对象结构中的元素理论上也可以有N个

  ObjectStructure是用来方便客户端使用时遍历某对象结构中全部的元素。

访问者模式结构说明:

  1. Visitor: 访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。
  2. ConcreteVisitor: 具体的访问者实现对象,实现要真正被添加到对象结构中的功能。
  3. Element: 抽象的元素对象,对象结构的顶层接口,定义接受访问的操作。
  4. ConcreteElement: 具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
  5. 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 }
View Code

--------------------------------------------------------------------

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 }
View Code

从代码中可以看出,目前功能很少,但是现在随着业务的发展,需要加强对客户管理的功能,假设需要增加以下功能:

  1. 客户对公司产品的偏好分析。针对企业客户和个人客户有不同的分析策略,主要是根据以往购买的历史、潜在购买意向等进行分析,对于企业客户还要添加上客户所在行业的发展趋势、客户的发展预期等分析。
  2. 客户价值分析。针对企业客户和个人客户,有不同的分析方法和策略。主要是根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析。

除了上面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 对个人客户: 李四 进行价值分析
View Code

2.3、有何问题

分析上面的实现代码,可以发现两个问题:

  1. 在企业客户和个人客户的类中,都分别实现了提出服务请求、进行产品偏好分析、进行客户价值分析等功能,也就是说,这些功能的实现代码是混杂在同一个类中的;而且相同的功能分散到了不同的类中去实现,会导致整个系统难以理解、难以维护。
  2. 如果要给客户扩展新功能,每次都要改动企业客户的类和个人客户的类。

2.4、使用访问者模式来解决问题

(1)解决问题的思路

  对于客户这个对象结构,不想改变类,又要添加新的功能,很明显就需要一种动态的方式,在运行期间把功能动态地添加到对象结构中去,应用访问者模式来解决问题的思路大致如下:

  1. 定义一个接口来代表要新加入的功能。
  2. 在对象结构上添加一个方法,作为通用的功能方法,也就是可以代表被添加的功能,在这个方法中传入具体的实现新功能的对象。在对象结构的具体实现对象中实现这个方法,回调传入具体的实现新功能的对象,就相当于调用到新功能上了。
  3. 提供实现新功能的对象
  4. 提供一个能够循环访问整个对象结构的类,让这个类来提供符合客户端业务需求的方法,来满足客户端调用的需要

这样的话,只要提供实现新功能的对象给对象结构,就可以为这些对象添加新的功能,由于在对象结构中定义的方法是通用的功能方法,所以什么新功能都可以加入。

(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 现在对个人客户 : 李四进行产品偏好分析
View Code

上面示例代码使用访问者模式重新实现了功能,把各类相同的功能放在单独的访问者对象中,使得代码不再杂乱。上面的代码中还有一个功能"对客户进行价值分析"没有实现,接下来就看看如何把这个功能增加到已有的系统中。(在访问者模式中药给对象结构增加新的功能,只需要把新的功能实现称为访问者,然后在客户端调用的时候使用这个访问者对象来访问对象结构就可以了。)

代码如下:

 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 }
View Code

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 叶子: 套装
View Code

3.3、谁负责遍历所有元素对象

  在访问者模式中,访问者必须要能够访问到对象结构中的每个对象,因为访问者要为每个对象添加功能,为此特别在模式中定义一个ObjectStructure,然后由ObjectStructure负责遍历访问一系列对象中的每个对象。

(1)在ObjectStructure迭代所有的元素时,又分成以下两种情况

  • 元素的对象结构是通过集合来组织的,因此直接在ObjectStructure中对集合进行迭代,然后对每一个元素调用accept就可以了。
  • 元素的对象结构是通过组合模式来组织的,通常可以构成对象树,这种情况一般就不需要在ObjectStructure中迭代了。

(2)不需要ObjectStructure的时候

  在实际开发中, 有一种典型的情况可以不需要ObjectStructure对象,那就是只有一个被访问对象的时候。

3.4、访问者模式的优缺点

优点:

  1. 好的扩展性,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
  2. 好的复用性,可以通过访问者来定义整个对象结构通用的功能
  3. 分离无关行为,可以通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一

缺点:

  1. 对象结构变化很困难,不适用于对象结构中的类经常变化的情况,因为对象结构发生了改变,访问者的接口和访问者的实现都要发生相应的改变。
  2. 破坏封装性,访问者模式通常需要对象结构开放内部数据给访问者和ObjectStructure,这破坏了对象的封装性

----------------------------------------------------------------------

4、思考访问者模式                                                                             

4.1、访问者模式的本质

  本质: 预留通路,回调实现。

  回过头来看看访问者模式,它的实现主要是通过预先定义好调用的通路,在被访问的对象上定义accept方法,在访问者的对象上定义visit方法;然后在调用真正发生的时候,通过两次分发技术,利用预先定义好的通路,回调到访问者具体的实现上。

4.2、何时选用访问者模式

  1. 如果想对一个对象结构实施一些依赖于对象结构中具体类的操作,可以使用访问者模式。
  2. 如果想对一个对象结构中的各个元素进行很多不同的而且不相关的操作时。
  3. 如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作时。

 

原文地址:https://www.cnblogs.com/xinhuaxuan/p/6377314.html