Android设计模式之访问者模式

访问者模式是一种将数据库操作与数据结构分离的设计模式。

访问者模式的基本思想


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


          访问者模式的定义

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


          访问者模式的使用场景


1.对象结构比较稳当,但经常需要在此对象结构上定义新的操作。
2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,儿需要避免这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。

 

角色介绍

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

ConcreteVisitor:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为。

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

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

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

 

访问者模式简单Demo

package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 * 员工基类
 * Staff类定义了员工的基本信息以及一个accept方法,accept方法表示接受访问者的访问,由子类具体实现。
 */
public abstract class Staff {

    public String name;
    //员工kpi
    public int kpi;
    public Staff(String aName) {
        this.name = aName;
        kpi = new Random().nextInt(10);
    }
    //接受Visitor的访问
    public abstract void accept(Visitor visitor);
}
package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 * 工程师类中添加了获取代码行数的函数,而经理类型中则添加了获取新产品数量的函数,
 * 它们的职责是不一样的,也正是它们的差异性,才使得访问者模式能够发挥它是作用。
 * Staff、Engineer、Manager3个类型就是对象结构,这些类型相对稳定,不会发生变化。
 */

public class Engineer extends Staff {

    public Engineer(String aName) {
        super(aName);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    //工程师这一年写的代码数量
    public int getCodeLines() {
        return new Random().nextInt(10*10000);
    }
}

 

package visitorpattern.gome.com.visitorpattern;
import java.util.Random;
/**
 * Created by ying.zhang on 2018/8/13.
 */
public class Manager extends Staff {

    private int products;// 产品数量

    public Manager(String aName) {
        super(aName);
        products = new Random().nextInt(10);
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }

    //一年内做的产品数量
    public int getProducts() {
        return products;
    }
}

 

package visitorpattern.gome.com.visitorpattern;
/**
 * Created by ying.zhang on 2018/8/13.
 */
public interface Visitor {

    //访问工程师类型
    public void visit(Engineer engineer);
    //访问经理类型
    public void visit(Manager manager);

}

 

package visitorpattern.gome.com.visitorpattern;

import android.util.Log;

/**
 * Created by ying.zhang on 2018/8/13.
 * 在CEO的访问者中,CEO只关注Engineer员工的KPI,而对于Manager类型的员工除了KPI之外,还有该Manager本年度新开发产品的数量。
 * 两类员工关注点不同,通过两个visitor方法分别进行处理。
 */

public class CEOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        Log.v(MainActivity.TAG, "engineer:" + engineer.name + ", KPI:" + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        Log.v(MainActivity.TAG, "mananger:" + manager.name + ", KPI:" + manager.kpi + ",新产品数量:" + manager.getProducts());
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import android.util.Log;

/**
 * Created by ying.zhang on 2018/8/13.
 */

public class CTOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        Log.v(MainActivity.TAG, "engineer:"+ engineer.name + ",codeslines:" + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        Log.v(MainActivity.TAG, "manager:" + manager.name + "productsNumber:" + manager.getProducts());
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import java.util.LinkedList;
import java.util.List;

/**
 * Created by ying.zhang on 2018/8/13.
 * 员工业务报表1类
 *
 * 然后将这些员工添加到一个业务报表类中,公司高层通过该报表类的showReport函数查看所有员工的业绩
 */

public class BusinessReport {

    List<Staff> mStaffs = new LinkedList<Staff>();

    public BusinessReport() {
        mStaffs.add(new Manager("manager-1"));
        mStaffs.add(new Engineer("engineer-1"));
        mStaffs.add(new Engineer("engineer-2"));
        mStaffs.add(new Engineer("engineer-3"));
        mStaffs.add(new Manager("manager-2"));
    }

    /**
     * 为访问者展示报表
     * @param visitor CEO或者CTO
     */
    public void showReport(Visitor visitor) {
        for (Staff staff : mStaffs) {
            staff.accept(visitor);
        }
    }
}

 

package visitorpattern.gome.com.visitorpattern;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    public static String TAG = "VisitorLOG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //构建报表
        BusinessReport report = new BusinessReport();
        Log.v(TAG, "--------CEO visit--------------");
        //设置访问者,这里是CEO
        report.showReport(new CEOVisitor());
        Log.v(TAG, "--------CTO visit-------------");
        //注入另一个访问者, CTO
        report.showReport(new CTOVisitor());
    }
}

 

Log分析:

V/VisitorLOG: --------CEO visit--------------

V/VisitorLOG: mananger:manager-1, KPI:0,新产品数量:5

V/VisitorLOG: engineer:engineer-1, KPI:3

V/VisitorLOG: engineer:engineer-2, KPI:6

V/VisitorLOG: engineer:engineer-3, KPI:7

V/VisitorLOG: mananger:manager-2, KPI:1,新产品数量:5

V/VisitorLOG: --------CTO visit-------------

V/VisitorLOG: manager:manager-1productsNumber:5

V/VisitorLOG: engineer:engineer-1,codeslines:62026

V/VisitorLOG: engineer:engineer-2,codeslines:34320

V/VisitorLOG: engineer:engineer-3,codeslines:53385

V/VisitorLOG: manager:manager-2productsNumber:5

 

 

 

上述示例中,Staff扮演了Element角色,而Engineer和Manager都是ConcreteElement;CEOVisitor和CTOVisitor都是具体的Visitor对象;而BusinessReport就是ObjectStructure;Client就是客户端代码。

访问者模式最大的优点就是增加访问者非常容易,从代码中可以看到,如果要增加一个访问者,你新创建一个实现了Visitor接口的类,然后实现两个visit函数来对不同的元素进行不同的操作,从而达到数据对象与数据操作分离的效果。

 

 

 

在现阶段的Android开发中,注解越来越流行,按照处理时期,注解又可以分为两种类型,一种是运行时注解,另一种是编译时注解。

运行时注解因为性能问题被一些人诟病,编译时注解的核心原理依赖APT(Annotation Processing Tools)实现,例如ButterKnife、Dagger、Retrofit等开源库都是基于APT。那么编译时注解是如何工作的?它与访问者模式又有什么样的关系?

编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员在编译期间进行相应的处理,例如根据注解生成新的Java类,这也是ButterKnife等开源库的基本原理。

在编译处理的时候,是分开进行的。如果在某个处理中产生了新的Java源文件,那么就需要另外一个处理来处理新生成的源文件,如此往复,直到没有新文件被生成为止。在完成处理之后,再对Java代码进行编译。JDK5.0中提供了APT工具用来对注解进行处理。APT是一个命令行工具,与之配套的还有一套用来描述语义结构的Mirror API,Mirror API(com.sun.mirror.*)描述的是程序在编译时的静态结构,通过Mirror API可以获取到被注解的Java类型元素的信息,从而提供相应的逻辑处理,具体的处理工作交给APT来完成。编写注解处理器的核心是AnnotationProcessorFactory 和AnnotationProcessor两个接口,后者表示注解处理器,而前者则为某些注解类型创建注解处理器的工厂。

对于编译器来说,代码中的元素结构是基本不变的,例如,组成代码的基本元素有包、类、函数、字段、类型参数、变量。JDK中为这些元素定义了一个基类,也就是Element类,它有如下几个子类:

1.PackageElement——包元素,包含了某个包下的信息,可以获取到包名等;

2.TypeElement——类型元素,如某个字段属于某种类型;

3.ExecutableElement——可执行元素,代表了函数类型的元素;

4.VariableElement——变量元素

5.TypeParameterElement——类型参数元素

 

因为注解可以指定作用在哪些元素上,因此,通过上述抽象来对应这些元素,例如下面这个注解,指定的是只能作用在函数上面,并且这个注解只能保留在class文件中。

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.CLASS)

@interface Test{

String value();

}

该注解因为只能作用于函数类型,因此,它对应的元素类型就是ExecutableElement,当我们想通过ADT处理这个注解时就可以获取目标对象上的Text注解,并且将所有这些元素转换为ExecutableElement元素,以便获取到它们对应的信息。

可以看看元素基类的实现,完整的路径是javax.lang.model.element.Element
/**
 * Android中找不到Element类
 */

public interface Element {
    //代码省略
    //获取元素类型
    ElementKind getKind();
    
    //获取修饰符,如public 、 static 、final等
    Set<Modifier> getModifiers();

    //接受访问者的访问
    <R, P> R accept(ElementVisitor<R, P> P p);
}


/**
 * Android中找不到ElementVisitor类
 * 元素访问者
 */
public interface ElementVisitor<R, P> {

    //访问元素
    R visit(Element e, P p);

    //访问包元素
    R visitPackage(PackageElement e, P p);

    //访问一个类型元素
    R visitType(TypeElement e, P p);

    //访问一个变量类型
    R visitVariable(VariableElement e, P p);

    //访问一个可执行元素
    R visitExecutable(ExecutableElement e, P p);

    //访问一个参数元素
    R visitTypeParameter(TypeParameterElement e, P p);

    //处理未知的元素类型,这是为了应对后续java语言的扩展而预留的接口
    //例如后续元素类型增加,那么通过这个接口就可以处理上述没有声明的类型
    R visitUnknown(Element e, P p);
}

在ElementVisitor中定义了多个visit接口,每个接口处理一种元素类型,这就是典型的访问者模式。一个类元素和一个函数元素是完全不一样的,它们的结构不一样,因此,编译器对它们的操作也不同,通过访问者模式正好解决数据结构与数据操作 分离的问题,避免这些操作“污染”数据对象类。但是,与经典的访问者模式不同的是,ElementVisitor预留了一个visitUnKnown函数类应对元素结构的变化,例如如果后续在Java语言中添加了某个新的元素类型叫做NewElement,那么这个元素就会被visitUnknown函数访问,此时JDK只需要继承现有的Visitor类,然后覆写visitUnknown函数并且对NewElement进行相应的处理即可应对变化。

当Visitor对元素结构进行访问时就可以针对不同类型进行不同的处理,例如SimpleElementVisitor6就是其中一个访问者,它基本什么都不做,直接返回元素的默认值,具体代码如下:

public class SimpleEmentVisitor6<R, P> extends AbstractElementVisitor6<R, P> {

    protected final R DEFAULT_VALUE;
    protected SimpleEmentVisitor6(R defaultValue) {
        DEFAULT_VALUE = defaultValue;
    }
    protected R defaultAction(Element e, P p) {
        return DEFAULT_VALUE;
    }
    public R visitType(TypeElement e, P p) {
        return defaultAction(e, p);
    }
    public R visitExecuteable(ExecutableElement e, P p) {
        return defaultAction(e, p);
    }
    //代码省略
}

 

参考《Android源码设计模式》

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值