访问者模式-Visitor
访问者模式提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。
在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,访问者通过遍历对象结构实现对其中存储的元素的逐个操作。
一、类图
访问者模式由如下角色组成:
- 访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
- 具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。
- 元素角色(Element):定义一个Accept操作,它以一个访问者为参数。
- 具体元素角色(Concrete Element):实现由元素角色提供的Accept操作。
- 对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
二、示例
考虑这样一个应用:扩展客户管理的功能。既然是扩展功能,那么肯定是已经存在一定的功能了,先看看目前已经的功能,公司的可分为:企业客户、个人客户,目前的功能很简单,就是让客户提出服务申请。
/** * 各种客户的父类 */ 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; } }
从代码中可以看出,目前功能很少,但是现在随着业务的发展,需要加强对客户管理的功能,假设需要增加以下功能:
①客户对公司产品的偏好分析。针对企业客户和个人客户有不同的分析策略,主要是根据以往购买的历史、潜在购买意向等进行分析,对于企业客户还要添加上客户所在行业的发展趋势、客户的发展预期等分析。
②客户价值分析。针对企业客户和个人客户,有不同的分析方法和策略。主要是根据购买的金额大小、购买的产品和服务的多少、购买的频率等进行分析。
三、优缺点和使用场景
先来看下访问者模式的使用能否避免引言中的痛苦。使用了访问者模式以后,对于原来的类层次增加新的操作,仅仅需要实现一个具体访问者角色就可以了,而不必修改整个类层次。而且这样符合“开闭原则”的要求。而且每个具体的访问者角色都对应于一个相关操作,因此如果一个操作的需求有变,那么仅仅修改一个具体访问者角色,而不用改动整个类层次。
看来访问者模式确实能够解决我们面临的一些问题。而且由于访问者模式为我们的系统多提供了一层“访问者”,因此我们可以在访问者中添加一些对元素角色的额外操作。
但是“开闭原则”的遵循总是片面的。如果系统中的类层次发生了变化,会对访问者模式产生什么样的影响呢?你必须修改访问者角色和每一个具体访问者角色……
看来访问者角色不适合具体元素角色经常发生变化的情况。而且访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,而在java中就意味着其它的对象也可以访问。这就破坏了元素角色的封装性。而且在访问者模式中,元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。
……更多设计模式的内容,可以访问Refactoring.Guru