第十六章、访问者模式

访问者模式是一种行为型模式,它是23种设计模式中最复杂的一个,虽然使用频率不高,但是并不代表可以忽略,在合适的地方,它会带来意想不到的灵活性。访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。

1.定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

2.使用场景

(1)对象结构比较稳定,但经常需要在此对象结构上定义新的操作。

(2)需要对一个对象结构中的对象进行很多不同的且不相关的操作,而需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

3.UML类图

(1)Visitor:接口或者抽象类,它定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法数理论上来讲与元素个数是一样的,因此,访问者模式要求元素的类族要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor接口,如果这样则不适合使用访问者模式。

(2)ConcreteVisitor1、ConcreteVisitor2:具体的访问类,它需要给出对每一个元素类访问时所产生的具体行为。

(3)Element:元素接口或者抽象类,它定义了一个接受访问者的方法(Accept),其意义是指每一个元素都要可以被访问者访问。

(4)ConcreteElementA、ConcreteElementB:具体的元素类,它提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

(5)ObjectStructure:定义当中所说的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素供访问者访问。

4.简单实现

情景:年终了,公司会给员工进行业绩考核。但是,不同领域的管理人员对于员工的评定标准不一样。现在员工有攻城狮和经理,评定者有CEO和CTO,我们假定CTO只关注攻城狮的代码量、经理的新产品数量,而CEO关注的是攻城狮的KPI和经理的KPI以及新产品数量。

员工基类:

/*** 员工基类(Element) */
public abstract class Staff {//员工姓名public String name;//员工KPIpublic int kpi;public Staff(String name) {super();this.name = name;this.kpi = new Random().nextInt(10);}//接受Visitor的访问public abstract void accept(Visitor visitor);}

攻城狮:

/*** 攻城狮 */
public class Engineer extends Staff{private int codeLines;//代码数量public Engineer(String name) {super(name);codeLines = new Random().nextInt(10 * 10000);}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}//攻城狮这一年写的代码数量public int getCodeLines(){return codeLines;}
}

经理:

/*** 经理*/
public class Manager extends Staff{private int products;//产品数量public Manager(String name) {super(name);products = new Random().nextInt(10);}@Overridepublic void accept(Visitor visitor) {visitor.visit(this);}//一年内做的产品数量public int getProducts(){return products;}
}

Visitor类:

public interface Visitor {/*** 访问攻城狮类型*/public void visit(Engineer engineer);/*** 访问经理类型*/public void visit(Manager manager);
}

CEO访问者:

public class CEOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {System.out.println("攻城狮:" + engineer.name + ", KPI:" + engineer.kpi);}@Overridepublic void visit(Manager manager) {System.out.println("经理:" + manager.name + ", KPI:" + manager.kpi+ ", 新产品数量 :" + manager.getProducts());}}

CTO访问类:

public class CTOVisitor implements Visitor {@Overridepublic void visit(Engineer engineer) {System.out.println("攻城狮:" + engineer.name + ", 代码数量:" + engineer.getCodeLines());}@Overridepublic void visit(Manager manager) {System.out.println("经理:" + manager.name +", 产品数量 :" + manager.getProducts());}}

员工报表类:

//员工业务报表类(ObjectStructure)
public class BusinessReport {List<Staff> mStaffs = new LinkedList<Staff>();public BusinessReport() {mStaffs.add(new Manager("王经理"));mStaffs.add(new Engineer("攻城狮-A"));mStaffs.add(new Engineer("攻城狮-B"));mStaffs.add(new Manager("李经理"));mStaffs.add(new Engineer("攻城狮-C"));}/*** 为访问者展示报表 * @param visitor 如CEO、CTO*/public void showReport(Visitor visitor){for(Staff staff : mStaffs){staff.accept(visitor);}}
}

Client访问:

public class Client {public static void main(String[] args) {//构建报表BusinessReport report = new BusinessReport();System.out.println("===== 给CEO看报表 =====");//设置访问者CEOreport.showReport(new CEOVisitor());System.out.println("===== 给CTO看报表 =====");//设置访问者CTOreport.showReport(new CTOVisitor());}
}

结果:

===== 给CEO看报表 =====
经理:王经理, KPI:2, 新产品数量 :5
攻城狮:攻城狮-A, KPI:5
攻城狮:攻城狮-B, KPI:7
经理:李经理, KPI:9, 新产品数量 :8
攻城狮:攻城狮-C, KPI:1
===== 给CTO看报表 =====
经理:王经理, 产品数量 :5
攻城狮:攻城狮-A, 代码数量:26238
攻城狮:攻城狮-B, 代码数量:8282
经理:李经理, 产品数量 :8
攻城狮:攻城狮-C, 代码数量:47927

从上面代码中可以看出,如果要增加一个访问者,你新创建一个实现了Visitor接口的类,然后实现两个visit方法来对不同的元素进行不同的操作,从而达到数据对象与数据操作相分离的效果。如果不使用访问者模式,而又想对不同元素进行不同的操作,那么必定会使用if-else和类型转换,这使得代码难以升级维护。

5.Android中的访问者模式

安卓中的著名开源库ButterKnife、Dagger、Retrofit都是基于APT(Annotation Processing Tools)实现。而编译注解核心依赖APT。当我们通过APT处理注解时,最终会将获取到的元素转换为相应的Element元素,以便获取到它们对应信息。那么元素基类的源码如下:(路径:javax.lang.model.element.Element)

public interface Element extends javax.lang.model.AnnotatedConstruct {/*** Returns the {@code kind} of this element.** @return the kind of this element*/ElementKind getKind();//获取元素类型//代码省略/*** Applies a visitor to this element.** @param <R> the return type of the visitor's methods* @param <P> the type of the additional parameter to the visitor's methods* @param v   the visitor operating on this element* @param p   additional parameter to the visitor* @return a visitor-specified result*/<R, P> R accept(ElementVisitor<R, P> v, P p);//接受访问者的访问
}

ElementVisitor就是访问者类型,ElementVisitor源码如下:

public interface ElementVisitor<R, P> {/*** Visits an element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visit(Element e, P p);/*** A convenience method equivalent to {@code v.visit(e, null)}.* @param e  the element to visit* @return a visitor-specified result*/R visit(Element e);/*** Visits a package element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitPackage(PackageElement e, P p);/*** Visits a type element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitType(TypeElement e, P p);/*** Visits a variable element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitVariable(VariableElement e, P p);/*** Visits an executable element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitExecutable(ExecutableElement e, P p);/*** Visits a type parameter element.* @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result*/R visitTypeParameter(TypeParameterElement e, P p);/*** Visits an unknown kind of element.* This can occur if the language evolves and new kinds* of elements are added to the {@code Element} hierarchy.** @param e  the element to visit* @param p  a visitor-specified parameter* @return a visitor-specified result* @throws UnknownElementException*  a visitor implementation may optionally throw this exception*/R visitUnknown(Element e, P p);
}

在ElementVisitor中定义了多种visit接口,每个接口处理一种元素类型,那么这就是典型的访问者模式。

6.总结

1.优点

(1)各角色职责分离,符合单一职责原则。

(2)具有优秀的扩展性。

(3)使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。

(4)灵活性。

2.缺点

(1)具体元素对访问者公布细节,违反了迪米特原则。

(2)具体元素变更时导致修改成本大。

(3)违反了依赖倒置原则,为了达到“区别对待”而依赖了具体类,没有依赖抽象。

7.参考

1. 访问者模式

《Android源码设计模式解析与实战》读书笔记(十六)相关推荐

  1. 《Android源码设计模式解析与实战》读书笔记(十三)

    第十三章.备忘录模式 备忘录模式是一种行为模式,该模式用于保存对象当前的状态,并且在之后可以再次恢复到此状态,有点像是我们平常说的"后悔药". 1.定义 在不破坏封闭的前提下,捕获 ...

  2. 《Android源码设计模式解析与实战》读书笔记(十七)

    第十七章.中介者模式 中介者模式也称为调解者模式或调停者模式,是一种行为型模式. 1.定义 中介者模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显作用.从而使它们可以松散耦合.当某些对象之 ...

  3. 《Android源码设计模式解析与实战》读书笔记(十二)

    第十二章.观察者模式 观察者模式是一个使用率非常高的模式,它最常用在GUI系统.订阅–发布系统.因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们之间的依赖性更小,甚至做到毫无依赖. ...

  4. 《Android源码设计模式解析与实战》读书笔记(十四)

    第十四章.迭代器模式 迭代器模式,又叫做游标模式,是行为型设计模式之一.我们知道对容器对象的访问必然会涉及遍历算法,我们可以将遍历的方法封装在容器中,或者不提供遍历方法,让使用容器的人自己去实现去吧. ...

  5. 《Android源码设计模式解析与实战》读书笔记(十)

    第十章.解释器模式 解释器模式是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式.但是它的使用场景确实很广泛,只是因为我们自己很少回去构造一个语言的文法,所以使用较少. 1.定义 ...

  6. 《Android源码设计模式解析与实战》读书笔记(二十四)

    第二十四章.桥接模式 桥接模式也称为桥梁模式,是结构型设计模式之一.桥接模式中体现了"单一职责原则"."开闭原则"."里氏替换原则".&qu ...

  7. 《Android源码设计模式解析与实战》读书笔记(二十一)

    第二十一章.装饰模式 装饰模式也称为包装模式,是结构型设计模式之一.装饰模式是一种用于替代继承技术的一种方案. 1.定义 动态的给一个对象添加一些额外的职责.就增加功能来说,装饰模式相比生成子类更为灵 ...

  8. 《Android源码设计模式解析与实战》读书笔记(十一)

    第十一章.命令模式 命令模式是行为型模式之一.总体来说并不难理解,只是比较繁琐,他会将简单的调用关系解耦成多个部分,增加类的复杂度,但是即便如此,命令模式的结构依然清晰. 1.定义 将一个请求封装成一 ...

  9. 《Android源码设计模式解析与实战》读书笔记(十九)

    第十九章.组合模式 组合模式也称为部分-整体模式,结构型设计模式之一. 1.定义 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性. ...

最新文章

  1. 【node】------mongoose的基本使用------【巷子】
  2. Careercup - Microsoft面试题 - 5428361417457664
  3. 企业管理软件解决方案 出售 :针对华东区中小企业订单仓储管理流程
  4. NS2 分裂机制及代码分析一
  5. 三、系统分层和分割策略
  6. ubuntu10下Eclipse中无法输入中文
  7. 微信机器人,微信聊天机器人搭建教程附源代码
  8. 2022-09-21 虚机安装威联通 QuTScloud
  9. linux命令行取出网卡ip地址
  10. 01.JavaSwing(概述)
  11. Arduino之干接点控制继电器取反实例
  12. 元宇宙来了,用好名字好概念赋能中国制造国际营销的初步构想
  13. python调用adb shell命令_如何在python脚本里面连续执行adb shell后面的各种命令
  14. 跨越新数字鸿沟,懂行共识激发共振效应,成就数字化转型最短路径
  15. 中学计算机教育论文,【中学计算机教育互动式培养策略计算机教育论文材料】...
  16. 谷歌的两个经典事件案例
  17. 接口并发测试之:WebSocket从原理到代码实战,我没草率~
  18. android 获取系统的ram和rom,以及可用的
  19. mysql学生选课系统的关系模型_数据库系统原理ER模型与关系模型
  20. Python 常用静态代码检查工具简介

热门文章

  1. 使用高德SDK开发安卓地图应用软件
  2. word将一个文档的样式导入到另一个文档
  3. 博科光纤交换机常用命令
  4. 数据库-mysql的配置
  5. Android自定义键盘的简单实现
  6. Spring框架基础学习小结。概念,文件配置
  7. 数据应用案例之“客户画像体系”
  8. onNewIntent与singleTask
  9. MySql连接数据库
  10. 学习vue笔记(5)