Android开发Android进阶之路程序员

Android 设计模式 - 访问者模式

2019-02-28  本文已影响3人  Android架构

如何使用访问者模式

访问者模式将数据操作和数据结构分离,客户端使用用访问者对象去访问原有的数据结构,这样做的好处是在不用修改原有数据结构的前提下,通过定义不同的访问者就可以对同一个数据结构进行不同的操作.
它的uml图如下:

一个简单的应用:
公司ceo和cto对员工的绩效进行考核,目前有两种角色,一种是经理,一种工程师。ceo对经理的绩效关注kpi和产品数量,对工程师的绩效关注kpi. cto对经理的绩效关注注kpi和产品数量,对工程师的绩效关注kpi和代码数量.
这里就是一个访问者模式的使用场景

下面来看看代码怎么实现:

public interface Visitor {
    void visit(Engineer engineer);
    void visit(Manager manager);
}

public abstract class Staff {
    public String name;
    public int kpi;

    public Staff(String name) {
        this.name = name;
        kpi = new Random().nextInt(10);
    }

    public abstract void accept(Visitor visitor);
}

public class Engineer extends Staff{
    public Engineer(String name) {
        super(name);
    }

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

    public int getCodeLines(){
        return new Random().nextInt(10 * 10000);
    }
}

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;
    }
}

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());
    }
}

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());
    }
}

public class BusinessReport {
    List<Staff> mStaffs = new LinkedList<>();

    public BusinessReport(){
        mStaffs.add(new Manager("王经理"));
        mStaffs.add(new Engineer("工程师-Shawn.X"));
        mStaffs.add(new Engineer("工程师-Eric"));
        mStaffs.add(new Engineer("工程师-K"));
        mStaffs.add(new Engineer("工程师-Jay"));
    }

    public void showReport(Visitor visitor){
        for (Staff staff : mStaffs){
            staff.accept(visitor);
        }
    }
}

最后调用

public static void main(String[] args){
    BusinessReport businessReport = new BusinessReport();
    businessReport.showReport(new CTOVisitor());
    businessReport.showReport(new CEOVisitor());
}

Android源码中的访问者模式

在java中注解分为两种,一种是运行时注解,另外一种是编译期注解。运行时注解存在效率问题,所以一直被一些人诟病,编译注解的核心原理依赖APT(Annotaion Processing Tools)实现.
编译时Annotation解析的基本原理是,在某些代码元素上(如类型,函数,字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译期进行相应的处理, 例如根据注解生成新的java类,这也就是ButterKnife等开源库的基本原理.

在我们了解process这个过程之前,先来了解下Element类

PackageElement - 包元素,包含某个包下面的信息,可以获取到包名等
TypeElement - 类型元素,如某个字段属于某种类型
ExecutableElement - 可执行元素,代表了函数类型的元素

VariableElement - 变量元素
TypeParameterElement - 类型参数元素
我们常常会在注解中看到

@Target(ElementType.TYPE)

上面是在指定作用域,每一个ElementType对应下面一个类,下面一个例子

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
@interface Test{
     String value()
}

ElementType.TYPE该注解表明只能作用于函数类型,它对应的元素类型是ExecutableElement元素,当我们用APT处理这个注解时候可以将它转换为ExecutableElement元素,以便获取完整的对应信息.

public interface Element extends AnnotatedConstruct {
    TypeMirror asType();

    ElementKind getKind();

    Set<Modifier> getModifiers();

    Name getSimpleName();

    Element getEnclosingElement();

    List<? extends Element> getEnclosedElements();

    boolean equals(Object var1);

    int hashCode();

    List<? extends AnnotationMirror> getAnnotationMirrors();

    <A extends Annotation> A getAnnotation(Class<A> var1);

    <R, P> R accept(ElementVisitor<R, P> var1, P var2);
}

可以看到Element中有一个<R, P> R accept(ElementVisitor<R, P> var1, P var2); ,这里就是利用访问者模式去访问Element中的所有元素信息, 我们来看看ElementVisitor.

public interface ElementVisitor<R, P> {
    R visit(Element var1, P var2);

    R visit(Element var1);

    R visitPackage(PackageElement var1, P var2);

    R visitType(TypeElement var1, P var2);

    R visitVariable(VariableElement var1, P var2);

    R visitExecutable(ExecutableElement var1, P var2);

    R visitTypeParameter(TypeParameterElement var1, P var2);

    R visitUnknown(Element var1, P var2);
}

ElementVisitor中定义了很多visit方法,每个visit方法对应一种元素,和之前ceo和cto访问不同类型员工调用不同visit方法一样。同样我们可以联想到注解中类元素和函数元素的Element是不一样的,访问者模式正好使得Element元素独立(不需要提供复杂的访问成员变量方法)。另外里面visitUnknown方法是为了扩展新的Element调用的。
下面来看看两个ElementVisitor的实现类,SimpleElementVisitor6

public class SimpleElementVisitor6<R, P> extends AbstractElementVisitor6<R, P> {
    protected final R DEFAULT_VALUE;

    protected SimpleElementVisitor6() {
        this.DEFAULT_VALUE = null;
    }

    protected SimpleElementVisitor6(R var1) {
        this.DEFAULT_VALUE = var1;
    }

    protected R defaultAction(Element var1, P var2) {
        return this.DEFAULT_VALUE;
    }

    public R visitPackage(PackageElement var1, P var2) {
        return this.defaultAction(var1, var2);
    }

    public R visitType(TypeElement var1, P var2) {
        return this.defaultAction(var1, var2);
    }

    public R visitVariable(VariableElement var1, P var2) {
        return var1.getKind() != ElementKind.RESOURCE_VARIABLE?this.defaultAction(var1, var2):this.visitUnknown(var1, var2);
    }

    public R visitExecutable(ExecutableElement var1, P var2) {
        return this.defaultAction(var1, var2);
    }

    public R visitTypeParameter(TypeParameterElement var1, P var2) {
        return this.defaultAction(var1, var2);
    }
}

它基本没做什么操作,直接访问了元素的默认值.
另外一个元素是ElementKindVisitor6

public class ElementKindVisitor6<R, P> extends SimpleElementVisitor6<R, P> {
public R visitType(TypeElement var1, P var2) {
        ElementKind var3 = var1.getKind();
        switch(null.$SwitchMap$javax$lang$model$element$ElementKind[var3.ordinal()]) {
        case ANNOTATION_TYPE:
            return this.visitTypeAsAnnotationType(var1, var2);
        case CLASS:
            return this.visitTypeAsClass(var1, var2);
        case ENUM:
            return this.visitTypeAsEnum(var1, var2);
        case INTERFACE:
            return this.visitTypeAsInterface(var1, var2);
        default:
            throw new AssertionError("Bad kind " + var3 + " for TypeElement" + var1);
        }
    }
}

ElementKindVisitor6对于不同的类型进行不同的处理。

总结:在现实情况下,我们根据具体的情况来评估是否适合使用访问者模式,例如,我们的对象结构是否稳定,使用访问者模式是否优化我们的代码,而不是使得代码变得更加复杂.

访问者模式的优点:

(1)各角色职责分离,符合单一职责原则
(2)具有优秀的扩展性
(3)使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
(4)灵活性

访问者模式的缺点:

(1)具体元素对访问者公布细节,违反了迪米特原则
(2)具体元素变更时导致修改成本大
(3)违反了依赖倒置原则,为了达到区别对待而依赖了具体类,没有依赖抽象
【附录】

资料图

需要资料的朋友可以加入Android架构交流QQ群聊:513088520

点击链接加入群聊【Android移动架构总群】:加入群聊

获取免费学习视频,学习大纲另外还有像高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)等Android高阶开发资料免费分享。

上一篇下一篇

猜你喜欢

热点阅读