设计模式之四:访问者模式

yangchong211 / 文 发表于2017-11-23 18:12 次阅读 设计模式,注解,源码分析

目录介绍

  • 1.访问者模式介绍
  • 2.访问者模式定义
  • 3.访问者模式UML图
  • 4.访问者模式简单案例
  • 5.访问者模式之Android源码分析
  • 5.1 注解简单介绍
  • 5.2 注解与访问者模式关系
  • 5.3 注解与性能的关系
  • 6.访问者模式之实践
  • 6.1 介绍
  • 6.2 编译期注解之ButterKnife
  • 6.3 编译期注解之Dagger2
  • 6.4 自己写个简单支持View的ID注入的工具
  • 6.4.0 基本逻辑思路
  • 7.注解之ButterKnife源码分析
  • 7.0 简单工作流程
  • 7.1 首先看看Bind注解
  • 7.2 看看支持的类型
  • 7.3 编译时注解之ButterKnifeProcessor
  • 7.4 接下来看看findAndParseTargets(env)代码
  • 7.5 然后看看parseBind方法
  • 7.6 看看parseBind中的parseBindMany方法
  • 7.7 回到bindingClass.brewJava()方法中
  • 7.8 接下来看注解过程,bind方法
  • 7.9 看注解过程中bind中的findViewBinderForClass 方法

0.本人写的综合案例**

  • 案例
  • 说明及截图
  • 模块:新闻,音乐,视频,图片,唐诗宋词,快递,天气,记事本,阅读器等等
  • 接口:七牛,阿里云,天行,干货集中营,极速数据,追书神器等等
  • 持续更新目录说明:http://www.jianshu.com/p/53017c3fc75d

1.访问者模式介绍

  • 访问者模式,是一种将数据操作和数据结构分离的设计模式.
  • 大多数情况下,你并不需要使用访问者模式,但是当你一旦需要使用它时,那你就是真地需要它了.

2.访问者模式定义

  • 定义:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作.
  • 优点:
  • 1.各角色职责分离,符合单一职责的原则
  • 2.具有优秀的扩展性
  • 3.使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化
  • 4.灵活性
  • 缺点:
  • 1.具体元素对访问者公布细节,违反了迪米特原则
  • 2.具体元素变更时导致修改成本变大
  • 3.违反了依赖导致原则,为了达到”区别对待”而依赖了具体类,没有依赖抽象.
  • 使用场景:
  • 1.对象结构比较稳定,但经常需要在此对象结构上定义新的操作.
  • 2.需要对一个对象结构中的对象进行很多不同的并且不相关的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作

3.访问者模式UML图

  • Visitor:接口或者抽象类,它定义了对每一个元素的访问行为,它的参数就是可以访问的元素
  • ConcreteVisitor:具体访问者,对每一个元素类访问的具体行为
  • Element:元素接口或者抽象类,定义了一个accept方法,每一个月uansu都要可以被访问者访问
  • ElementA:具体元素类,它提供了接受访问的具体方法实现
  • ObjectStructure:对象结构,内部管理元素集合,并且可以迭代这些元素供访问者访问 Image.png

4.访问者模式简单案例

5.访问者模式之Android源码分析

  • 5.1 注解简单介绍
  • 注解分类:运行时注解,编译期注解 通俗的分析如下所示:
  • 1、标记一些信息,这么说可能太抽象,那么我说,你见过@Override、@SuppressWarnings等,这类注解就是用于标识,可以用作一些检验
  • 2、运行时动态处理,这个大家见得应该最多,在运行时拿到类的Class对象,然后遍历其方法、变量,判断有无注解声明,然后做一些事情。类似上述三篇博文中的做法。
  • 3、编译时动态处理,,一般这类注解会在编译的时候,根据注解标识,动态生成一些类或者生成一些xml都可以,在运行时期,这类注解是没有的会依靠动态生成的类做一些操作,因为没有反射,效率和直接调用方法没什么区别
  • 编译期注解,编译注解的核心原理依赖APT,比如ButterKnife,Dagger,Retrofit等都是基于APT的
  • 编译时,Annotation解析的原理: 原理是在某些代码元素上(如类型、函数、字段等)添加注解,在编译时编译器会检查AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理,例如,根据注解生成新的Java类,这也就是EventBus,Retrofit,Dragger等开源库的基本原理。

  • 5.2 注解与访问者模式关系

  • 5.3 注解与性能的关系
  • 只要解析出来是注解和反射,必然的一个问题就是:这样会不会影响性能呀?会有性能的损耗

6.访问者模式之实践

  • 6.1 介绍
  • 实际开发中该模式用的少,具有实战意义的是编译期注解,就是APT示例。

  • 6.2 编译期注解之ButterKnife
  • ButterKnife简单介绍
  • 编译期注解的库【基于编译时注解,然后通过APT生成辅助类,在运行时通过bind函数调用那些生成的辅助类来完成功能】。针对View,资源ID等进行注解
  • 6.3 编译期注解之Dagger2
  • Dagger2简单介绍
  • 编译期注解的库
  • 针对对象进行注解
  • 6.4 自己写个简单案例

    6.4.0 基本逻辑思路 通过ViewInject注解标识一些View成员变量; 通过ViewInjecyProcessor捕获添加了ViewInject注解的元素,并且按照宿主类进行分类; 为每个含有ViewInject注解的宿主类生成一个InjectAdapter辅助类,并且在它的inject函数中生成初始化View的代码; 在SimpleDagger的inject函数中构建生成的辅助类,此时内部会它这个InjectAdapter辅助类的inject函数,这个函数中又会初始化宿主类中的View成员变量,至此,View就已经被初始化了。

7.注解之ButterKnife源码分析

  • 7.0 简单工作流程
  • 开始它会扫描Java代码中所有的ButterKnife注解@Bind、@OnClick、@OnItemClicked等。
  • 当它发现一个类中含有任何一个注解时,ButterKnifeProcessor会帮你生成一个Java类,名字类似$$ViewBinder,这个新生成的类实现了ViewBinder接口。
  • 这个ViewBinder类中包含了所有对应的代码,比如@Bind注解对应findViewById(), @OnClick对应了view.setOnClickListener()等等。
  • 最后当Activity启动ButterKnife.bind(this)执行时,ButterKnife会去加载对应的ViewBinder类调用它们的bind()方法。
  • 7.1 首先看看Bind注解
  • @Retention保留时间,可选值SOURCE(源码时),CLASS(编译时),RUNTIME(运行时),默认为CLASS,值为SOURCE大都为MarkAnnotation,这类Annotation大都用来校验,比如Override,Deprecated,SuppressWarnings
  • @Target可以用来修饰哪些程序元素,如TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER等,未标注则表示可修饰所有

    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.RetentionPolicy.CLASS;
    //说明就是编译时动态处理的    这个值是一个枚举:有三个:SOURCE、RUNTIME、CLASS
    @Retention(CLASS)
    //标明这个注解能标识哪些东西,比如类、变量、方法、甚至是注解本身(元注解)等
    @Target(FIELD)
    public @interface Bind {
    /** View ID to which the field will be bound. */
    int[] value();
    }
  • 7.2 看看支持的类型 Image.png Image.png

  • 7.3 编译时注解之ButterKnifeProcessor
  • Annotation processing是在编译阶段执行的,它的原理就是读入Java源代码,解析注解,然后生成新的Java代码。新生成的Java代码最后被编译成Java字节码,注解解析器(Annotation Processor)不能改变读入的Java 类,比如不能加入或删除Java方法。
  • 这个是编译过程中很重要的一部分,对于butterKnife所有的注解,都是在编译时进行注解。其中ButterKnifeProcessor这个类是继承AbstractProcessor,并重写 process方法
  • 来看看Process方法中的源代码

    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    //获取所有注释的元素并且解析
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);
    //循环遍历,获取到注解中的键值
    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
        TypeElement typeElement = entry.getKey();
        BindingClass bindingClass = entry.getValue();
        try {
            //写进文件,生成辅助类
            JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
            Writer writer = jfo.openWriter();
            writer.write(bindingClass.brewJava());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
                    e.getMessage());
        }
    }
    return true;
    }
  • 7.4 接下来看看findAndParseTargets(env)代码
  • 遍历,然后比较并且解析
    private Map<TypeElement, BindingClass> findAndParseTargets(RoundEnvironment env) {
    //创建一个map集合,用来存储所有注释的元素的键值对
    Map<TypeElement, BindingClass> targetClassMap = new LinkedHashMap<TypeElement, BindingClass>();
    //创建删除删除目标名称的set集合
    Set<String> erasedTargetNames = new LinkedHashSet<String>();
    //遍历
    // Process each @Bind element.
    for (Element element : env.getElementsAnnotatedWith(Bind.class)) {
        try {
            //解析
            parseBind(element, targetClassMap, erasedTargetNames);
        } catch (Exception e) {
            logParsingError(element, Bind.class, e);
        }
    }
    }
  • 7.5 然后看看parseBind方法 Image.png
  • 重点了解一下几个方法

isInaccessibleViaGeneratedCode 验证方法修饰符不能为private和static 验证包含类型不能为非Class 验证包含类的可见性并不是private

-

isBindingInWrongPackage 它判断了这个类的包名,包名不能以android.开头 它判断了这个类的包名,包名不能以java.开头

private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
                                              String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    Set<Modifier> modifiers = element.getModifiers();
    //验证方法修饰符不能为private和static。
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) {
        error(element, "@%s %s must not be private or static. (%s.%s)",
                annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
                element.getSimpleName());
        hasError = true;
    }

    //验证包含类型不能为非Class
    if (enclosingElement.getKind() != CLASS) {
        error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
                annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
                element.getSimpleName());
        hasError = true;
    }

    //验证包含类的可见性并不是private。
    if (enclosingElement.getModifiers().contains(PRIVATE)) {
        error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
                annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(),
                element.getSimpleName());
        hasError = true;
    }
    return hasError;
}

private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
                                        Element element) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();

    //它判断了这个类的包名,包名不能以android.开头
    if (qualifiedName.startsWith(ANDROID_PREFIX)) {
        error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
                annotationClass.getSimpleName(), qualifiedName);
        return true;
    }
    //它判断了这个类的包名,包名不能以java.开头
    if (qualifiedName.startsWith(JAVA_PREFIX)) {
        error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
                annotationClass.getSimpleName(), qualifiedName);
        return true;
    }
    return false;
}
  • 7.6 看看parseBind中的parseBindMany方法
  • 如下所示

    private void parseBindMany(Element element, Map<TypeElement, BindingClass> targetClassMap,
                           Set<String> erasedTargetNames) {
    //是否有错误,默认是false
    boolean hasError = false;
    //获取被包含的元素
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    //验证的类型是一个列表List或数组array。
    TypeMirror elementType = element.asType();
    //删除类型
    String erasedType = doubleErasure(elementType);
    TypeMirror viewType = null;
    FieldCollectionViewBinding.Kind kind = null;
    //比较获取的类型是否是ARRAY类型
    if (elementType.getKind() == TypeKind.ARRAY) {
        ArrayType arrayType = (ArrayType) elementType;
        viewType = arrayType.getComponentType();
        kind = FieldCollectionViewBinding.Kind.ARRAY;
    } else if (LIST_TYPE.equals(erasedType)) {
        DeclaredType declaredType = (DeclaredType) elementType;
        List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
        if (typeArguments.size() != 1) {
            error(element, "@%s List must have a generic component. (%s.%s)",
                    Bind.class.getSimpleName(), enclosingElement.getQualifiedName(),
                    element.getSimpleName());
            hasError = true;
        } else {
            viewType = typeArguments.get(0);
        }
        kind = FieldCollectionViewBinding.Kind.LIST;
    } else {
        //如果都不是list或者array类型,那么直接抛出异常
        throw new AssertionError();
    }
    if (viewType != null && viewType.getKind() == TypeKind.TYPEVAR) {
        TypeVariable typeVariable = (TypeVariable) viewType;
        viewType = typeVariable.getUpperBound();
    }
    
    //验证目标类型是否从视图扩展.
    if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) {
        error(element, "@%s List or array type must extend from View or be an interface. (%s.%s)",
                Bind.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName());
        hasError = true;
    }
    
    //如果有错误,执行结束
    if (hasError) {
        return;
    }
    
    // 收集实地信息.
    String name = element.getSimpleName().toString();
    int[] ids = element.getAnnotation(Bind.class).value();
    if (ids.length == 0) {
        error(element, "@%s must specify at least one ID. (%s.%s)", Bind.class.getSimpleName(),
                enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
    }
    
    Integer duplicateId = findDuplicate(ids);
    if (duplicateId != null) {
        error(element, "@%s annotation contains duplicate ID %d. (%s.%s)", Bind.class.getSimpleName(),
                duplicateId, enclosingElement.getQualifiedName(), element.getSimpleName());
    }
    assert viewType != null; // Always false as hasError would have been true.
    String type = viewType.toString();
    boolean required = isRequiredBinding(element);
    BindingClass bindingClass = getOrCreateTargetClass(targetClassMap, enclosingElement);
    FieldCollectionViewBinding binding = new FieldCollectionViewBinding(name, type, kind, required);
    //将生成的BindingClass存入数组中
    bindingClass.addFieldCollection(ids, binding);
    erasedTargetNames.add(enclosingElement.toString());
    }
  • 7.7 回到bindingClass.brewJava()方法中
  • 继续回到process代码中,思考write是做什么用的呢?
  • 这个函数通过将我们绑定类的信息写入到文件外还负责创建绑定和创建解除绑定。在将绑定函数写入到文件后,整个编译器的注解方法就结束。 Image.png Image.png

  • 7.8 接下来看注解过程,bind方法
  • 如图所示 Image.png

  • 我们可以看到最终都是调用这个方法
    static void bind(Object target, Object source, ButterKnife.Finder finder) {
    //获取class
    Class<?> targetClass = target.getClass();
    try {
        if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
        //通过findViewBinderForClass生成每个类
        ButterKnife.ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
        if (viewBinder != null) {
            //如果viewBinder不为空,则进行绑定
            viewBinder.bind(finder, target, source);
        }
    } catch (Exception e) {
        throw new RuntimeException("Unable to bind views for " + targetClass.getName(), e);
    }
    }

    7.9 看注解过程中bind中的findViewBinderForClass 方法

    private static ButterKnife.ViewBinder<Object> findViewBinderForClass(Class<?> cls)
        throws IllegalAccessException, InstantiationException {
    //从内存中查找
    ButterKnife.ViewBinder<Object> viewBinder = BINDERS.get(cls);
    if (viewBinder != null) {
        if (debug) Log.d(TAG, "HIT: Cached in view binder map.");
        return viewBinder;
    }
    String clsName = cls.getName();
    //检查是否为framework class
    if (clsName.startsWith(ANDROID_PREFIX) || clsName.startsWith(JAVA_PREFIX)) {
        if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
        return NOP_VIEW_BINDER;
    }
    try {
        //实例化“MainActivity$$ViewBinder”这样的类
        Class<?> viewBindingClass = Class.forName(clsName + ButterKnifeProcessor.SUFFIX);
        //noinspection unchecked
        viewBinder = (ButterKnife.ViewBinder<Object>) viewBindingClass.newInstance();
        if (debug) Log.d(TAG, "HIT: Loaded view binder class.");
    } catch (ClassNotFoundException e) {
        if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
        //异常,则去父类查找
        viewBinder = findViewBinderForClass(cls.getSuperclass());
    }
    //放入内存并返回
    BINDERS.put(cls, viewBinder);
    return viewBinder;
    }

其他

  • 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
  • 领英:https://www.linkedin.com/in/chong-yang-049216146/
  • 简书:http://www.jianshu.com/u/b7b2c6ed9284
  • csdn:http://my.csdn.net/m0_37700275
  • 网易博客:http://yangchong211.blog.163.com/
  • 新浪博客:http://blog.sina.com.cn/786041010yc
  • github:https://github.com/yangchong211
  • 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
  • 脉脉:yc930211
  • 360图书馆:http://www.360doc.com/myfiles.aspx
  • 开源中国:https://my.oschina.net/zbj1618/blog
  • 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
  • 邮箱:yangchong211@163.com
  • 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100239.headeruserinfo.3.dT4bcV
收藏 赞 (0) 踩 (0)