美文网首页Android开发Android进阶之路程序员
Android 设计模式 - 访问者模式

Android 设计模式 - 访问者模式

作者: Android架构 | 来源:发表于2019-02-28 21:28 被阅读3次

    如何使用访问者模式

    访问者模式将数据操作和数据结构分离,客户端使用用访问者对象去访问原有的数据结构,这样做的好处是在不用修改原有数据结构的前提下,通过定义不同的访问者就可以对同一个数据结构进行不同的操作.
    它的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高阶开发资料免费分享。

    相关文章

      网友评论

        本文标题:Android 设计模式 - 访问者模式

        本文链接:https://www.haomeiwen.com/subject/lyhluqtx.html