Android编程设计模式之访问者模式详解

本文实例讲述了Android编程设计模式之访问者模式。分享给大家供大家参考,具体如下:

一、介绍

访问者模式是一种将数据操作与数据结构分离的设计模式,它是《设计模式》中23种设计模式中最复杂的一个,但它的使用频率并不高,正如《设计模式》的作者GOF对访问者模式的描述:大多数情况下,你不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要它了。

访问者模式的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的处理。在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中会调用访问者的visit方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。

二、定义

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

三、使用场景

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

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

四、访问者模式的UML类图

UML类图:

角色介绍:

Visitor:接口或抽象类,定义了对每一个元素的访问行为,参数就是可访问的元素,方法个数理论上是个元素个数一样的。因此,访问者模式要求被访问的对象结构要稳定,如果经常增删元素,必然会导致频繁修改Visitor接口,就不适合用访问者模式了。

ConcreteVisitor:具体的访问者,定义具体的对每一个元素的具体访问行为。

Element:抽象的元素接口或抽象类,定义了一个接待访问者的方法,让每个元素都可以被访问者访问。

ElementA,ElementB:具体的元素类,提供接收访问方法的具体实现。这个具体实现通常是调用访问者提供的访问该元素的方法。

ObjectStructure:定义对象结构,里面维护了一个元素的集合,并且迭代这些元素供访问者访问。

五、简单示例

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

员工基类:

/**
 * 员工基类(Element)
 */
public abstract class Staff {
  //员工姓名
  public String name;
  //员工KPI
  public 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);
  }
  @Override
  public 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);
  }
  @Override
  public 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 {
  @Override
  public void visit(Engineer engineer) {
    System.out.println("工程师:" + engineer.name + ", KPI:" + engineer.kpi);
  }
  @Override
  public void visit(Manager manager) {
    System.out.println("经理:" + manager.name + ", KPI:" + manager.kpi
        + ", 新产品数量 :" + manager.getProducts());
  }
}

CTO访问者:

public class CTOVisitor implements Visitor {
  @Override
  public void visit(Engineer engineer) {
    System.out.println("工程师:" + engineer.name + ", 代码数量:" + engineer.getCodeLines());
  }
  @Override
  public 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看报表 =====");
    //设置访问者CEO
    report.showReport(new CEOVisitor());
    System.out.println("===== 给CTO看报表 =====");
    //设置访问者CTO
    report.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和类型转换,这使得代码难以升级维护。

六、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接口,每个接口处理一种元素类型,那么这就是典型的访问者模式。

七、总结

正如本节开头引用GOF的话所说:大多数情况下,你不需要使用访问者模式,但是,当你一旦需要使用它时,那你就是真的需要它了。在现实情况下,我们要根据具体的情况来评估是否适合使用访问者模式,例如,我们的对象结构是否足够稳定,使用访问者模式是否能够优化我们的代码,而不是使我们的代码变得更复杂。在使用一个模式之前,我们应该明确它的使用场景、它能解决什么问题等,以此来避免滥用设计模式的现象。

优点:

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

具有优秀的扩展性。

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

灵活性。

缺点:

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

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

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

更多关于Android相关内容感兴趣的读者可查看本站专题:《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》

希望本文所述对大家Android程序设计有所帮助。

时间: 2017-12-24

Android编程设计模式之模板方法模式详解

本文实例讲述了Android编程设计模式之模板方法模式.分享给大家供大家参考,具体如下: 一.介绍 在面向对象开发过程中,通常会遇到这样的一个问题,我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序,但是,某些步骤的具体实现是未知的,或者说某些步骤的实现是会随着环境的变化而改变的,例如,执行程序的流程大致如下: 1.检查代码的正确性: 2.链接相关的类库: 3.编译相关代码: 4.执行程序. 对于不同的程序设计语言,上述4个步骤都是不一样的,但是,它们的执行流程是固定的,这类问题的解决方

android设计模式之单例模式详解

这是我们最常见的一类模式,对这一类模式有一个通用的特点就是: 封装创建的方式和过程. 这里所谓封装就是隐藏的意思,对对象的创建方法和过程不可见,或者是虚拟的过程. 隐藏创建方式,就是如单例,工厂方法,隐藏创建过程则是指builder,原型,至于抽象工厂,我认为他包含了以上两种. 我们想想一个对象的创建有哪些步骤? 1.创建什么东西?--接口定义 2.谁创建?        --决策类or帮助类 3.如何创建?     --how,创建过程 4.什么时候创建?    --创建时机的触发 由此可知,

Android编程设计模式之策略模式详解

本文实例讲述了Android编程设计模式之策略模式.分享给大家供大家参考,具体如下: 一.介绍 在软件开发中也常常遇到这样的情况:实现某一个功能可以有多种算法或者策略,我们根据实际情况选择不同的算法或者策略来完成该功能.例如,排序算法,可以使用插入排序.归并排序.冒泡排序等. 针对这种情况,一种常规的方法是将多种算法写在一个类中.例如,需要提供多种排序算法,可以将这些算法写到一个类中,每一个方法对应一个具体的排序算法:当然,也可以将这些排序算法封装在一个统一的方法中,通过if-else-或者ca

Android编程设计模式之责任链模式详解

本文实例讲述了Android编程设计模式之责任链模式.分享给大家供大家参考,具体如下: 一.介绍 责任链模式(Iterator Pattern),是行为型设计模式之一.什么是"链"?我们将多个节点首尾相连所构成的模型称为链,比如生活中常见的锁链,就是由一个个圆角长方形的铁环串起来的结构.对于链式结构,每个节点都可以被拆开再连接,因此,链式结构也具有很好的灵活性.将这样一种结构应用于编程领域,将每一个节点看作是一个对象,每一个对象拥有不同的处理逻辑,将一个请求从链式的首端发出,沿着链的路

Android编程设计模式之解释器模式详解

本文实例讲述了Android编程设计模式之解释器模式.分享给大家供大家参考,具体如下: 一.介绍 解释器模式(Interpreter Pattern)是一种用的比较少的行为型模式,其提供了一种解释语言的语法或表达式的方式,该模式定义了一个表达式接口,通过该接口解释一个特定的上下文.在这么多的设计模式中,解释器模式在实际运用上相对来说要少很多,因为我们很少会自己去构造一个语言的文法.虽然如此,既然它能够在设计模式中有一席之位,那么必定有它的可用之处. 二.定义 给定一个语言,定义它的文法的一种表示

Android编程设计模式之命令模式详解

本文实例讲述了Android编程设计模式之命令模式.分享给大家供大家参考,具体如下: 一.介绍 命令模式(Command Pattern),是行为型设计模式之一.命令模式相对于其他的设计模式来说并没有那么多的条条框框,其实它不是一个很"规范"的模式,不过,就是基于这一点,命令模式相对于其他的设计模式更为灵活多变.我们接触比较多的命令模式个例无非就是程序菜单命令,如在操作系统中,我们点击"关机"命令,系统就会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然

Android编程设计模式之备忘录模式详解

本文实例讲述了Android编程设计模式之备忘录模式.分享给大家供大家参考,具体如下: 一.介绍 备忘录模式是一种行为模式,该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态,这有点像我们平时说的"后悔药".备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护好被保存的这些对象状态的完整性以及内部实现不向外暴露. 二.定义 在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样,以后就可将该对象恢复到原先保存的状态. 三.使用

Android编程设计模式之迭代器模式详解

本文实例讲述了Android编程设计模式之迭代器模式.分享给大家供大家参考,具体如下: 一.介绍 迭代器模式(Iterator Pattern)又称为游标(Cursor)模式,是行为型设计模式之一.迭代器模式算是一个比较古老的设计模式,其源于对容器的访问,比如Java中的List.Map.数组等,我们知道对容器对象的访问必然会涉及遍历算法,我们可以将遍历的方法封装在容器中,或者不提供遍历方法.如果我们将遍历的方法封装到容器中,那么对于容器类来说就承担了过多的功能,容器类不仅要维护自身内部的数据元

Android编程设计模式之状态模式详解

本文实例讲述了Android编程设计模式之状态模式.分享给大家供大家参考,具体如下: 一.介绍 状态模式中的行为是由状态来决定的,不同的状态下有不同的行为.状态模式和策略模式的结构几乎完全一样,但它们的目的.本质却完全不一样.状态模式的行为是平行的.不可替换的,策略模式的行为是彼此独立.可相互替换的.用一句话来表述,状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类.状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变. 二.定义 当一个对象的内在

Android编程设计模式之中介者模式详解

本文实例讲述了Android编程设计模式之中介者模式.分享给大家供大家参考,具体如下: 一.介绍 中介者模式(Mediator Pattern)也称为调解者模式或调停者模式,Mediator本身就有调停者和调解者的意思. 在日常生活中调停者或调解者这个角色我们见得比较多的是"和事老",也就是说调解两个有争端的人的角色,举个不恰当的例子,比如爸妈吵架,孩子或者双方父母则会出面劝架或阻止争吵,这里孩子或双方父母则是充当的是调解者的模式. 而对于中介呢?大家平时听得最多的莫过于房产中介了,在

Android编程设计模式之抽象工厂模式详解

本文实例讲述了Android编程设计模式之抽象工厂模式.分享给大家供大家参考,具体如下: 一.介绍 抽象工厂模式(Abstract Factory Pattern),也是创建型设计模式之一.前一节我们已经了解了工厂方法模式,那么这个抽象工厂又是怎么一回事呢?大家联想一下现实生活中的工厂肯定都是具体的,也就是说每个工厂都会生产某一种具体的产品,那么抽象工厂意味着生产出来的产品是不确定的,那这岂不是很奇怪?抽象工厂模式起源于以前对不同操作系统的图形化解决方案,如不同操作系统中的按钮和文本框控件其实现

PHP实现设计模式中的抽象工厂模式详解

抽象工厂模式(Abstact Factory)是一种常见的软件设计模式.该模式为一个产品族提供了统一的创建接口.当需要这个产品族的某一系列的时候,可以为此系列的产品族创建一个 具体的工厂类. [意图] 抽象工厂模式提供一个创建一系统相关或相互依赖对象的接口,而无需指定它们具体的类[GOF95] [抽象工厂模式结构图] [抽象工厂模式中主要角色] 抽象工厂(Abstract Factory)角色:它声明一个创建抽象产品对象的接口.通常以接口或抽象类实现,所有的具体工厂类必须实现这个接口或继承这个类

php设计模式之简单工厂模式详解

本文以实例形式较为详细的介绍了PHP设计模式的简单工厂模式,对于进行PHP程序设计来说有很好的借鉴作用.具体如下: 一.概念 简单工厂模式 [静态工厂方法模式](Static Factory Method) 是类的创建模式 工厂模式的几种形态: 1.简单工厂模式(Simple Factory)又叫做 静态工厂方法模式(Static Factory Method) 2.工厂方法模式(Factory Method)又叫做 多态性工厂模式(Polymorphic Factory) 3.抽象工厂模式(A

Java设计模式之静态工厂模式详解

本文实例讲述了Java设计模式之静态工厂模式.分享给大家供大家参考,具体如下: 静态工厂模式(static factory)也叫简单工厂模式. 涉及到3个角色:工厂类角色,抽象产品类角色和具体产品类角色. 抽象产品类可以使用接口或者父类来描述产品对象的行为特征. 具体产品类就是某一具体的对象. 静态工厂类有一个静态的方法,含有判断逻辑,决定要创建哪一种具体的产品对象. 其设计模式如下: 抽象产品类  IProduct package org.test.design.sf; public inte

java设计模式之简单工厂模式详解

简单工厂模式:由一个工厂对象决定创建出哪一种类的实例. 1.抽象类 public abstract class People { public abstract void doSth(); } 2.具体类 public class Man extends People{ @Override public void doSth() { System.out.println("I'm a man,I'm coding."); } } 3.具体类 public class Girl exte

Python设计模式之抽象工厂模式原理与用法详解

本文实例讲述了Python设计模式之抽象工厂模式原理与用法.分享给大家供大家参考,具体如下: 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的类 下面是一个抽象工厂的demo: #!/usr/bin/env python # -*- coding:utf-8 -*- __author__ = 'Andy' """ 大话设计模式 设计模式--抽象工厂模式 抽象工厂模式(Abstract Factory

Java设计模式之抽象工厂模式实例详解

本文实例讲述了Java设计模式之抽象工厂模式.分享给大家供大家参考,具体如下: 具体工厂类:生产创建某一类具体产品对象. 抽象产品类可以使用接口或者父类来描述产品对象的行为特征. 具体产品类就是某一具体的对象. 那么抽象工厂模式和工厂模式的不同之处呢? 其实最大的不同就在于,在产品类的结构更加复杂时,抽象工厂模式针对不同的产品族(就是一类产品对象)定义了不同的行为,也就是在父类或接口中,定义了不同的产生方法.不同的产品族调用各自的创建方法.同时不同的产品族横向比较,也有可归类的相同特征,这些特征

Java设计模式之抽象工厂模式

一.场景描述 接<Java设计模式(一)工厂模式> 工厂模式有一缺点,就是破坏了类的封闭性原则.例如,如果需要增加Word文件的数据采集,此时按以下步骤操作: 创建Word文件数据采集类,实现仪器数据采集接口: 修改仪器数据采集工厂类,增加Word文件数据采集类的工厂方法: 调用工厂类的word文件方法: 步骤2修改了工厂类,如果每增加一实现类都需要修改工厂类,那么这样就不合理了. 解决办法是使用抽象工厂类,为每一个实现类都创建其工厂类,并增加工厂接口,使各工厂类实现该接口. 使用抽象工厂后,