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

  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)首先定义一个接口来代表要新加入的功能,把它称作访问者

 /**
* 访问者接口
*/
public interface Visitor {
/**
* 访问元素A,相当于给元素A添加访问者的功能
* @param elementA 元素A的对象
*/
public void visitConcreteElementA(ConcreteElementA elementA); /**
* 访问元素B,相当于给元素B添加访问者的功能
* @param elementA 元素B的对象
*/
public void visitConcreteElementB(ConcreteElementB elementB);
} (2)抽象的元素对象定义
/**
* 被访问的元素的接口
*/
public abstract class Element {
/**
* 接受访问者的访问
* @param visitor 访问者
*/
public abstract void accept(Visitor visitor);
} (3)具体的元素A和元素B
/**
* 具体元素的实现对象
*/
public class ConcreteElementA extends Element { @Override
public void accept(Visitor visitor) {
//回调访问者对象的相应方法
visitor.visitConcreteElementA(this);
} /**
* 已有的功能
*/
public void opertionA(){ }
} /**
* 具体元素的实现对象
*/
public class ConcreteElementB extends Element { @Override
public void accept(Visitor visitor) {
//回调访问者对象的相应方法
visitor.visitConcreteElementB(this);
} public void opertionB(){
//已有的功能实现
}
} (4)访问者的具体实现
/**
* 具体的访问者实现
*/
public class ConcreteVisitor1 implements Visitor { @Override
public void visitConcreteElementA(ConcreteElementA elementA) {
//把要访问ConcreteElementA时,需要执行的功能实现在这里
//可能需要访问元素已有的功能,比如:
elementA.opertionA();
} @Override
public void visitConcreteElementB(ConcreteElementB elementB) {
//把要访问ConcreteElementB时,需要执行的功能实现在这里
//可能需要访问元素已有的功能,比如:
elementB.opertionB();
}
} public class ConcreteVisitor2 implements Visitor { @Override
public void visitConcreteElementA(ConcreteElementA elementA) {
//把要访问ConcreteElementA时,需要执行的功能实现在这里
//可能需要访问元素已有的功能,比如:
elementA.opertionA();
} @Override
public void visitConcreteElementB(ConcreteElementB elementB) {
//把要访问ConcreteElementB时,需要执行的功能实现在这里
//可能需要访问元素已有的功能,比如:
elementB.opertionB();
}
} (5)ObjectStructure的实现
import java.util.ArrayList;
import java.util.Collection; /**
* 对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素
*/
public class ObjectStructure {
/**
* 对象结构,可以是一个组合结构或是集合
*/
private Collection<Element> col = new ArrayList<Element>(); /**
* 提供给客户端操作的高层接口
* @param visitor 客户端需要使用的访问者
*/
public void handleRequest(Visitor visitor){
//循环对象结构中的元素,接受访问
for(Element ele : col){
ele.accept(visitor);
}
} /**
* 组件对象结构,向对象结构中添加元素
* @param ele 加入到对象结构的元素
*/
public void addElement(Element ele){
this.col.add(ele);
}
} (6)客户端
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructure os = new ObjectStructure(); //创建要加入对象结构的元素
Element eleA = new ConcreteElementA();
Element eleB = new ConcreteElementB(); //把元素加入对象结构
os.addElement(eleA);
os.addElement(eleB); //创建访问者
Visitor visitor = new ConcreteVisitor1();
//调用业务处理方法
os.handleRequest(visitor);
}
}

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

2、体会访问者模式                                                                      

2.1、扩展客户管理的功能

  考虑这样一个应用:扩展客户管理的功能。既然是扩展功能,那么肯定是已经存在一定的功能了,先看看目前已经的功能,公司的可分为:企业客户、个人客户,目前的功能很简单,就是让客户提出服务申请。

目前的程序结构如下图:

目前的程序代码如下:

 /**
* 各种客户的父类
*/
public abstract class Customer {
/**
* 客户编号
*/
private String customerId;
/**
* 客户名称
*/
private String name; /**
* 客户提出服务请求的方法
*/
public abstract void serviceRequest(); public String getCustomerId() {
return customerId;
} public void setCustomerId(String customerId) {
this.customerId = customerId;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
} /**
* 企业客户
*/
public class EnterpriseCustomer extends Customer {
/**
* 联系人
*/
private String linkman;
/**
* 联系电话
*/
private String linkTelephone;
/**
* 企业注册地址
*/
private String registerAddress; /**
* 企业客户提出服务请求的方法
*/
@Override
public void serviceRequest() {
System.out.println(this.getName()+" 企业提出服务请求");
} public String getLinkman() {
return linkman;
} public void setLinkman(String linkman) {
this.linkman = linkman;
} public String getLinkTelephone() {
return linkTelephone;
} public void setLinkTelephone(String linkTelephone) {
this.linkTelephone = linkTelephone;
} public String getRegisterAddress() {
return registerAddress;
} public void setRegisterAddress(String registerAddress) {
this.registerAddress = registerAddress;
}
} /**
* 个人客户
*/
public class PersonalCustomer extends Customer { /**
* 联系电话
*/
private String telephone; /**
* 年龄
*/
private int age; /**
* 个人客户提出服务请求
*/
@Override
public void serviceRequest() {
System.out.println("个人客户:" +this.getName() + " 提出服务请求");
} public String getTelephone() {
return telephone;
} public void setTelephone(String telephone) {
this.telephone = telephone;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
}

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

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

除了上面2个功能外,还有许多潜在的功能待实现。

2.2、不用模式的解决方案

  看了上面的新增功能,既然不同类型的客户操作是不同的,那么在不同类型的客户中分别实现这些功能就可以了。

程序结构图如下:

程序代码如下:

 /**
* 各种客户的父类
*/
public abstract class Customer {
/**
* 客户编号
*/
private String customerId;
/**
* 客户名称
*/
private String name; /**
* 客户提出服务请求的方法
*/
public abstract void serviceRequest(); /**
* 客户对公司产品的偏好分析
*/
public abstract void predilectionAnalyze(); /**
* 客户价值分析
*/
public abstract void worthAnalyze(); public String getCustomerId() {
return customerId;
} public void setCustomerId(String customerId) {
this.customerId = customerId;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
} /**
* 企业客户
*/
public class EnterpriseCustomer extends Customer {
/**
* 联系人
*/
private String linkman;
/**
* 联系电话
*/
private String linkTelephone;
/**
* 企业注册地址
*/
private String registerAddress; /**
* 企业客户提出服务请求的方法
*/
@Override
public void serviceRequest() {
System.out.println(this.getName()+" 企业提出服务请求");
} /**
* 客户对公司产品的偏好分析
*/
@Override
public void predilectionAnalyze() {
System.out.println("对企业客户: " + this.getName() +" 进行公司产品偏好分析");
} /**
* 客户价值分析
*/
@Override
public void worthAnalyze() {
System.out.println("对企业客户: " + this.getName() +" 进行价值分析");
} public String getLinkman() {
return linkman;
} public void setLinkman(String linkman) {
this.linkman = linkman;
} public String getLinkTelephone() {
return linkTelephone;
} public void setLinkTelephone(String linkTelephone) {
this.linkTelephone = linkTelephone;
} public String getRegisterAddress() {
return registerAddress;
} public void setRegisterAddress(String registerAddress) {
this.registerAddress = registerAddress;
}
} /**
* 个人客户
*/
public class PersonalCustomer extends Customer { /**
* 联系电话
*/
private String telephone; /**
* 年龄
*/
private int age; /**
* 个人客户提出服务请求
*/
@Override
public void serviceRequest() {
System.out.println("个人客户:" +this.getName() + " 提出服务请求");
} /**
* 客户对公司产品的偏好分析
*/
@Override
public void predilectionAnalyze() {
System.out.println("对个人客户: " + this.getName() +" 进行公司产品偏好分析");
} /**
* 客户价值分析
*/
@Override
public void worthAnalyze() {
System.out.println("对个人客户: " + this.getName() +" 进行价值分析");
} public String getTelephone() {
return telephone;
} public void setTelephone(String telephone) {
this.telephone = telephone;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
} import java.util.ArrayList;
import java.util.Collection; public class Client {
public static void main(String[] args) {
//准备一些测试数据
Collection<Customer> colCustomer = preparedTestData(); for(Customer c : colCustomer){
//进行偏好分析
c.predilectionAnalyze();
//进行价值分析
c.worthAnalyze();
}
} public static Collection<Customer> preparedTestData(){
Collection<Customer> colCustomer = new ArrayList<Customer>(); Customer cm1 = new EnterpriseCustomer();
cm1.setName("ABC集团");
colCustomer.add(cm1); Customer cm2 = new EnterpriseCustomer();
cm2.setName("QWE公司");
colCustomer.add(cm2); Customer cm3 = new PersonalCustomer();
cm3.setName("李四");
colCustomer.add(cm3); return colCustomer;
}
} 运行结果:
对企业客户: ABC集团 进行公司产品偏好分析
对企业客户: ABC集团 进行价值分析
对企业客户: QWE公司 进行公司产品偏好分析
对企业客户: QWE公司 进行价值分析
对个人客户: 李四 进行公司产品偏好分析
对个人客户: 李四 进行价值分析

2.3、有何问题

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

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

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

(1)解决问题的思路

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

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

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

(ps: 可能有人会想起用装饰模式,装饰模式可以实现为一个对象透明地添加功能,但装饰模式基本上是在现有的基础之上进行功能增加,实际上是对现有功能的加强或者改造,并不是在现有功能不改动的情况下,为对象添加新的功能。)

(2)使用访问者模式来解决问题

首先按照访问者模式的结构,分离出两个类层次来: 一个是对应于元素的类层次,一个是对应于访问者的类层次。

对应于元素的类层次,就是客户的对象层次;对应于访问者的类层次,需要先定义一个访问者接口,然后把每种业务实现成为一个单独的访问者对象。

下图是结构示意图:

示例代码如下:

 (1)先来看看Customer的代码,Customer相当于访问者模式中的Element
/**
* 各种客户的父类
*/
public abstract class Customer {
/**
* 客户编号
*/
private String customerId;
/**
* 客户名称
*/
private String name; /**
* 接受访问者的访问
* @param visitor 访问者
*/
public abstract void accept(Visitor visitor); public String getCustomerId() {
return customerId;
} public void setCustomerId(String customerId) {
this.customerId = customerId;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
}
(2)企业客户和个人客户
/**
* 企业客户
*/
public class EnterpriseCustomer extends Customer {
/**
* 联系人
*/
private String linkman;
/**
* 联系电话
*/
private String linkTelephone;
/**
* 企业注册地址
*/
private String registerAddress; @Override
public void accept(Visitor visitor) {
//回调访问者对象对应的方法
visitor.visitEnterpriseCustomer(this);
} public String getLinkman() {
return linkman;
} public void setLinkman(String linkman) {
this.linkman = linkman;
} public String getLinkTelephone() {
return linkTelephone;
} public void setLinkTelephone(String linkTelephone) {
this.linkTelephone = linkTelephone;
} public String getRegisterAddress() {
return registerAddress;
} public void setRegisterAddress(String registerAddress) {
this.registerAddress = registerAddress;
}
} /**
* 个人客户
*/
public class PersonalCustomer extends Customer {
/**
* 联系电话
*/
private String telephone; /**
* 年龄
*/
private int age; @Override
public void accept(Visitor visitor) {
//回调访问者对象对应的方法
visitor.visitPersonalCustomer(this);
} public String getTelephone() {
return telephone;
} public void setTelephone(String telephone) {
this.telephone = telephone;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
}
}
(3)访问者接口定义
/**
* 访问者接口
*/
public interface Visitor { /**
* 访问企业客户,相当于给企业客户添加访问者的功能
* @param ec 企业客户的对象
*/
public void visitEnterpriseCustomer(EnterpriseCustomer ec); /**
* 访问个人客户,相当于给个人客户添加访问者的功能
* @param pc
*/
public void visitPersonalCustomer(PersonalCustomer pc);
}
(4)具体访问者的实现
/**
* 具体的访问者,实现客户提出服务请求的功能
*/
public class ServiceRequestVisitor implements Visitor { @Override
public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
//企业客户提出的具体服务请求
System.out.println(ec.getName() + "企业提出服务请求");
} @Override
public void visitPersonalCustomer(PersonalCustomer pc) {
//个人客户提出的具体服务请求
System.out.println("客户" + pc.getName() + " 提出服务请求");
}
} /**
* 具体的访问者,实现对客户的偏好分析
*/
public class PredilectionAnalyzeVisitor implements Visitor { @Override
public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
//根据以往购买的历史、潜在购买意向
//以及客户所在行业的发展趋势、客户的发展预期等的分析
System.out.println("现在对企业客户 : " + ec.getName() + "进行产品偏好分析");
} @Override
public void visitPersonalCustomer(PersonalCustomer pc) {
//根据以往购买的历史、潜在购买意向
//以及客户所在行业的发展趋势、客户的发展预期等的分析
System.out.println("现在对个人客户 : " + pc.getName() + "进行产品偏好分析");
}
}
(5)ObjectStructure实现
import java.util.ArrayList;
import java.util.Collection; /**
* 要操作的客户集合
*/
public class ObjectStructure {
/**
* 要操作的客户集合
*/
private Collection<Customer> col = new ArrayList<Customer>(); /**
* 提供给客户端操作的高层接口,具体的功能由客户端传入的访问者决定
* @param visitor 客户端需要使用的访问者
*/
public void handleRequest(Visitor visitor){
//循环对象结构中的元素,接受访问
for(Customer cm : col){
cm.accept(visitor);
}
} /**
* 组件对象结构,向对象结构中添加元素
* @param ele 加入到对象结构的元素
*/
public void addElement(Customer ele){
this.col.add(ele);
}
} (6)客户端
public class Client {
public static void main(String[] args) {
//创建ObjectStructure
ObjectStructure os = new ObjectStructure(); //准备些测试数据,创建客户对象,并加入ObjectStructure
Customer cm1 = new EnterpriseCustomer();
cm1.setName("ABC集团");
os.addElement(cm1); Customer cm2 = new EnterpriseCustomer();
cm2.setName("TYU公司");
os.addElement(cm2); Customer cm3 = new PersonalCustomer();
cm3.setName("李四");
os.addElement(cm3); //客户提出服务请求,传入服务请求的Visitor
ServiceRequestVisitor srVisitor = new ServiceRequestVisitor();
os.handleRequest(srVisitor); //对客户偏好进行分析
PredilectionAnalyzeVisitor paVisitor = new PredilectionAnalyzeVisitor();
os.handleRequest(paVisitor);
}
} 运行结果:
ABC集团企业提出服务请求
TYU公司企业提出服务请求
客户李四 提出服务请求
现在对企业客户 : ABC集团进行产品偏好分析
现在对企业客户 : TYU公司进行产品偏好分析
现在对个人客户 : 李四进行产品偏好分析

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

代码如下:

 /**
* 具体的访问者,实现对客户价值分析
*/
public class WorthAnalyzeVisitor implements Visitor { @Override
public void visitEnterpriseCustomer(EnterpriseCustomer ec) {
//根据购买金额的大小、购买的产品和服务的多少、购买的频率等进行分析
System.out.println("现在对企业客户 : " + ec.getName() + "进行价值分析");
} @Override
public void visitPersonalCustomer(PersonalCustomer pc) {
//根据购买金额的大小、购买的产品和服务的多少、购买的频率等进行分析
System.out.println("现在对个人客户 : " + pc.getName() + "进行价值分析");
}
}

3、理解访问者模式                                                                          

3.1、认识访问者模式

(1)访问者的功能

  访问者模式能给一系列对象透明地添加新功能,从而避免在维护期间对这一系列对象进行修改,而且还能变相实现复用访问者所具有的功能。

(2)调用通路

  访问者之所以能实现"为一系列对象透明地添加新功能",注意是透明的,也就是这一系列对象是不知道被添加功能的。重要的就是依靠通用方法,访问者提供一个方法(如:visit),对象提供一个方法(如:accept),这两个方法并不代表任何具体的功能,只是构成一个调用的通路,在accept方法里面回调visit方法,从而回调到访问者的具体实现还是那个,而这个访问者的具体实现的方法就是要添加的新的功能。

(3)两次分发技术

  在访问者模式中,当客户端调用ObjectStructure的时候,会遍历ObjectStructure中所有的元素,调用这些元素的accpet方法,让这些元素来接收访问,这是请求的第一次分发;在具体的元素对象中实现accept方法的时候,会回调访问者的visit方法,等于请求被第二次分发了,请求被分发给访问者来处理,真正实现功能的正式访问者的visit方法。

两次分发技术具体的调用过程如下图:

(4)空的访问方法

  并不是所有的访问方法都需要实现,由于访问者模式默认的是访问对象结构中的所有元素,因此在实现某些功能的时候,如果不需要涉及到某些元素的访问方法,这些方法可以实现成为空的。比如:这个访问者只想要处理组合对象,那么访问叶子对象的方法就可以为空,虽然还是需要访问所有的元素对象。

3.2、操作组合对象结构

  访问者模式一个很常见的应用,就是和组合模式结合使用,通过访问者模式来给由组合模式构建的对象结构增加功能。

  组合模式目前存在的问题:对于使用组合构建的组合对象结构,对外有一个统一的外观,要想添加新的功能只要在组件的接口上定义新的功能就可以了,但这样一来的话,需要修改所有的子类,而且,每次添加一个新功能,都需要修改组件接口,然后修改所有的子类。

  访问者模式和组合模式组合使用的思路:首先把组合对象结构中的功能方法分离出来,然后把这些功能方法分别实现成访问者对象,通过访问者模式添加到组合对象结构中去。

下面通过访问者模式和组合模式组合来实现:输出服装商品树的功能。

示例的整体结构:

示例代码如下:

 (1)先来定义访问者接口
/**
* 访问组合对象结构的访问者接口
*/
public interface Visitor {
/**
* 访问组合对象,相当于给组合对象添加访问者的功能
* @param composite 组合对象
*/
public void visitComposite(Composite composite); /**
* 访问叶子对象,相当于给叶子对象添加访问者的功能
* @param leaf 叶子对象
*/
public void visitLeaf(Leaf leaf);
}
(2)组合对象的定义
/**
* 抽象的组件对象,相当于访问者模式中的元素对象
*/
public abstract class Component { /**
* 接受访问者的访问
* @param visitor 访问者对象
*/
public abstract void accept(Visitor visitor); /**
* 向组合对象中加入组件对象
* @param child 被加入组合对象中的组件对象
*/
public void addChild(Component child){
//默认实现,抛出例外
throw new UnsupportedOperationException("对象不支持这个功能");
} /**
* 从组合对象中移出某个组件对象
* @param child 被移出的组件对象
*/
public void removeChild(Component child){
//默认实现,抛出例外
throw new UnsupportedOperationException("对象不支持这个功能");
} /**
* 返回某个索引对应的组件对象
* @param index 需要获取的组件对象的索引,索引从0开始
* @return 索引对应的组件对象
*/
public Component getChildren(int index){
//默认实现,抛出例外
throw new UnsupportedOperationException("对象不支持这个功能");
}
}
(3)实现组合对象和叶子对象
import java.util.ArrayList;
import java.util.List; /**
* 组合对象,可以包含其他组合对象或者叶子对象
* 相当于访问者模式中具体的Element实现对象
*/
public class Composite extends Component { /**
* 用来存储组合对象中包含的子组件对象
*/
private List<Component> childComponents = new ArrayList<Component>(); /**
* 组合对象的名称
*/
private String name = ""; /**
* 构造方法
* @param name 组合对象的名称
*/
public Composite(String name){
this.name = name;
} @Override
public void accept(Visitor visitor) {
//回调用访问者对象的相应方法
visitor.visitComposite(this); //循环子元素,让子元素也接受访问
for(Component c : childComponents){
//调用子对象接受访问,变相实现递归
c.accept(visitor);
}
} @Override
public void addChild(Component child) {
childComponents.add(child);
} public String getName() {
return name;
}
} /**
* 叶子对象,相当于访问者模式的具体Element实现对象
*/
public class Leaf extends Component { /**
* 叶子对象的名称
*/
private String name = ""; /**
* 构造方法
* @param name 叶子对象的名称
*/
public Leaf(String name){
this.name = name;
} @Override
public void accept(Visitor visitor) {
//回调访问者对象的相应方法
visitor.visitLeaf(this);
} public String getName() {
return name;
}
}
(4)访问者对象
/**
* 具体的访问者,实现:输出对象的名称
*/
public class PrintNameVisitor implements Visitor { @Override
public void visitComposite(Composite composite) {
//访问到组合对象的数据
System.out.println("节点: " + composite.getName());
} @Override
public void visitLeaf(Leaf leaf) {
//访问到叶子对象的数据
System.out.println("叶子: " + leaf.getName());
}
}
(5)访问所有元素对象的对象--ObjectStructure
/**
* 对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素
*/
public class ObjectStructure { /**
* 表示对象结构,可以是一个组合结构
*/
private Component root = null; /**
* 提供给客户端操作的高层接口
* @param visitor 客户端需要使用的访问者
*/
public void handleRequest(Visitor visitor){
//让组合对象结构中的根元素 接受访问
//让组合对象结构中已经实现了元素的遍历
if(root != null){
root.accept(visitor);
}
} public void setRoot(Component ele){
this.root = ele;
}
}
(6)客户端
public class Client {
public static void main(String[] args) {
//定义所有的组合对象
Component root = new Composite("服装");
Component c1 = new Composite("男装");
Component c2 = new Composite("女装"); //定义所有的叶子对象
Component leaf1 = new Leaf("衬衣");
Component leaf2 = new Leaf("夹克");
Component leaf3 = new Leaf("裙子");
Component leaf4 = new Leaf("套装"); //按照树的结构来组合组合对象和叶子对象
root.addChild(c1);
root.addChild(c2); c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4); //创建ObjectStructure
ObjectStructure os = new ObjectStructure();
os.setRoot(root); Visitor visitor = new PrintNameVisitor();
os.handleRequest(visitor);
}
} 运行结果:
节点: 服装
节点: 男装
叶子: 衬衣
叶子: 夹克
节点: 女装
叶子: 裙子
叶子: 套装

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. 如果对象结构很少变动,但是需要经常给对象结构中的元素对象定义新的操作时。

 

设计模式 -- 访问者模式(Visitor)的更多相关文章

  1. C#设计模式——访问者模式(Visitor Pattern)

    一.概述由于需求的改变,某些类常常需要增加新的功能,但由于种种原因这些类层次必须保持稳定,不允许开发人员随意修改.对此,访问者模式可以在不更改类层次结构的前提下透明的为各个类动态添加新的功能.二.访问 ...

  2. 大话设计模式--访问者模式 Visitor -- C++实现实例

    1. 访问者模式: 表示一个作用于某对象结构中的和元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 访问者模式把数据结构和作用于结构上的操作之间的耦合脱开,使得操作集合可以 ...

  3. 深入浅出设计模式——访问者模式(Visitor Pattern)

    模式动机 对于系统中的某些对象,它们存储在同一个集合中,且具有不同的类型,而且对于该集合中的对象,可以接受一类称为访问者的对象来访问,而且不同的访问者其访问方式有所不同,访问者模式为解决这类问题而诞生 ...

  4. 设计模式 ( 二十 ) 访问者模式Visitor(对象行为型)

    设计模式 ( 二十 ) 访问者模式Visitor(对象行为型) 1.概述 在软件开发过程中,对于系统中的某些对象,它们存储在同一个集合collection中,且具有不同的类型,而且对于该集合中的对象, ...

  5. 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern)

    原文:乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 访问者模式(Visitor Pattern) 作者:webabc ...

  6. 访问者模式 Visitor 行为型 设计模式(二十七)

    访问者模式 Visitor    <侠客行>是当代作家金庸创作的长篇武侠小说,新版电视剧<侠客行>中,开篇有一段独白:  “茫茫海外,传说有座侠客岛,岛上赏善罚恶二使,每隔十年 ...

  7. 二十四种设计模式:访问者模式(Visitor Pattern)

    访问者模式(Visitor Pattern) 介绍表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 示例有一个Message实体类,某些对象对 ...

  8. .NET设计模式访问者模式

    一.访问者模式的定义: 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作. 二.访问者模式的结构和角色: 1.Visitor 抽象访问者角色,为该 ...

  9. JAVA 设计模式 访问者模式

    用途 访问者模式 (Visitor) 表示一个作用于某对象结构中的各元素的操作. 它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 访问者模式是一种行为型模式. 用途

随机推荐

  1. Android SDK的安装与环境变量的配置

    配置Andriod环境变量前提是要先安装好JAVA环境 1.下载Android SDK,点击安装,放在任意不含空格.特殊符号和中文的路径即可. 2.默认路径安装后,安装完成,开始配置环境变量. 3.打 ...

  2. isolation forest进行异常点检测

    一.简介 孤立森林(Isolation Forest)是另外一种高效的异常检测算法,它和随机森林类似,但每次选择划分属性和划分点(值)时都是随机的,而不是根据信息增益或者基尼指数来选择.在建树过程中, ...

  3. Machine Learning系列--L0、L1、L2范数

    今天我们聊聊机器学习中出现的非常频繁的问题:过拟合与规则化.我们先简单的来理解下常用的L0.L1.L2和核范数规则化.最后聊下规则化项参数的选择问题.这里因为篇幅比较庞大,为了不吓到大家,我将这个五个 ...

  4. Nginx服务安全设置和参数调优

    1.添加参数隐藏Nginx版本号 vim /application/nginx/conf/nginx.conf #http标签下添加 server_tokens off; #测试 [root@cobb ...

  5. Java跨域问题的处理

    1,JavaScript由于安全性方面的考虑,不允许页面跨域调用其他页面的对象,那么问题来了,什么是跨域问题? 答:这是由于浏览器同源策略的限制,现在所有支持JavaScript的浏览器都使用了这个策 ...

  6. Caffe学习系列(8):solver,train_val.prototxt,deploy.prototxt及其配置

    solver是caffe的核心. net: "examples/mnist/lenet_train_test.prototxt" test_iter: 100 test_inter ...

  7. java并发编程实战笔记---(第五章)基础构建模块

    . 5.1同步容器类 1.同步容器类的问题 复合操作,加容器内置锁 2.迭代器与concurrentModificationException 迭代容器用iterator, 迭代过程中,如果有其他线程 ...

  8. HDU 3085 Nightmare Ⅱ(双向BFS)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3085 题目大意:给你一张n*m地图上,上面有有 ‘. ’:路 ‘X':墙 ’Z':鬼,每秒移动2步,可 ...

  9. 配置OpenCV+VS2013环境

    配置OpenCV+VS2013环境 准备工作 win7系统 下载opencv的windows编译版 安装vs2013 express 设定环境变量 按windows窗键输入path,选择第二个结果编辑 ...

  10. Mybatis基础及入门案例

    这几天正在对SSM框架的知识进行一个回顾加深,有很多东西学的囫囵吞枣,所以利用一些时间进一步的学习.首先大概了解一下mybatis的使用,再通过一个案例来学习它. 什么是MyBatis Mybatis ...