访问者模式.docx
《访问者模式.docx》由会员分享,可在线阅读,更多相关《访问者模式.docx(21页珍藏版)》请在冰点文库上搜索。
访问者模式
访问者模式的使用
一、问题场景
考虑这样一个应用场景:
扩展客户管理功能。
1.现有业务
客户分为两类:
个人客户和企业客户,客户现有的业务功能为资产登记
类图如下:
代码示例如下:
//ACustomer.java客户抽象类
packageorg.angus.nopattern;
/**
*客户接口
**/
publicabstractclassACustomer{
protectedStringname;
publicStringgetName(){
returnthis.name;
}
/**
*资产登记
*模拟用户现在具有功能
**/
publicabstractvoidpropertyRegister();
}
//Person.java个人客户
packageorg.angus.nopattern;
/**
*个人客户
**/
publicclassPersonextendsACustomer{
publicPerson(Stringname){
this.name=name;
}
publicvoidpropertyRegister(){
System.out.println("计算个人用户资产...");
}
}
//Enterprise.java企业客户
packageorg.angus.nopattern;
/**
*企业客户
**/
publicclassEnterpriseextendsACustomer{
publicEnterprise(Stringname){
this.name=name;
}
publicvoidpropertyRegister(){
System.out.println("计算企业用户资产...");
}
}
//调用客户端Client.java
packageorg.angus.nopattern;
importjava.util.ArrayList;
importjava.util.Collection;
publicclassClient{
publicstaticvoidmain(String[]args){
CollectioncolCustomer=getTestData();
//循环对所有客户进行业务操作
for(ACustomercustomer:
colCustomer){
//资产登记业务
customer.propertyRegister();
}
}
privatestaticCollectiongetTestData(){
CollectioncolCustomer=newArrayList();
ACustomercustomer1=newPerson("张三");
colCustomer.add(customer1);
ACustomercustomer2=newEnterprise("山水一村农场");
colCustomer.add(customer2);
ACustomercustomer3=newEnterprise("店小二个体店");
colCustomer.add(customer3);
returncolCustomer;
}
}
2.功能扩展
现在随着业务的发展,需要加强对客户的管理功能,假设现在需要增加以下功能:
1.计算客户所得税
2.计算客户消费所得积分(如常见的话费积分、购物积分,各服务商针对客户类型不同方式不同)
3.客户价值分析
不使用模式的解决方案
要实现上述要求的新功能,也不是很困难,比较直接的想法就是在抽象类ACustomer.java中添加三个抽象方法,然后由Person.java和Enterprise.java实现即可。
3.有何问题
上面用很简单的方式实现了要求的功能,这种实现有没有问题呢?
仔细分析,发现有两个主要的问题:
1.在企业客户和个人客户的类里面,都分别实现了资产登记、计算所得税、计算客户积分、客户价值分析。
这些功能的实现时混在在同一个类里面的,而相同的功能分散到不同的类中区实现,会导致整个系统难以理解、难以维护。
2.而更为严重的是,采用这种实现方式,如果要给客户扩展新的功能,每次扩展都需要改动企业客户和个人客户的类,类继承体系改动太大。
针对上述问题,我们需要采用别的方式实现,即在不改变客户这个对象结构中各元素类的前提下,为这些类定义新的功能。
二、使用访问者模式
用来解决上述问题的一个合理的解决方案,就是使用访问者模式。
1.模式定义
表示一个作用于某对象结构中的各元素的操作。
使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
仔细分析上面的示例,对于客户这个对象结构,不想改变类,又要添加新的功能,很明显就要采用一种动态的方式,在运行期间把功能动态地添加到对象结构中去。
访问者模式结构和说明:
Visitor
访问者接口,为所有的访问者对象声明一个visit方法,用来代表为对象结构添加的功能,理论上可以代表任意的功能。
ConcreteVisitor
具体的访问者实现对象,实现要真正被添加到对象结构中的功能。
Element
抽象的元素对象,对象结构的顶层接口,定义接受访问的操作。
ConcreteElement
具体元素对象,对象结构中具体的对象,也是被访问的对象,通常会回调访问者的真实功能,同时开放自身的数据供访问者使用。
ObjectStructure
对象结构,通常包含多个被访问的对象,它可以遍历这多个被访问的对象,也可以让访问者访问它的元素。
可以是一个复合或是一个集合,如一个列表或无序集合。
示例代码:
/**
*访问者接口
*/
publicinterfaceVisitor{
/**
*访问元素A,相当于给元素A添加访问者的功能
*@paramelementA元素A的对象
*/
publicvoidvisitConcreteElementA(ConcreteElementAelementA);
/**
*访问元素B,相当于给元素B添加访问者的功能
*@paramelementB元素B的对象
*/
publicvoidvisitConcreteElementB(ConcreteElementBelementB);
}
/**
*具体的访问者实现
*/
publicclassConcreteVisitor1implementsVisitor{
publicvoidvisitConcreteElementA(ConcreteElementAelement){
System.out.println("元素["+element.getClass().getName()+"]通过访问者实现了功能xxx");
//此处可能还需要调用元素已有的功能,如:
element.opertionA();
}
publicvoidvisitConcreteElementB(ConcreteElementBelement){
System.out.println("元素["+element.getClass().getName()+"]通过访问者实现了功能xxx");
//此处可能还需要调用元素已有的功能,如:
element.opertionB();
}
}
/**
*具体的访问者实现
*/
publicclassConcreteVisitor2implementsVisitor{
publicvoidvisitConcreteElementA(ConcreteElementAelement){
System.out.println("元素["+element.getClass().getName()+"]通过访问者实现了功能xxx");
//此处可能还需要调用元素已有的功能,如:
element.opertionA();
}
publicvoidvisitConcreteElementB(ConcreteElementBelement){
System.out.println("元素["+element.getClass().getName()+"]通过访问者实现了功能xxx");
//此处可能还需要调用元素已有的功能,如:
element.opertionB();
}
}
/**
*被访问的元素的接口
*/
publicabstractclassElement{
/**
*接受访问者的访问
*@paramvisitor访问者对象
*/
publicabstractvoidaccept(Visitorvisitor);
}
/**
*具体元素的实现对象
*/
publicclassConcreteElementAextendsElement{
publicvoidaccept(Visitorvisitor){
//回调访问者对象的相应方法
visitor.visitConcreteElementA(this);
}
/**
*示例方法,表示元素已有的功能实现
*/
publicvoidopertionA(){
//已有的功能实现
System.out.println("元素["+this.getClass().getName()+"]已具有的功能xxx");
}
}
/**
*具体元素的实现对象
*/
publicclassConcreteElementBextendsElement{
publicvoidaccept(Visitorvisitor){
//回调访问者对象的相应方法
visitor.visitConcreteElementB(this);
}
/**
*示例方法,表示元素已有的功能实现
*/
publicvoidopertionB(){
//已有的功能实现
System.out.println("元素["+this.getClass().getName()+"]已具有的功能xxx");
}
}
importjava.util.ArrayList;
importjava.util.Collection;
/**
*对象结构,通常在这里对元素对象进行遍历,让访问者能访问到所有的元素
*/
publicclassObjectStructure{
/**
*示意,表示对象结构,可以是一个组合结构或是集合
*/
privateCollectioncol=newArrayList();
/**
*示意方法,组建对象结构,向对象结构中添加元素。
*不同的对象结构有不同的构建方式
*@paramele加入到对象结构的元素
*/
publicvoidaddElement(Elementele){
this.col.add(ele);
}
/**
*示意方法,提供给客户端操作的高层接口
*@paramvisitor客户端需要使用的访问者
*/
publicvoidhandleRequest(Visitorvisitor){
//循环对象结构中的元素,接受访问
for(Elementele:
col){
ele.accept(visitor);
}
}
}
//调用客户端
publicclassClient{
publicstaticvoidmain(String[]args){
//创建ObjectStructure
ObjectStructureos=newObjectStructure();
//创建要加入对象结构的元素
ElementeleA=newConcreteElementA();
ElementeleB=newConcreteElementB();
//把元素加入对象结构
os.addElement(eleA);
os.addElement(eleB);
//创建访问者
Visitorvisitor=newConcreteVisitor1();
//调用业务处理的方法
os.handleRequest(visitor);
}
}
2.使用访问者模式重写示例
按照访问者模式的类关系图:
重写后的代码如下:
packageorg.angus.visitor;
/**
*被访问的元素接口
**/
publicabstractclassACustomer{
protectedStringname;
publicStringgetName(){
returnthis.name;
}
/**
*资产登记
*模拟用户现在具有功能
**/
publicabstractvoidpropertyRegister();
/**
*接受访问者的访问
*扩展用户新功能时使用访问者模式
*@paramvisitor访问者对象
**/
publicabstractvoidaccept(Visitorvisitor);
}
packageorg.angus.visitor;
/**
*个人客户
**/
publicclassPersonextendsACustomer{
publicPerson(Stringname){
this.name=name;
}
publicvoidpropertyRegister(){
System.out.println("计算个人用户资产...");
}
publicvoidaccept(Visitorvisitor){
//回调访问者对象的相应方法
//当访问者是不同功能的实现时相当于给Person对象添加不同的功能
visitor.visitPersion(this);
}
}
packageorg.angus.visitor;
/**
*企业客户
**/
publicclassEnterpriseextendsACustomer{
publicEnterprise(Stringname){
this.name=name;
}
publicvoidpropertyRegister(){
System.out.println("计算企业用户资产...");
}
publicvoidaccept(Visitorvisitor){
visitor.visitEnterprise(this);
}
}
packageorg.angus.visitor;
publicinterfaceVisitor{
/**
*访问个人用户,相当于给个人用户添加访问者的功能
**/
publicvoidvisitPersion(Personcustomer);
/**
*访问企业用户,相当于给企业用户添加访问者的功能
**/
publicvoidvisitEnterprise(Enterprisecustomer);
}
packageorg.angus.visitor;
/**
*计算所得税
**/
publicclassCalculateIncomeTaximplementsVisitor{
/**
*计算个人所得税
**/
publicvoidvisitPersion(Personcustomer){
System.out.println("计算个人用户["+customer.getName()+"]的个税...");
System.out.println("2014年11月份工资收入3800块,个税起征点3500,"
+"扣除社保、医保、公积金后计算个税为:
0.00");
}
/**
*计算企业营业所得税
**/
publicvoidvisitEnterprise(Enterprisecustomer){
System.out.println("计算企业用户["+customer.getName()+"]的个税");
System.out.println("2014年11月份营业收入250000块,营业税率17%,"
+"计算个税为:
42500.00");
}
}
packageorg.angus.visitor;
importjava.util.ArrayList;
importjava.util.Collection;
publicclassObjectStructure{
privateCollectioncol=newArrayList();
publicvoidaddCustomer(ACustomercustomer){
this.col.add(customer);
}
publicvoidhandleRequest(Visitorvisitor){
for(ACustomercustomer:
col){
customer.accept(visitor);
}
}
}
packageorg.angus.visitor;
publicclassClient{
publicstaticvoidmain(String[]args){
//对象结构
ObjectStructureos=newObjectStructure();
ACustomerpersion=newPerson("angus.wang");//个人用户
ACustomerenterprise=newEnterprise("新世纪联华超市");//企业用户
os.addCustomer(persion);
os.addCustomer(enterprise);
Visitorvisitor=newCalculateIncomeTax();//计算个税访问者
//对对象结构中的每类用户都计算个税
os.handleRequest(visitor);
}
}
3.功能扩展的应用
如果我们要为客户添加计算消费积分的功能,则添加一个访问者实现即可:
packageorg.angus.visitor;
/**
*计算积分
**/
publicclassCalculateIntegralimplementsVisitor{
/**
*计算个人用户积分
**/
publicvoidvisitPersion(Personcustomer){
System.out.println("计算个人用户["+customer.getName()+"]的优惠积分");
System.out.println("2014年度总消费2300元,积分规则为每一元得一分,总积分为:
2300");
}
/**
*计算企业用户积分
**/
publicvoidvisitEnterprise(Enterprisecustomer){
System.out.println("计算企业用户["+customer.getName()+"]的优惠积分");
System.out.println("2014年度总消费25016元,积分规则为每十元得一分,总积分为:
2501.6");
}
}
然后在Client中添加如下代码:
System.out.println("------------------------------------------");
Visitorvisitor2=newCalculateIntegral();//计算积分访问者
//对对象结构中的每类用户都计算积分
os.handleRequest(visitor2);
如果要为客户添加客户价值分析功能,原有的Person类、Enterprise类都不用做任何修改,只需要添加一个客户价值分析功能的访问者实现即可。
通过访问者模式实现了,通过对原有对象调用不同的访问者,实现不同的功能,进而在不修改原有对象的情况下,实现对原有对象增加新的功能。
三、对访问者模式的思考
(一)访问者模式的核心技术是业务请求通过调用通路的二次分发技术:
1.调用通路:
访问者提供一个访问的方法,若visit方法;元素对象提供一个接受访问者的方法,如accept方法。
这两个方法并不代表如何具体的功能,只是构成一个调用的通路,真正的实现时在accept方法里面,回调visit的方法,从而回调到访问者的具体实现上
2.二次分发技术:
元素对象提供一个accept方法,让这些元素来接收访问,这是请求的第一次分发;在具体的元素对象中实现accept方法的时候,回调访问者的visit方法,等于请求被第二次分发了,请求被分发给访问者来惊醒处理,真正实现功能的正是访问者的visit方法。
访问者模式将纵向的功能扩展(继承体系)变换为横向的扩展,从而不改动继承体系,实现了功能的线性扩展,便于维护,有很好的灵活性。
(二)访问者模式的开点和闭点:
访问者模式对元素的结构闭,而对作用元素的功能开。
如果再对上面例子中的用户添加一个计算信用等级的功能,则类关系图如下:
类文件:
CalculateCreditLevel.java
packageorg.angus.visitor;
/**
*计算客户信用等级
**/
publicclassCalculateCreditLevelimplementsVisitor{
/**
*计算个人客户信用等级
**/
publicvoidvisitPersion(Personcustomer