目录

设计模式之路

灵活之路 - 面向对象六大原则

如果下面文字描述,不是很明白那么跳转到后面链接有详细代码说明原则解析

  • 单一职责原则SRP(Single Responsibility Principle)
    • 定义: 就一个类而言, 应该仅有一个引起它的变化的原因. 通俗点就是一个类应该是相关性很高数据封装
    • 举例: 现在有一个图片加载类. 但是这个类内部包含了图片下载的逻辑,图片缓存的逻辑这样就使得这个类的职责过多, 随着功能的不断完善, 这个类的代码和逻辑也变得纵横交错混合在了一起. 对于后续的修改维护扩展都是不利的. 所以让两个类组合起来, 一个类内部只负责图片下载,另一个类内部负责图片缓存. 保持每个类的单一职责
  • 开闭原则OCP(Open Close Principle)
    • 定义: 软件中的对象应该对于扩展开放的 但是对于修改封闭的. 通俗点 : 尽量通过扩展的方式来实现变化, 而不是通过修改已有的代码来实现.
    • 举例: 此时我们实现了一个双缓存类单缓存类. 在图片加载类中进行这两个缓存类的实例. 并对外暴露一个布尔值让用户设置是否使用双缓存来决定内部缓存的逻辑. ok. 目前看可能没有问题. 但是如果有一个更好的缓存算法类, 这时候每次都需要在图片加载类中修改代码. 这就违反了OCP原则, 利用继承,接口的特性可以让此类问题得以解决. 比如: 我们可以定义一个缓存接口, 在加载类中使用的个这个接口中的方法. 而这个接口的具体实现通过暴露一个方法让外部调用的时候传入, 以后如果有新的缓存类只需要调用方法传入接口的子类就可以. 这样就对于原始代码修改进行了关闭, 而对于扩展是开放的.
  • 里氏替换原则LSP(Liskov Substitution Principle)
    • 定义: 所有引用基类的地方必须能透明地使用其子类. 通俗点:是基于继承,多态两大特性. 再简单点抽象
    • 举例: Window#show(View view)这个方法接收一个View, 但是我们可以Button,TextView等等. 其实很简单. 我们常用只不过不知道这个名字而已. 所以LSP的原则的核心就是抽象. 抽象又依赖于继承这个特性. 通常开闭原则和里氏替换是不离不弃的例如上面OCP中举得例子. 在外部调用就是利用了继承的特性, 也就是里氏替换
  • 依赖倒置原则DIP(Dependence Inversion Principle)
    • 定义: 指代了一种特定的解耦形式, 使得高层次的模块不依赖于低层次的模块的实现细节的目的, 依赖模块被颠倒了. 通俗点: 在Java中依赖抽象(接口,抽象类), 而不依赖具体实现类. 模块之间的依赖通过抽象发生, 实现类之间不发生直接的依赖关系, 其依赖关系是通过接口或抽象类产生.
    • 举例: 还是在OCP中的例子, 内部加载类依赖于也就是成员变量是缓存接口, 而不是具体的某一个单缓存或者双缓存的实现类.
  • 接口隔离原则ISP(Interface Segregation Principles)
    • 定义: 接口的依赖关系应该建立在最小的接口上. 通俗点:接口隔离原则的目的是系统解开耦合, 从而容易重构, 更改和重新部署.
    • 举例: 在操作一些IO文件,网络的时候我们总是伴随着try...catch...finally. 在最终调用块中调用close()确保资源可以正确的释放. 但这样这样的代码不仅可读性差可以每次都是写一些冗余的模板代码. 其实可以提供一个静态方法, 而根据java中的的特性,之上操作的对象都会实现一个标识接口Closeable,这个接口标识了一个可关闭的对象有一个close(). 所以这个静态方法的形参接收一个Closeable接口,并在方法内调用close()即可. 仔细想想: 这个方法的形参在调用的时候传入的实参是里氏替换原则, 而方法内部调用的是一个接口的close()方法,但传入的可能是某一个实现类,那么这不就是依赖导致原则,并且建立在最小化的依赖基础上, 只要知道这个对象是可关闭的, 别的一概不关心, 这就是接口隔离原则.
  • 迪米特原则LOD(Law of Demeter)
    • 定义: 一个对象应该对其他对象有最少的了解. 通俗点: 一个类应该对自己需要耦合或调用的类知道的最少, 类的内部如果实现与调用者或者依赖者没有关系, 调用者或者依赖者只需要知道他需要的方法即可, 其他一概不管.
    • 举例: 房间类, 中介类, 上班族类. 可以上班族应该只关心中介类, 而不需要关注房间类. 只需要中介类返回房子的地址即可. 而不需要通过调用中介类返回一个房间类 . 这也就是代码中需要注意的. 不要过度耦合, 要降低类之间的关系.

启航之路 - UML类图说明

对于许多类组成的庞大关系网, 最好的办法是通过图来表示出其关系. 可以直观的看出组合的元素, 元素直接是如何存在的, 元素与哪些元素直接存在着联系等. 表示出来的图就是UML类图.

可以看如下一个稍微完整的一个UML类图

UML_ALL.png


组成元素

  • 类和接口: 通过黄色的矩形框来表示一个类, 例如上面鸟就是一个普通类, 如果类名是斜体那么就是抽象类, 如果和飞翔或者唐老鸭的表示法那么就是接口.
  • 访问权限: 通过+ 公共权限, - 私有权限, # 保护权限
  • 变量和方法: 分别在第二行, 和第三行表示,抽象方法同样斜体表示, 静态属性的用下划线表示.

关系结构

  • 继承关系: 类与类之间的关系, 通过空心三角+实线表示, 通过箭头的方向指向父类表述关系.
  • 实现关系: 类与接口直接的关系, 通过空心三角+虚线表示, 通过箭头的方向指向接口表述关系.
  • 关联关系: 当一个类知道另一个类的时候,可以使用关联, 比如企鹅和气候两个类中, 企鹅类的变量有气候类的引用, 这个时候就如上图之间的关系. 实线箭头表示, 箭头指向被知道的类
  • 依赖关系: 例如动物是依赖氧气和水的, 就如动物类中的方法形参类型依赖这两个类型. 如上图动物和水之间关系. 使用虚线箭头, 箭头指向被依赖的类
  • 聚合关系: 表示一种弱拥用, A可以包含B, 但B不可以包含A. 如大雁和雁群两个类. 雁群类中会有一个数组,数组的元素是大雁类型. 这之间就是聚合. 使用空心菱形+实线箭头
  • 合成关系: 也可以认为是组合. 是一种强拥有关系. 例如鸟类和翅膀类, 鸟类是整体, 翅膀类是部分. 并且其生命周期相同, 对应着就是在鸟类初始化的时候,翅膀类也会随之初始化. 并且, 上图中的鸟到翅膀还有1..2的字样. 这称为基数. 表明一段会有几个实例, 例如一个鸟会有两个翅膀. 如果一个类有无数个实例那就用n表示. 关联关系,聚合关系也是可以有基数的. 使用实心菱形+实线箭头表示.

编程是门技术, 更加是一门艺术, 不能只满足代码结果运行正确就完事, 时常考虑如果让代码更加简练, 更加容易维护, 更易扩展和复用, 这样才可以真正提高.

发现之路 - 23种设计模式

单例模式 Singleton

模式介绍

  • 定义: 确保某个类只有一个实例, 而且自行实例化并向整个系统提供这个实例.
  • 场景: 确保一个类只会有一个对象实例, 避免产生多个对象消耗过多的资源, 或者某种类型的对象只应该有且只有一个. 如创建一个对象需要消耗的资源过多, 访问IO和数据库等资源时就可以考虑单例.

模式范例

单例模式的实现有5种.


知识扩展

枚举实现法最大的优点就是实现简单, 但是在android却比较消耗内存. 有一点与其他单例模式不同的是: 默认枚举实例的创建是线程安全的. 为什么? 因为其他的单例在一种特定的场合下会重新创建对象,那就是反序列化.

反序列化是从磁盘读回数据并创建一个新的对象. 即使构造函数是私有的, 反序列化依然可以通过特殊的途径去创建一个实例, 相当于调用了构造函数. 反序列化提供了一个很特别的钩子函数, 类中具有一个私有的, 被实例化的方法readResolver(), 这个方法可以让开发人员控制对象的反序列化. 例如上面的几个单例模式, 如果想杜绝单例对象在被反序列化时重新生成对象, 那么必须加入如下方法:

private Object readResolve() throws ObjectStreamException(){
    return sInstent;        // 返回单例中的实例对象
}

这样在反序列化的时候就不是默认的重新生成一个新对象. 而对于枚举,并不存在这个问题. 因为即使反序列化它也不会重新生成新的实例.

Android源码对应模式

我们经常会在Activity中通过getSystemService(String name)这个函数来获取系统的服务, 比如说WMS,AMS,LayoutInflater等等. 这些服务都会在某一时刻以容器单例的形式保存在应用中.

Adapter#getView()中使用布局加载器LayoutInflate.from(context).inflate(layoutId,null)为例

会调用ContextImpl#getSystemService(String)方法获取服务, 而方法内部只是从一个SYSTEM_SERVICE_MAP名字的集合中获取了一个ServiceFetcher对象, 并从其中获取具体的服务返回.

那么我们可以缕一下应用的启动, 并定位到何时保存的这些服务到这个集合的.

  1. 首先应用的入口为ActivityThread#main(),在这个函数里面会创建ActivityThread对象, 并启动消息循环(UI)线程, 调用attach(boolean)函数
  2. attach(boolean)中通过Binder机制与ActivityManagerService通信, 最终回调本类的handlelaunchActivity()函数.
  3. 然后执行PerformLaunchActivity()函数, 开始创建Application,Context,Activity, 并把上下文关联到Activity中, 最终调用Activity#onCreate()

ok刚才大概流程是这样的, 通过之前的分析我们知道, 各个系统服务是保存在ContextImpl类中的, 这个类是在上面的第3步中被初始化的. 看如下代码, 就是服务被注册的代码, 时机也就是第一个Context被创建的时候.

class ContextImpl extends Context {
    // 存储所有系统服务的集合
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =new HashMap<String, ServiceFetcher>();
    
    // 一个注册服务的并添加到结合的方法
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
       if (!(fetcher instanceof StaticServiceFetcher)) {
           fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
       }
       SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }
    
    // 静态语句块, 只在类第一次被加载的时候调用, 保证了服务只被添加一次.
    static {
        // 注册了LayoutInflate服务
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});
                
        registerService(INPUT_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    return InputManager.getInstance();
                }});

        /**
         *  后面省略一大坨的注册的服务代码
        **/
    }
    
}

建造者模式 Builder

模式介绍

一个复杂的对象有很多组成成分, 如汽车, 车轮, 方向盘, 发动机,等等. 为了在构建过程中对外部隐藏实现细节, 就可以使用Builder模式将部件和组装过程分离, 使得构建过程和部件都可以自由扩展, 两者之间的耦合也将到了最低.

  • 定义: 将一个复杂对象的构建与它的表示分离, 使得同样的构建过程可以创建不同的表示.
  • 场景:
    1. 当初始化一个队形特别复杂, 参数特别多, 且有很多参数都具有默认值时.
    2. 相同的方法, 不同的执行顺序, 产生不同的事件结果时
    3. 多个部件或零件, 都可以装配到一个对象中, 但是产生的运行结果又不相同.

模式范例

范例代码

范例的UML类图

UML_Builder.png

上例中通过具体MacbookBuilder类构建Macbook对象, 而Director封装了构建复杂产品对象的过程, 对外隐藏了构建的细节. BuilderDirector一起将一个复杂对象的构建与它的表示分离, 是的同样的构建过程可以创建不同的对象.

可能你会觉得唉? 怎么和我见过的Builder模式不一样呢? ,这是因为Director这个角色经常会被忽略. 而直接使用一个Builder来进行对象的封装, 并且这个Builder通常为链式调用, 它的每个setter方法都会返回this自身, 比如我们常用的AlertDialog. 下节介绍.

Android源码模式实现

在Android中最经典的Builder实现就是AlertDialog. 看一下开发中的使用:

// 一个粗略的创建dialog
// 创建构建者builder角色
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.sym_def_app_icon)
      .setTitle("标题")
      .setMessage("message")
      // 设置点击等..
      .setPositiveButton("确定", null);

// 构建
AlertDialog alertDialog = builder.create();

// 显示
alertDialog.show();

从类名就可以看出这是一个Builder模式, 通过Builder对象来组装Dialog的各个部分. 将Dialog的构造和表示进行了分离.

接下来看一下AlertDialog的源码:

public class AlertDialog extends Dialog implements DialogInterface {
    // AlertController 这个对象会保存Builder对象中的各个参数
    private AlertController mAlert;
    
    // 实际上操作的是上面这个变量中的属性
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }
    
    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }
    // 省略一坨代码如各种setter等
    // Builder以内部类的形式存在
    public static class Builder {
        // 1.存储AlertDialog的各个参数 如title,icon等
        private final AlertController.AlertParams P;
        
        // 构造函数
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }

        // 2. 设置参数, 我们构建的Builder设置的参数就是这些方法
        public Builder setTitle(int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }
        
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }
        
        // ....
        
        // 3.构建AlertDialog, 传递参数
        public AlertDialog create() {
            // 4.因为已经通过builder设置了参数, 接下来就可以创建真正需要的AlertDialog对象
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
            
            // 5.将Builder类中的成员变量P应用到AlertDialog类中
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }      
    }   
}

对, 最后还调用了AlertDialog#show()函数, 这个函数主要做了如下几件事情:

  1. 通过dispatchOnCreate()函数来调用AlertDialog#onCreate()函数
  2. 然后调用AlertDialog#onStart()函数
  3. 最后将DialogDecorView添加到WindowManager中.

那么在看一下onCreate()函数的源码及后续调用.

// AlertDialog类
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mAlert.installContent();
}

// AlertController类
public void installContent() {
   // 设置窗口, 没有title类型
   mWindow.requestFeature(Window.FEATURE_NO_TITLE);
   int contentView = selectContentView();
   // 设置窗口的内容视图
   mWindow.setContentView(contentView);
   // 初始化AlertDialog其他子视图的内容
   setupView();
   setupDecor();
}

这部分比较重要, 通过Window#setContentView()和Activity是一样的过程, 设置了内容布局, 通过AlertController的构造函数可以发现加载布局资源就是com.android.internal.R.layout.alert_dialog这个文件, 之前的Builder中的各种setter方法就是把设置的内容传入到这个布局当中.


可以看到Android源码中的AlertDialog并没有遵循GOF设计模式中经典的实现方式, 而是进行了变种, 但却使其使用更加的方便. 这里AlertDialog.Builder这个类同时扮演了范例中的builder,具体实现builder,Director的角色. 简化了Builder设计模式, 因为模块比较稳定不会存在变化, 根据具体场景简化模式, 正是体现了灵活运用设计模式的实例.

实战场景

就如Picasso,Glide等链式的调用, 你可以通过链式设置很多配置属性, 也可以仅调用两三此传入必要参数即可. 是的调用实现更加灵活.

原型模式 Prototype

模式介绍

创建性模式, 从一个样板对象中复制出一个内部属性一致的对象, 其实就是克隆. 而被复制的对象就叫做原型, 多用于创建复杂的或者构造耗时的实例

  • 定义: 用原型实例指定创建对象的种类, 并通过拷贝这些原型创建新的对象.
  • 场景:
    1. 类初始化需要消耗非常多的资源, 这个资源包括数据,硬件资源等, 可通过原型拷贝避免这些消耗
    2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限, 同样可以使用原型模式
    3. 一个对象需要提供给其他对象访问, 并且会能会对其修改属性, 可以用原型拷贝多个对象提供使用

其实这个模式很简单, 就是利用Object#clone()方法可以复制一份提供使用(clone是一个native方法). 但是需要注意, 通过实现Cloneable接口的原型模式在调用clone函数构造并不一定就比通过new方式的快, 只有当通过new构造对象较为耗时或者说成本较高时, 通过clone方法才能获得效率提升.

UML类图

UML_Prototype.png

模式范例

这里模式实现很简单, 实现也比较少, 这里就贴出代码

public class WordDocument implements Cloneable{

    // 文本
    public String mText;

    // 图片名列表
    public ArrayList<String> mImages = new ArrayList<String>();

    public WordDocument(){
        System.out.println("-----------WordDocument构造函数-----------");
    }

    @Override
    protected WordDocument clone() {
        try {
            // 通过本地方法特殊途径, 构建一个对象
            WordDocument doc = (WordDocument) super.clone();
            doc.mText = this.mText;

            // 因为Image是引用类型, 这样直接赋值属于浅拷贝, 再次对集合进行clone. 实现wordDocument的深拷贝
            doc.mImages = (ArrayList<String>) this.mImages.clone();
            return doc;
        }catch (Exception ex){}

        return null;
    }

    /**
     * 打印文档内容
     */
    public void showDocument(){
        System.out.println("------------开始输出内容---------------------");
        System.out.println("Text: "+mText);
        System.out.println("List: "+mImages.toString());
        System.out.println("------------输出结束------------------------");
    }
}

与标准的原型模式相比WordDocument就是一个具体实现的原型对象. 而实现的Cloneable接口为抽象的原型对象.

其实Cloneable这个接口内部没有任何方法, 所以其本质就是标识接口,只是表明这个类的对象是可拷贝的, 而clone()这个方法是Objec类中的, 如果没有标识这个接口, 那么调用会抛出异常.


深拷贝浅拷贝

例如上面的代码中进行修改一下

@Override
protected WordDocument clone() {
   try {
       // 通过本地方法特殊途径, 构建一个对象
       WordDocument doc = (WordDocument) super.clone();
       doc.mText = this.mText;

       // 这里进行修改 那么此时属于浅拷贝
       doc.mImages = this.mImages;
       return doc;
}

你可能应该发现了什么, 其实本质不过就是通过super.clone()构建了一个本类对象的初始状态, 然后把被拷贝的对象的各个属性值进行赋值操作而已.

的确, 就是如此. 就如上面两处不同的代码,

  • 浅拷贝: 也称影子拷贝, 拷贝出来的对象并不是完全一份独立的对象, 新的对象某些属性如引用传递可能会引用原始对象的对应属性值, 也就是说, 对浅拷贝的属性可能会影响到原始数据的属性.
  • 深拷贝: 拷贝出一份原始对象, 并对原始对象的属性值, 进行复制添加到新拷贝的对象的各个属性上. 这样拷贝出来的对象与原始对象不存在任何关联, 只作为一个数据的副本存在.

上面因为mImages的类型是ArrayList如果直接进行赋值那么属于引用传递, 共享的一份数据源, 而如果在对ArrayList进行一次clone, 那么相当于又构建了一个集合并进行数据的复制.

mText虽然是对象, 但是因为是String类型, 属于安全类型, 由于final类,实例不可更改的特性. 如果对副本进行字符串的修改, 只不过是把原引用删除,重新指向了新的字符串.

Android源码对应实现

上面我们说了通过对集合再次调用clone()即可完成深拷贝. 那么看一下ArrayList源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    transient Object[] elementData; 
    private int size;
    
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

ArrayList的内部克隆实现很简单, 我们都知道ArrayList内部是通过数组的数据结构来实现的. 通过Arrays工具类对原始集合的数据进行赋值并添加到一个新的数组并返回, 而返回的数组挂到了克隆出来对象上的elementData变量上.

而集合的大小size没有被进行赋值? 因为其类型是整型, 属于值传递, 在clone之后原始值通过值传递到了新对象中, 即使修改也不会对原始对象有任何的影响.


那么Android源码中的实现是什么?

Intent, 我们看如下代码

Intent intent = new Intent("某一个activity的action");
intent.putExtra("result", "Successful");

// 调用克隆方法
Intent clone = (Intent) intent.clone();
startActivity(clone);

这样同样没问题, 一样的效果. 那么看一下Intent#clone()内部是如何实现的.

@Override
public Object clone() {
   return new Intent(this);
}

/**
* Copy constructor.
*/
public Intent(Intent o) {
   this.mAction = o.mAction;
   this.mData = o.mData;
   this.mType = o.mType;
   this.mPackage = o.mPackage;
   this.mComponent = o.mComponent;
   this.mFlags = o.mFlags;
   this.mContentUserHint = o.mContentUserHint;
   if (o.mCategories != null) {
       this.mCategories = new ArraySet<String>(o.mCategories);
   }
   if (o.mExtras != null) {
       this.mExtras = new Bundle(o.mExtras);
   }
   if (o.mSourceBounds != null) {
       this.mSourceBounds = new Rect(o.mSourceBounds);
   }
   if (o.mSelector != null) {
       this.mSelector = new Intent(o.mSelector);
   }
   if (o.mClipData != null) {
       this.mClipData = new ClipData(o.mClipData);
   }
}

很简单不需要解释了, 手动new的并进行数据复制. 相当于封装了一下复制的细节而已.

但是为什么没有调用super.clone()来实现拷贝呢? 之前说过使用clone还是new关键字是需要根据构造对象的成本来决定的, 如果对象的构造成本比较复杂或者麻烦, 那么clone则是一种更优的选择, 否则就可以使用new的形式. 这和c++拷贝构造函数是一样的.

实战场景

当登录模块登录成功之后, 会把一些个人信息,token等信息在保存类中的某个数据结构上, 并通过一个方法对外暴露出去, 提供其他模块使用. 但是如果你返回的是一个数据结构也就是一个对象, 这个对象包含了很多个人信息, 但是正常来说, 对于外部应该只提供查看数据的能力, 不应该提供修改的能力.

所以这个使用, 就可以对登录模块对外暴露的方法进行修改, 利用原型模式对外返回的是一个内部数据的深拷贝, 这样就把可能出现的隐患彻底的隔绝了.

说明

原型模式是通过内存中二进制流的方式拷贝, 要比直接通过new一个对象性能更好, 特别是循环体内产生大量对象是. 但是注意, 因为是二进制流的拷贝, 所以构造函数是不会执行的. 这点要明确记牢.

工厂方法模式 Factory

模式介绍

创建型设计模式, 其实这个模式可能在开发中出现很多回了, 只是并不了解什么是工厂模式的概念.

  • 定义: 定义一个用于创建的对象的接口, 让子类决定实例化哪个类
  • 场景: 在任何需要生成复杂对象的地方, 都可以使用工厂方法模式. 复杂对象适合使用工厂模式, 用new就可以完成创建的对象无需使用工厂模式.

工厂方法模式完全符合设计模式原则, 降低了对象之间的耦合度, 而且, 工厂方法模式依赖于抽象的架构, 将实例化的任务交由了子类实现.

模式范例

实现代码

这是范例的UML类图.

UML_Factory.png

其实这里, 可以去掉抽象的工厂类, 只需要一个工厂即可. 这样会更加简洁直观.

Android源码对应实现

ListSet不陌生, 都继承Collection接口, 而Collection接口继承Iterable接口, 而这个接口很简单就一个iterator()方法, 如下

public interface Collection<E> extends Iterable<E> {
    // ....
}

public interface Iterable<T> {
    Iterator<T> iterator();
    
    // 可能JDK1.8之后添加两个默认方法, 这里我们不需要关心
}

关于ListSet迭代器的方法遍历元素应该都用过. 那么看一下源码实现.

public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {

@Override public Iterator<E> iterator() {
        return new ArrayListIterator();
    }

    private class ArrayListIterator implements Iterator<E> {
        private int remaining = size;
        
        private int removalIndex = -1;

        private int expectedModCount = modCount;

        public boolean hasNext() {
            return remaining != 0;
        }

        @SuppressWarnings("unchecked") public E next() {
            ArrayList<E> ourList = ArrayList.this;
            // 返回集合大小元素, 还有几个未遍历
            int rem = remaining;
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }

        public void remove() {
            Object[] a = array;
            int removalIdx = removalIndex;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (removalIdx < 0) {
                throw new IllegalStateException();
            }
            System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
            a[--size] = null;  // Prevent memory leak
            removalIndex = -1;
            expectedModCount = ++modCount;
        }
    }

}
// hashSet 复写逻辑
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable,Serializable {
    public Iterator<E> iterator() {
        return backingMap.keySet().iterator();
    }
}
// HashMap 复写逻辑
public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable {
    Iterator<K> newKeyIterator() { return new KeyIterator();   }
    
    private final class KeyIterator extends HashIterator
            implements Iterator<K> {
        public K next() { return nextEntry().key; }
    }

}
  • HashSetiterator方法会返回成员变量backingMap中对应HashSet对象元素的迭代器对象, 最终返回的是KeySet中的一个迭代器对象
  • ArrayListHashMap中的iterator()就相当一个工厂方法, 专为new对象而生!

Android中, 看一下如下代码

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new FrameLayout(this));   
    }
}

通过onCreate()这个方法, 我们可以构建出任何样式的根布局, 如LinearLayout,TextView等等. 我们在不同的Activity#onCreate()方法将设置的布局通过setContentView()函数传递给frameworks并显示出来. 这不就是一个工厂模式的结构. 方法内部可以创建不同的对象, 产生不同的实例.

实战场景

例如对数据的持久化, 可以通过的途径有SP,File,SQLite等. 但是对数据的操作无非就是增删改查, 那么我们可以抽象一个抽象类并定义CURD抽象方法. SP, File,SQLite分别继承抽象类, 并在抽象方法实现自己的处理逻辑. 然后就可以创建一个工厂类, 工厂类有一个方法, 形参为产品实现类的字节码, 返回一个泛型上限限定是产品的抽象类对象, 方法内部通过字节码反射具体的产品类实例.

这样在使用的使用, 我们只需有通过工厂方法传入的不同产品Class就可以构建不同的实例, 而数据的CRUD通过依赖倒置抽象特性, 高层不需要依赖底层的类.

抽象工厂模式 Abstract Factory

模式介绍

创建型设计模式, 之前工厂模式会生产某一个产品, 但是如果说, 不同的操作系统图形的场景下的两个产品按钮文本框. 对于每一个操作系统, 其本身就构成了一个单独的产品. 两种产品两种变化, 这种情况就较之前的普通工厂升级了复杂度, 如: Android中的ButtonTextView, iOS中的ButtonTextView或者WindowPhone场景…

  • 定义: 为创建一组相关或者是相互依赖的的对象提供一个接口, 而不需要指定他们的具体类
  • 场景: 一个对象族有相同的约束时可以使用抽象工厂, 如androidiOS都有打电话软件和短信软件, 两者都属于软件的范畴, 但是他们的操作平台不同, 实现逻辑也不会相同. 这个时候就可以使用抽象工厂方法模式

模式范例

实现代码

范例UML图

UML_AbsFactory.png

看一下运行结果:

test_absFactory.png

如果这是时候, 如果想创建一种使用普通轮胎, 新款发动机的车型. 只需要继承抽象工厂, 并使用原有的普通轮胎类, 并继承IEngfine实现一款新的发动机类. 即可完成扩展. 这就是通过接口扩展.

上面的范例, 对于每一个造车的工厂, 内部使用的零件不管哪个车场都是具有抽象的轮胎和发送机类. 这样可以达到一种自由组合的状态.

但是弊端也显示出来了, 不仅需要扩展新的工厂类还要扩展新的组件类.

Android源码对应实现

抽象工厂在Android实现较少, 上一节说onCreate()方法就相当于一个工厂方法. 那么对于另外一个组件Service#onBind()同样也可以看做一个工厂方法.

如果从frameworks层的角度来看ActivityService可以看做一个具体的工厂, 这样来看相当于一个抽象方法模式的雏形也没错.

另一个更像的例子是Android底层对MediaPlayer使用. 这里书上噼里啪啦一堆C语言. 我就不抄了….

策略模式 Strategy

模式介绍

开发中可能有这样的情况: 实现某一个功能可以有多中算法或者策略, 我们根据不同的功能来选择不同的算法. 针对这种情况, 1.可以在一个类中封装多个方法, 每个方法实现不同算法. 2.通过if..else if..else..条件判断来决定使用哪种算法. 但是这两种都是硬编码实现. 并且随着算法的增多类也就变得臃肿, 维护的成本随之变高. 如果需要增加一种新的算法, 必然需要对算法类进行修改. 这就违反了OCP原则和单一职责的原则.

  • 定义: 策略模式定义了一系列的算法, 并将每一个算法封装起来, 而且使它们还可以相互替换. 策略模式让算法独立于使用它的客户而独立变化.
  • 场景:
    • 针对同一类型问题的多种处理方式, 仅仅是具体行为有差别时
    • 需要安全地封装多种同一类型的操作时
    • 出现同一抽象类有多个子类, 而又不需要使用if-else或者switch等来选择具体子类.

模式范例

最方便的记忆法就是记住, 策略模式可以去掉if-else或者switch. 语句, 即使后续会扩展通过接口来进行扩展, 不会对源代码进行修改. 满足了OCP开闭原则.

看一下范例代码: –> 对于交通费用的计算, 计算的算法可能会有公交, 地铁等…

代码地址

范例类图:

UML_Strategy.png

在看一下代码的使用以及结果–>

public static void main (String arg[]){
   // 创建操作策略的环境类
   TranficCalculator calculator = new TranficCalculator();
   // 设置公交车的策略, 并准备计算
   calculator.setStrategy(new BusStrategy());
   System.out.println("公交车-->计算9公里价格: "+calculator.calculatePrice(9));

   // 设置地铁的策略, 并准备计算
   calculator.setStrategy(new SubwayStrategy());
   System.out.println("地铁-->计算9公里价格: "+calculator.calculatePrice(9));
}

// 结果-->
公交车-->计算9公里价格: 1
地铁-->计算9公里价格: 4

你应该可以发现, 这种方式在隐藏实现的同时, 可扩展性变得很强, 如果此时需要增加一个出租车的计算策略, 那么只需要添加一个实现了计算策略接口即可. 对原始代码的修改进行了关闭, 并对扩展开放.

Android源码对应实现

动画里面的插值器Interpolator利用了策略模式, 利用Interpolator策略的抽象, LinearInterpolator,CycleInterpolator等插值器为具体的实现策略, 通过注入不同的插值器实现不同的动态效果.

看一下大概的类图

UML_Animation.png

  • 动画中的TimeInterpolator时间插值器, 它的作用是根据时间流逝的百分比计算出当前属性值改变的百分比, 内置的插值器有如下几种
    • 线性插值器(LinearInterpolator)用于匀速动画
    • 加速减速插值器(AccelerateDecelerateInterpolator):起始时动画加速, 结尾时动画减速
    • 减速插值器(DecelerateInterpolator): 用于随着时间的推移动画越来越慢.
  • 动画中的TypeEvalutor类型估值器: 根据当前属性改变的百分比来计算改变后的属性值. 内置的类型估值器有如下几种
    • 整型估值器(IntEvalutor)
    • 浮点型估值器(FloatEvalutor)
    • Color估值器(ArgbEvalutor)

接下来就开始回忆一下从一个动画开始后, 代码究竟做了什么?

对于源码的起始点入口就是调用View的startAnimation()

public void startAnimation(Animation animation) {
    // 1.初始化动画的开始时间
   animation.setStartTime(Animation.START_ON_FIRST_FRAME);
   // 2.对View设置动画
   setAnimation(animation);
   // 3.刷新父类缓存
   invalidateParentCaches();
   // 4.刷新View本身及子View
   invalidate(true);
}

这里首先设置了动画的起始时间, 然后将该动画设置到View中, 最后再向ViewGroup请求刷新视图, 随后ViewGroup会调用dispatchDraw()方法对这个View所在的区域进行重绘. 其实对于某一个View的重绘最终是调用其ViewGroupdrawChild(...)方法. 跟入一下

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   // 简单的转发
   return child.draw(canvas, this, drawingTime);
}

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    // ....
    // 查看是否需要清除动画信息
    final int flags = parent.mGroupFlags;
    // 省略无关代码
    
    // 获取设置的动画信息
    final Animation a = getAnimation();
        
    if (a != null) {
            // 绘制动画
           more = drawAnimation(parent, drawingTime, a, scalingRequired);
           //...
       } 
}

父类会调用子类的draw方法, 其中会先判断是否设置了清除动画的标记, 然后再获取该View动画信息, 如果设置了动画, 就会调用View#drawAnimation()方法.

private boolean drawAnimation(ViewGroup parent, long drawingTime,
       Animation a, boolean scalingRequired) {
   Transformation invalidationTransform;
   final int flags = parent.mGroupFlags;
   final boolean initialized = a.isInitialized();
   // 1. 判断动画是否已经初始化过
   if (!initialized) {
       a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
       a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
       if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
       // 如果设置了动画的监听, 则触发对应的回调
       onAnimationStart();
   }
   // 获取Transformation对象, 存储动画的信息
   final Transformation t = parent.getChildTransformation();
   // 2. 调用Animation#getTransformation, 通过计算获取动画的相关值
   boolean more = a.getTransformation(drawingTime, t, 1f);
    

   if (more) {
        // 3. 根据具体实现, 判断当前动画类型是否需要进行调整位置大小, 然后刷新不同的区域
       if (!a.willChangeBounds()) {
           // ...
       } else {
           // 获取重绘区域
           a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                   invalidationTransform);
           parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

            // 更新计算有效区域
           final int left = mLeft + (int) region.left;
           final int top = mTop + (int) region.top;
           
           // 进行区域更新
           parent.invalidate(left, top, left + (int) (region.width() + .5f),
                   top + (int) (region.height() + .5f));
       }
   }
   return more;
}

drawAnimation中主要操作是动画的初始化, 动画操作, 界面刷新. 动画的回调监听onStart()会在动画进行初始化的时候调用, 动画的具体实现是通过Animation#getTransformation()方法.这个方法主要获取了缩放系数和调用Animation.getTransformation(long, Transformation)来计算和应用动画效果.

public boolean getTransformation(long currentTime, Transformation outTransformation) {
   //...
   float normalizedTime;
   // 1.计算当前时间的流逝百分比
   if (duration != 0) {
       normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
               (float) duration;
   } else {
       // time is a step-change with a zero duration
       normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
   }
   // 动画是否完成标记
   final boolean expired = normalizedTime >= 1.0f;
   mMore = !expired;

   if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        // 2.通过插值器获取动画执行百分比  , 这里获取的方法就是通过策略模式
       final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
       // 3.应用动画效果
       applyTransformation(interpolatedTime, outTransformation);
   }

   // 4. 如果动画执行完毕, 那么触发动画完成的回调或者执行重复动画等操作
   // ...
   if (!mMore && mOneMoreTime) {
       mOneMoreTime = false;
       return true;
   }
   return mMore;
}

这段代码, 先计算已经流逝的的时间百分比, 然后再通过具体的插值器重新计算这个百分比, 也就是上面的第二步. 而具体是哪一个插值器是通过之前说的策略模式来实现的.

第3步调用了applyTransformation, 这个方法在基类Animation中是空实现, 可以在子类查看实现如ScaleAnimation,AlphaAnimation等查看. 当这个方法内部主要通过矩阵来实现动画. 当这个方法执行完毕之后, View的属性也就发生了变化, 不断地重复这个过程, 动画就随之产生.

实战场景

当我们自己组装了一个队列请求, 对于这个队列的处理方式默认可能是先处理进入队列的, 但是如果想实现一个可以先处理后入队的, 和随机读取队列元素. 那么为了以后的扩展不影响源代码, 那么可以通过策略模式在代码中通过对策略抽象面向接口,抽象编程. 是具体的实现有后续的传入的子类来决定.

状态模式 State

模式介绍

状态模式中的行为是由状态来决定的, 不同的状态有不同的行为, 状态模式和策略模式的结构几乎一模一样, 但他们的目的, 本质却完全不一样. 状态模式的行为是平行的不可替换的. 策略模式的行为是彼此独立, 可相互替换的. 总结一句话表述: 状态模式是把对象的行为包装在不同的状态对象里, 每一个状态对象都有一个共同的抽象状态基类, 状态模式的意图是让一个对象在其内部状态改变的时候, 其行为也随之改变

  • 定义: 当一个对象的内在状态改变时允许改变其行为, 这个对象看起来像是改变了其类.
  • 场景:
    1. 一个代码的行为取决于它的装填, 并且必须在运行时根据其状态改变它的行为
    2. 代码中包含大量与对象状态有关的条件语句, 同样可以去除分支语句的效果

模式范例

例如电视开关机状态下的频道切换或者音量调节, 不同的状态下的各种功能行为是不同的. 关机: 功能音量频道切换是无效的, 开机: 却可以实现. 实现这样一个关系, 如果最简单暴力的方法就是一个类实现, 里面充斥了各种条件判断来实现不同场景的功能.

范例代码实现

范例类图:

UML_State.png

客户端的实现:

public static void main(String arg[]){
        TvController tvController = new TvController();

        // 开机
        tvController.powerOn();

        // 下一个频道
        tvController.nextChannel();

        // 调高音量
        tvController.turnUp();

        // 关机
        tvController.powerOff();

        // 关机状态下调低音量
        tvController.turnDown();
}

/** 输出结果-->
 *  开机了--
 *  下一个频道
 *  调高音量
 *  关机了--
**/

其实有多重行为, 但代码中却不存在了条件分支语句

Android源码对相应实现

WiFi管理 其中的实现就使用了状态模式

WiFi复杂的调用中, 存在一个State的状态类, 它代表了WiFi的某个状态, 定义如下:

public class State implements IState {
    // 进入当前状态之后调用该函数
    @Override
    public void enter() {
    }
    
    // 退出该状态后改用该函数
    @Override
    public void exit() {
    }  
    
    // 处理消息
    @Override
    public boolean processMessage(Message msg) {
        return false;
    }      
}

状态之间并不是可以随意切换的, 他们有一种层级关系, 这些层级关系StateMachine的构造函数中被定义的, 代码如下:

// WiFiStateMachine
public WifiStateMachine(Context context, String wlanInterface,
            WifiTrafficPoller trafficPoller){
        super("WifiStateMachine");
        
        addState(mDefaultState);
        addState(mInitialState, mDefaultState);
        addState(mSupplicantStartingState, mDefaultState);
        addState(mSupplicantStartedState, mDefaultState);
        addState(mDriverStartingState, mSupplicantStartedState);
        addState(mDriverStartedState, mSupplicantStartedState);
        addState(mScanModeState, mDriverStartedState);
        addState(mConnectModeState, mDriverStartedState);
        addState(mL2ConnectedState, mConnectModeState);
        addState(mObtainingIpState, mL2ConnectedState);
        addState(mVerifyingLinkState, mL2ConnectedState);
        addState(mConnectedState, mL2ConnectedState);
        addState(mRoamingState, mL2ConnectedState);
        addState(mDisconnectingState, mConnectModeState);
        addState(mDisconnectedState, mConnectModeState);
        addState(mWpsRunningState, mConnectModeState);
        addState(mWaitForP2pDisableState, mSupplicantStartedState);
        addState(mDriverStoppingState, mSupplicantStartedState);
        addState(mDriverStoppedState, mSupplicantStartedState);
        addState(mSupplicantStoppingState, mDefaultState);
        addState(mSoftApStartingState, mDefaultState);
        addState(mSoftApStartedState, mDefaultState);
        addState(mTetheringState, mSoftApStartedState);
        addState(mTetheredState, mSoftApStartedState);
        addState(mUntetheringState, mSoftApStartedState);
        // 初始化模式为mInitialState
        setInitialState(mInitialState);
}

在构造函数中调用了addState()函数, 这些函数最终会调用SmHandler#addState()函数. 这个函数就是在状态之间建立一个层级关系, 这是一个树形的层级关系. 状态之间并不是跨越式的转换, 当前状态只能转换到上一个状态或者下一个状态.

上面说的比较抽象, 列举书中的例子. 一个电梯的状态有停止, 运行, 开门, 关门. 在运行状态只能到停止状态. 不会直接开门状态,这会出人命的, 关门状态也是不合乎常理的. 所以就如下关系图片:

elevator_sequence.png

正如上图, 不同状态对于不同的指令的反应是完全不一样的, WiFi工作状态机制也是同理, 除了对状态之间的转换进行控制之外, 还通过状态模式来对不同的命令进行不同的处理. State类就是状态的基类, 它与Wifi相关的子类都定义在WifiStateMachine中.

State的类有enter,exit,processMessage三个函数, 进入状态之后会调用enter(), 退出时调用exit(), 处理具体消息时调用processMessage(). 而状态模式的核心就是当一个对象的内在状态改变时允许改变其行为, 所以我们关注processMessage()不同的状态下就是依赖这个函数实现不同行为的.

例如: 在请求扫描Wifi时, 如果在初始化状态(InitialState)下, 说明Wifi驱动还没有进行加载和启动, 扫描的请求会被会被忽略. 而在驱动加载状态下, 请求会被添加到延迟处理的消息队列中, 等待驱动加载完毕进行扫描请求.

总结起来: 就是将请求的处理封装到状态类中, 在不同的状态类中对同一个请求进行不同的处理. 它能够消除一些重复的if-else逻辑, 使得程序的结构更加清晰, 可扩展性和稳定性也有了一定的提高

实战场景

例如新浪微博首页, 任何状态下可以看微博, 当点击转发后, 如果是登录状态那么就可以直接调转转发页面, 如果是未登录状态那么需要调转到登录界面. 这就可以使用状态模式进行逻辑的分离.

责任链模式

模式介绍

行为型设计模式, 将每一个对象看做一个节点, 并把所有节点串成一条链式, 从链头开始传递事件, 如果无法处理交给节点的下一个节点位置上, 直到有节点处理了这个事件.

  • 定义: 使多个对象都有机会处理请求, 从而避免了请求的发送者和接收者之间的耦合关系. 将这些对象连成一条链, 并沿着这条链传递该请求, 直到对象处理它为止
  • 场景: 多个对象都可以处理一个请求时, 但具体由哪个对象处理是在运行时决定.

模式范例

出差是需要经费的, 那么肯定需要找领导签字才会批下钱, 但是如果经费较多, 你的上一级可能无权签字,这个时候上一级领导就会把这个审批带向他的上级提交.. 直到有可以批准的为止. 从始至终出差人只需要知道自己的上一级即可. 不需要知道其他的审批人.

范例代码

范例类图

UML_Iterator.png

看一下客户端的调用–>

public static void main(String args[]){

        // 构造3个处理者对象
        Handler1 handler1 = new Handler1();
        Handler2 handler2 = new Handler2();
        Handler3 handler3 = new Handler3();

        // 构造3个请求者对象
        Request1 re1 = new Request1("请求1");
        Request2 re2 = new Request2("请求2");
        Request3 re3 = new Request3("请求3");

        // 设置当前处理者对象下一个节点的处理者对象
        handler1.nextHandler = handler2;
        handler2.nextHandler = handler3;

        // 准备开始请求
        // 总是从链式的首端发起请求
        handler1.handleRequest(re1);
        handler1.handleRequest(re2);
        handler1.handleRequest(re3);
}


// 执行结果===================> 
处理者 1 处理请求, 请求的等级为: 1
处理者 2 处理请求, 请求的等级为: 2
处理者 3 处理请求, 请求的等级为: 3

对于每个处理者其内部的逻辑是完全灵活的, 比如可以进行跳级传递等…

Android源码对应实现

责任链模式在Android中比较类似的就是事件的分发处理, 每当用户接触屏幕时, Android就会将对应的事件包装成一个事件对象从ViewTree的顶部之上而下地分发传递.

ViewGroup事件投递的递归调用就类似一条责任链, 一旦其寻到责任者, 那么就由责任者持有并消费掉该次事件 具体的体现在View#onTouchEvent()方法返回值的设置, 如果返回false, 那么就意味着当前View不会是该次事件的责任人, 将不会对其持有, 如果返回true, 则相反, 此时View会持有该事件并不在向外传递.

解释器模式 Interpreter

模式介绍

这是较少使用的行为型模式, 其提供了一种解释语言的语法或表达式的方式, 该模式定义了一个表达式接口, 通过该接口解释一个特定的上下文.

  • 定义: 给定一个语言, 定义它的文法的一种表示, 并定义一个解释器, 该解释器使用该表示来解释语言中的句子.
    • 文法? 如他很高,他很胖,他很瘦. 这三个语句可以看做一个他很[形容词]这样的结构, 可以看做是一条文法
  • 场景:
    1. 如果某个简单的语言需要解释执行而且可以将该语言中的语句表示为一个抽象语法树时可以考虑使用解释器模式
    2. 在某些特定的领域出现不断重复的问题时, 可以将该领域的问题转化为一种语法规则下的语句, 然后构建解释器来解释该语句.

模式范例

不好理解看看是通过代码形式的表示是否可以清楚一些?

比如一个场景是算术表达式的解释, 如m + n + p, 如果使用解释器模式对该表达式进行解释, 那么代表数字的mnp3个字母我们可以看成是终结符号, 而+这个算术运算符则可以当做非终结符号.

代码范例

如最终调用方式:

public static void main(String arg[]){
   Calculator calculator = new Calculator("12 + 11 + 13 + 14");
   System.out.println(calculator.calculate());
}

// 结果如下:
50

这个例子只是先了对加减法的解释计算, 如果要实现更多的运算规则, 乘除取余, 只需要创建对应解释器即可, 但是混合运算的复杂是要考虑各种符号的优先级的问题,这个就比较麻烦.

将一个具体的文法通过一个解释器解释, 把复杂的文法规则分离为简单的功能进行解释, 最后将其组合成一颗抽象的语法树解释执行, 至此, 可以看到解释器模式的原理和本质: 将复杂的问题简单化, 模块化, 分离实现, 解释执行

Android源码对应实现

Android源码中的解释器模式并不多见, 虽然没有经典实现, 但是可以在一些地方看到对解释器模式原理的应用. AndroidManifest.xml这个清单文件

整理一下大体过程. 关于读取配置文件, 那么就需要一个很重要的类PackageParser. 该类对AndroidManifest.xml中每一个组件标签创建了对应的类, 用于存储相应的消息.

PackageParserActivity,Service,Provider,Permission等构件在其内部以内部类的方式创建了对应的类, 按照解释器模式的定义, 这些类其实都对应AndroidManifest.xml中的一个标签, 也就是一条文法, 其在对该配置文件解析时充分运用了解释器模式分离实现, 解释器执行的特性.

对一个APK文件的解析会调用PackageManagerService#scanPackageLI()方法, 这个方法有两种实现

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user);
 
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) 

两者的唯一区别是第一个参数, 第一种实现为File第二种为PackageParser.Package. 在具体解析某个文件时会先调用第一种实现解析apk文件, 在调用第二种实现将解析后的信息保存至PMS中. 而这两种方法中同样会调用一个函数名相同但参数不同的函数. ParserPackage(...). 对于参数1为File类型的其中主要逻辑就是为了第二种参数为Resources实现准备好需要的参数, 然后可以调用第二种ParserPackage(Resource ...).

ParserPackage的第二种实现逻辑比较复杂, 内部主要对整个AndroidManifest.xml配置文件的每个子节点进行具体的解析.

例如parseApplication方法的会对application节点进行解析, 对于不同的子标签会调用不同的解析方法来对其内部进行解析. 如碰到了activity标签, 那么会调用parseActivity()进行内部解析. 而parseActivity()不仅承担着对Activity的解析, 其同样承担着Broadcast的解析. 并会继续调用方法对内部标签进行解析如parseIntentparseMetaData等.

命令模式 Command

模式介绍

行为型设计模式, 如当我们点击关机键的时候, 系统就会执行一系列的操作, 保存程序的进度, 结束程序, 调用内核命令关机. 用户不关心命令做了什么, 只需要点击关机即可达到效果.

  • 定义: 将一个请求封装成一个对象, 从而让用户使用不同的请求把客户端参数化; 对请求排队或者记录请求日志, 以及支持可撤销操作.
  • 场景:
    • 需要抽象出待执行的操作, 然后以参数的形式提供出来– 类似于过程设计中的回调机制, 而命令模式正式回调机制的一个面向对象的替代品
    • 在不同的时刻指定, 排列和执行请求. 一个命令对象可以有与初始请求无关的生存期
    • 需要支持取消操作
    • 支持修改日志的功能, 这样当系统崩溃的时候, 这些修改可以重做一遍
    • 需要支持事务的操作

模式范例

把俄罗斯方块的大体逻辑模拟成代码, 向左,向右,变形,加速下落这四个按钮相当于请求者, 执行具体按钮命令的逻辑方法可以看做是命令角色.

范例代码

范例类图

UML_Command.png

代码测试

public static void main(String arg[]){

   // 创建游戏
   TetrisMachine machine = new TetrisMachine();

   // 根据游戏构造四个命令
   LeftCommand leftCommand = new LeftCommand(machine);
   RightCommand rightCommand = new RightCommand(machine);
   FastCommand fastCommand = new FastCommand(machine);
   TransformCommand transformCommand = new TransformCommand(machine);

   // 按钮可以执行不同的命令
   Buttons buttons = new Buttons();
   buttons.setmLeftCom(leftCommand);
   buttons.setmRightCom(rightCommand);
   buttons.setmFastCom(fastCommand);
   buttons.setmTransformCom(transformCommand);

   // 具体按下那个按钮玩家说的算
   buttons.toLeft();
   buttons.toRight();
   buttons.fast();
   buttons.transform();
}

其实调用逻辑做的很复杂, 完全可以直接创建TetrisMachine类直接调用的. 这样做的主要原因是后续开发方便, 比如如果需要增加或修改游戏功能只需要修改TetrisMachine类就可以. 然后修改一下Player类. 但是事物是相对的对开发者方便了, 但是如果别人负责了这个项目看到这个功能可能会花更多时间去理解,反而简单的事情没有很直接的表达.

除此之外, 使用命令模式的另一个好处是可以实现命令记录的功能, 如上面代码中, 如果要Button请求者角色中使用一个数据结构来存储执行过的命令对象, 以此可以很方便地知道刚刚执行过哪些命令动作, 并可以在需要时恢复

Android源码对应实现

Android中关于命令模式的使用虽然不少, 但都不是典型, 很多方面的应用与其他大多数设计模式一样都有一定的变种, 一个比较经典的例子是Android的事件机制中底层逻辑对事件的转发处理, Android的每一种事件在屏幕上产生后都会经过底层逻辑将其封装转换为一个NotifiArgs对象.

实战场景

很好的一个场景就是, 对画板模块的使用, 可以很方便的实现重画,撤销等功能.

观察者模式 Observer

模式介绍

一个使用率非常高的模式, 常用的地方GUI系统, 订阅–发布系统. 最明显的特点就是解耦, 将被观察者和观察者进行解耦, 使得依赖性更小.

  • 定义: 定义对象间一种一对多的依赖关系,使得每当一个对象改变状态, 则所有依赖于它的对象都会得到通知并被自动更新.
  • 场景:
    • 关联行为场景, 需要注意的是, 关联行为是可拆分的, 而不是组合的关系
    • 事件多级触发场景
    • 跨系统的消息交换场景, 如消息队列,事件总线的处理机制

模式范例

例如一个简单的订阅, 订阅者可以在被观察者更新的时候收到通知.

范例源码

类图就不需要了, 因为JDK已经内置了此模式的实现, 看一下范例的调用方式和结果

public  static void main(String arg[]){
   // 创建被观察对象
   DecTechFrontier decTechFrontier = new DecTechFrontier();

   // 创建几个观察者
   Coder co1 = new Coder("张飞");
   Coder co2 = new Coder("李逵");
   Coder co3 = new Coder("关羽");
   Coder co4 = new Coder("孙悟空");

   // 将观察者注册到被观察的对象
   decTechFrontier.addObserver(co1);
   decTechFrontier.addObserver(co2);
   decTechFrontier.addObserver(co4);
   decTechFrontier.addObserver(co3);

   // 发布消息
   decTechFrontier.postNewPublication("葵花宝典");
}
// =====> 输出结果
你好, 关羽, 你订阅的东西有更新了: 葵花宝典 
你好, 孙悟空, 你订阅的东西有更新了: 葵花宝典 
你好, 李逵, 你订阅的东西有更新了: 葵花宝典 
你好, 张飞, 你订阅的东西有更新了: 葵花宝典 

ObserverObservableJDK中的内置类型, 可见观察者模式是非常重要的, 这里Observer是抽象观察者角色, 范例Coder类扮演的是具体观察者角色; Observable对应的是抽象主题角色, 范例DecTechFrontier是具体的主题角色. 主题角色通过setChange()标识主题发生改变,并通过notifyObservable()通知所有的观察者角色. 而观察者都过复写update()方法来实现主题更新时需要做的事情 . 至此这两个角色并没有耦合.

Android源码对应实现

ListView中的Adapter#notifyDataSetChange()就是通过观察者模式实现的子View的更新.

首先是notifyDataSetChange()方法为入口. 这个方法定义在BaseAdapter中.

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    // 数据集观察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    
    /**
     * 数据发生改变是, 调用所有观察者
    **/
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

}

很明显的BaseAdapter是一个观察者模式, 那么接着看一下如何运作, 以及这个观察者是什么.

public class DataSetObservable extends Observable<DataSetObserver> {
    // 调用每个观察者的onChange函数来通知他们被观察者发生了改变
    public void notifyChanged() {
        synchronized(mObservers) {
            // 调用所有的观察者onChange()
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
}

可以看到我们调用的notifyDataSetChanged()会遍历所有的观察者中的onChange().

这些观察者就是在ListView通过setAdapter()方法设置Adapter产生的.

@Override
public void setAdapter(ListAdapter adapter) {
   // 如果已经有了一个Adapter, 那么先注销该Adapter对应的观察者
   if (mAdapter != null && mDataSetObserver != null) {
           mAdapter.unregisterDataSetObserver(mDataSetObserver);
       }
   super.setAdapter(adapter);

   if (mAdapter != null) {
       mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
       mOldItemCount = mItemCount;
       // 获取数据的数量
       mItemCount = mAdapter.getCount();
       checkFocus();
       // *** 创建一个数据集观察者
       mDataSetObserver = new AdapterDataSetObserver();
       // 将这个观察者注册到Adapter中, 实际上注册到了 DataSetObservable中
       mAdapter.registerDataSetObserver(mDataSetObserver);

   }
   
   requestLayout();
}

可以看出, 在设置Adapter时会构建一个AdapterDataSetObserver, 这就是之前说的观察者, 最后将这个观察者注册到Adapter

那么AdapterDataSetObserver是什么? 是如何运作的? 首先这个这个类定义在了ListView的父类AbsListView中, 而这个类又继承了AbsListView的父类AdapterView的AdapterDataSetObserver.如下

class AdapterDataSetObserver extends DataSetObserver {

   private Parcelable mInstanceState = null;

   // 核心方法
   @Override
   public void onChanged() {
       mDataChanged = true;
       mOldItemCount = mItemCount;
       // 获取adapter的数量
       mItemCount = getAdapter().getCount();

       if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
               && mOldItemCount == 0 && mItemCount > 0) {
           AdapterView.this.onRestoreInstanceState(mInstanceState);
           mInstanceState = null;
       } else {
           rememberSyncState();
       }
       checkFocus();
       // 重新布局
       requestLayout();
   }
}

这回应该很清楚了, 当ListView的数据发生变化时, 调用了Adapter#notifyDataSetChanged()函数, 这个函数又会调用DataSetObserver#notifyChange()函数, 这个函数会遍历所有的观察者AdapterDataSetObserver#onChange()onChange()方法中又会调用ListView重新布局, 使得ListView刷新界面

实战场景

事件总线!

备忘录模式 Memento

模式介绍

一种行为模式, 该模式用于保存对象, 并且在之后可以再次恢复到此状态

  • 定义: 在不破坏封闭的前提下, 捕获一个对象的内部状态,并在该对象之外保存这个状态, 以后就可将该对象恢复到原先保存的状态.
  • 场景:
    1. 需要保存一个对象在某一个时刻的状态或部分状态
    2. 如果用一个接口来让其他对象得到这些状态, 将会暴露对象的实现细节并破坏对象的封装性, 一个对象不希望外界直接访问其内部状态, 通过中间对象可以间接访问其内部状态.

模式范例

比如一个游戏, 在退出时候保存进度, 在进入的时候恢复进度的场景

范例源码

范例类图

UML_Memento.png

范例的使用–>

public static void main(String arg[]){

   // 构建游戏对象
   CallOfDuty game = new CallOfDuty();

   // 1 打游戏
   game.play();

   Caretaker caretaker = new Caretaker();
   // 2 游戏存档
   caretaker.archive(game.createMemo());

   // 3 退出游戏
   game.quit();

   // 4 恢复游戏
   CallOfDuty newGame = new CallOfDuty();
   newGame.restore(caretaker.getMemo());
}

// =======> 运行结果
玩游戏: 第1关 奋战杀敌中
进度升级中
到达 第2关
-----
退出前的游戏属性: 当前游戏信息: checkpoint=2 ,mLifeValue=90 ,mWeapon=沙漠之鹰
退出游戏
-----
恢复后的游戏属性--> 当前游戏信息: checkpoint=2 ,mLifeValue=90 ,mWeapon=沙漠之鹰

可以看到CallOfDuty在这里为Originator角色, 也就是需要存储的对象, 在这里并没有直接存储对象, 而是通过MemoCallOfDuty对象的数据进行存储, 然后在存储Memo对象, 最终对Memo的存储操作交给Caretaker对象. 在这个过程中, 各个角色职责清晰, 单一, 即对外屏蔽了对CallOfDuty角色的直接访问, 在满足了对象状态存取功能的同时也使得该模块的结构清晰, 整洁.

Android源码对应实现

Android源码中的状态模式应用是Activity中的状态保存.

在这里, Activity扮演了Caretaker角色, 负责存储和恢复UI的状态信息; Activity,Fragment,View,ViewGroup等对象为Originator角色, 也就是需要存储状态的角色. Memo则由Bundle类扮演.

迭代器模式 Iterator

模式介绍

也成为游标模式, 行为性设计模式. 源于对容器的访问.

  • 定义: 提供了一种方法顺序访问一个容器对象中的各个元素, 而不需要暴露该对象的内部表示
  • 场景: 遍历一个容器对象

模式实现

场景: 如两个部门, 老板想要对两个部门的统计数据, 但是如果两个部门的内部实现存储如果是一个用数组, 一个集合, 那么老板访问就需要了解其内部的数据结构. 使得老板的职责过多, 这个时候如果用迭代器模式实现,统一遍历方式, 那么就会很方便,也不会对外暴露内部的实现细节. 如下:

范例代码

UML_Cursor.png

使用情况:

public static void main(String args[]){
   CompanSu companSu = new CompanSu();
   check(companSu.iterator());

   CompanLi companLi = new CompanLi();
   check(companLi.iterator());
}

private static void check(Iterator iterator) {
   while (iterator.hasNext()){
       System.out.println(iterator.next().toString());
   }
}

// ======> 结果
Employee{name='小敏', age=99, sex='男', position='程序员'}
Employee{name='小李', age=98, sex='男', position='程序员'}
Employee{name='小往', age=11, sex='女', position='程序员'}
Employee{name='小爱', age=9, sex='女', position='程序员'}
Employee{name='大敏', age=66, sex='妖', position='未知'}
Employee{name='大李', age=66, sex='妖', position='未知'}

这个例子只是列举个思想, 可以看到通过迭代器实现, 就可以对外通过一个统一的接口, 来对不同的内部细节不一样的容器进行访问. 这也是List,Map都实现迭代器的意义.

Android源码对应实现

几乎开发者不会自己去实现一个迭代器, 例如Android中, 除了各种数据结构体, 最典型的就是数据库查询使用了Cursor. 当使用SQLiteDatabase#query()方法查询数据时, 会返回一个Cursor对象. 该对象实质就是一个迭代器.

所以可以看出迭代器模式, 特点很明显也很单一, 支持以不同的方式去遍历一个容器对象, 也可以有多个遍历, 弱化了容器与遍历算法之间的关系. 几乎每一种高级语言都有对应的内置迭代器实现.

模板模式 Template

模式介绍

某一个算法所需要的关键步骤是已知的, 但是某一步的具体实现是未知的需要子类去实现

  • 定义: 定义一个操作中的算法框架, 而将一些步骤延迟到子类中, 使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤.
  • 场景:
    • 多个子类有共有的方法, 并且逻辑基本相同
    • 重要, 复杂的算法, 可以把核心算法设计为模板方法, 周边的相关细节由子类去实现
    • 重构时, 模板方法模式是一个经常使用的模式, 把相同的代码抽取到父类, 然后通过钩子函数约束其行为

模式范例

模板方式实际上是封装一个固定流程, 然后暴露某一个步骤方法, 这里以计算机开机为例子,

范例代码

UML_Template.png

代码使用:

public static void main(String arg[]){

   CodeComputer codeComputer = new CodeComputer();
   codeComputer.startUp();

   MilitaryComputer militaryComputer = new MilitaryComputer();
   militaryComputer.startUp();
}

// ========> 运行结果
--------- 开机 start -----------
开启电源
硬件检测
载入操作系统
需要密码
---------- 关机 end ---------------
--------- 开机 start -----------
开启电源
硬件检测
>> 需要检测防火墙
载入操作系统
需要进行眼膜验证
---------- 关机 end ---------------

上面代码不管什么情况四个步骤是必须的, 开启电源是刚需,不需要子类实现, startUp()设置方法是final因为调用流程是必须的. 而其余的方法根据不同的需求来进行改造.

Android源码对应实现

AndroidAsyncTask就是一个比较明显的模板方法模式.

其内部调用顺序就是 execute–>onPreExecute–>doInBackground–>onPostExecute

或者比如Activity的声明周期方法.

访问者模式 Visitor

模式介绍

访问者模式是一种将数据操作与数据结构分离的设计模式,它是23种设计模式最复杂的一个, 但使用率不高. 大体思想, 软件系统中拥有一个由许多对象构成的, 比较稳定的对象结构, 这些对象的类都拥有一个accept方法用来接收访问者对象的访问. 访问者是一个接口, 他拥有visit方法, 这个方法对访问到的对象结构中不同的类型元素做出不同的处理.

  • 定义: 封装一些作用于某种数据结构中的各个元素的操作, 它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作
  • 场景:
    • 对象结构比较稳定, 但经常需要在此对象结构上定义新的操作
    • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作, 而需要避免这些操作污染这些对象的类, 也不希望在增加新操作时修改这些类

模式范例

场景: 公司的年度考核, 评定员工分别是CEOCTO, 而CEO只关注工程师的代码量和经理的新产品数; CTO关注的是工程师的KPI和经理的KPI. 这时CEOCTO对于不同员工的关注点是不一样的. 这就需要对不同的员工类型进行不同的处理. 访问者模式此时可以派上用场了.

范例代码

代码使用结果:

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看的报表----------
CEO访问--> 经理王经理 . KPI : 9 , 新产品数量: 0
CEO访问--> 工程师工程师-jake . KPI : 2
CEO访问--> 工程师工程师-小李 . KPI : 5
CEO访问--> 工程师工程师-小张 . KPI : 0
----------给CTO看的报表----------
CTO访问--> 经理 王经理 , 新产品数量: 0
CTO访问--> 工程师 工程师-jake . 代码行数 : 14290
CTO访问--> 工程师 工程师-小李 . 代码行数 : 2183
CTO访问--> 工程师 工程师-小张 . 代码行数 : 83422

范例中Staff扮演了Element角色, 而EnginnerManager都是ConcreteElement; CEOVisitorCTOVistor都是具体的Vistor对象, 而BusinessReport就是ObjectStructure; Client就是客户端

访问者最大的优点就是增加访问者非常容易, 如果要增加一个访问者, 只需要创建一个实现了Visitor接口的类, 然后实现两个visi函数来对不同的元素进行不同的操作, 从而达到数据对象与数据操作相分离的效果.

Android源码对应实现

APT的注解. 简单记录一下. 首先编译器将代码抽象成一个代码元素的树, 然后在编译时对整棵树进行遍历访问, 每个元素都有一个accept()接收访问者的访问, 每个访问者中都有对应的visit()函数, 例如visitType()函数就是对类型元素的访问, 在每个visit函数中对不同的类型进行不同的处理, 这样就达到了差异处理效果, 同时将数据结构与数据操作分离, 使得每个类型的职责单一, 易于升级维护. JDK还特意预留了visitUnknown()接口应对Java语言后续发展可能添加的元素类型问题, 灵活的将访问者模式的缺点化解.

中介者模式 Mediator

模式介绍

也称为调节者模式或者调停者模式

  • 定义: 包装了一系列对象互相作用的方式, 使得这些对象不必互相明显作用. 从而使他们可以松散耦合. 当某些对象之间的作用发生改变时, 不会立即影响其他的一些对象之间的作用. 保证这些作用可以彼此独立的变化. 中介者模式将多对多的关系转化为一对多的相互作用. 中介者模式将对象的行为和协作抽象化, 把对象在小尺度的行为上与其他对象的相互作用分开处理.
  • 场景: 当对象之间的交互操作很多且每个对象的行为都依赖彼此时, 为防止在修改一个对象的行为会涉及修改很多其他对象的行为, 可采用中介者模式, 来解决紧耦合问题. 该模式将对象之间的多对多关系变成了一对多关系, 中介者对象将系统从网状结构变成以调停者为中心的星形结构, 达到降低系统的复杂性, 提高可扩展的作用.

模式范例

场景: 以电脑为例, 电脑主要部分为:CPU, 内存, 显卡, IO设备. 通常需要一个东西把这些组件连接起来共同工作,这就是主板的工作. 任何的两块模块之间的通信都会经过主板去协调. 这里以读取光盘为例.看主板是如何充当这个中介者角色的.

范例代码

UML_Mediator.png

代码使用

public static void main(String arg[]){

   // 构造主板对象
   MainBoard mainBoard = new MainBoard();

   // 构造各个零件同事
   CDDevice cdDevice = new CDDevice(mainBoard);
   CPU cpu = new CPU(mainBoard);
   GraphicsCard graphicsCard = new GraphicsCard(mainBoard);
   SoundCard soundCard = new SoundCard(mainBoard);

   // 将各个部件安装到主板
   mainBoard.setCdDevice(cdDevice);
   mainBoard.setCpu(cpu);
   mainBoard.setGraphicsCard(graphicsCard);
   mainBoard.setSoundCard(soundCard);

   // 完成后开始放片
   cdDevice.load();
}

从图片可以看出, 虽然彼此间会互相交互, 但是通过中介者模式, 会让一个网状的关系, 转成一个以中介者为中心的星状图.

Android源码对应实现

中介者模式在Android源码中比较好的例子是Keyguard锁屏的实现.

public class KeyguardViewMediator extends SystemUI {
    private AlarmManager mAlarmManager;
    private AudioManager mAudioManager;
    private StatusBarManager mStatusBarManager;
    private boolean mSwitchingUser;

    private boolean mSystemReady;
    private boolean mBootCompleted;
    private boolean mBootSendUserPresent;
    // ....
}

可以看到类中存在很多XXManager的变量, 这些各种各样的管理器就是各个具体的实现类, Android使用KeyguardViewMediator充当这个中介者协调这些管理器的状态改变, 同样也会定义很多方法来处理这些管理器的状态, 以解锁或锁屏时声音的播放为例, 对应的方法playSounds()来协调音频这一状态.

而其他管理器的协调同样可以在此类找到.


而另一个中介者模式的例子就是Binder机制, 在Binder机制中有3个非常重要的组件ServiceManager,Binder DriverBp Binder. 其中Bp BinderBinder的一个代理角色, 其提供了IBinder接口给各个客户端服务使用, 这三者就扮演了一个中介者角色

当手机启动后, ServiceManager会先向Binder Driver进行注册, 同样ServiceManager也是一个服务, 但特殊性在于, 它在Binder Driver中是最先被注册的, 其注册ID为0, 当其他的服务想要注册到Binder Driver时, 会先通过这个0号ID获取到ServiceManager所对应的IBinder接口, 该接口实质上的实现逻辑是由Bp Binder实现的, 获取到对应的接口后就回调其中的transact()方法, 此后就会在Binder Driver中注册一个ID 1来对应这个服务, 如果客户端想要使用这个服务, 那么他会先获取ID 0的接口, 也就是ServiceManager所对应的接口, 并调用其transact()要求连接到刚才的服务, 这个时候Binder Driver就会将ID 1的服务回传给客户端并将相关信息反馈给ServiceManager完成连接. 这里ServiceMangerBinder Driver就相当于一个中介者, 协调各个服务器和客户端.

代理模式 Proxy

模式介绍

也称委托模式, 结构性设计模式. 生活中也是有很多常见的代理例子, 代理上网, 叫外卖, 通过律师打官司都是一种代理

  • 定义: 为其他对象提供一种代理以控制对这个对象的访问
  • 场景: 当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问, 为了保证客户端使用的透明性, 委托对象与代理对象需要实现相同的接口.

模式范例

场景: 公司拖欠工资, 员工通过律师来间接的和公司要钱.

范例源码

UML_Proxy.png

使用时代码

public static void main(String args[]){
   // 构造一个起诉者
   ConcreteLawsuit concreteLawsuit = new ConcreteLawsuit();

   // 构造一个律师, 被代理者
   Lawyer lawyer = new Lawyer(concreteLawsuit);

   // 律师代理
   lawyer.submit();
   lawyer.burden();
   lawyer.defend();
   lawyer.finish();
}

代理模式大致分为两个部分, 一个是静态代理,还有一个是动态代理.

  • 静态代理如上述示例那样, 代理者的代码由程序员自己或者通过自动化工具生成固定的代码再对其进行编译, 也就是说在我们的代码运行前代理类class编译文件就已经存在
  • 动态代理则与静态代理相反, 通过反射机制动态生成代理者对象, 也就是说我们在code阶段压根就不需要知道代理谁, 代理谁将会在执行阶段决定, 而Java也给我们提供了一个便捷的动态代理接口InvocationHandler, 并复写invoke()

动态代理最终的调用方式:

// 构造一个动态代理
DynamicProxy dynamicProxy = new DynamicProxy(concreteLawsuit);

// 获取被代理者的ClassLoader
ClassLoader classLoader = concreteLawsuit.getClass().getClassLoader();

// 动态构造一个代理者律师
ILawsuit law = (ILawsuit) Proxy.newProxyInstance(classLoader, new Class[]{ILawsuit.class}, dynamicProxy);

// 动态调用
law.submit();
law.burden();
law.defend();
law.finish();

Android源码对应实现

Android源码中的代理模式实现有很多, 如源码中的ActivityManagerProxy代理类, 其具体代理的是ActivityManagerNative的子类ActivityManagerService. ActivityManagerProxyActivityManagerNative处于同一个文件.

ActivityManagerProxyActivityManagerNative都继承了IActivityManager

proxy-ams.png

可以很明显的看出这三个类构成的代理模式, 但是由于AMN是抽象类, 所以具体的实现交由了子类AMS去实现. 而AMS是系统级的Service并且运行于独立的进程空间中, 可以通过ServiceManager来获取它. 而AMP也运行于自己所处的进程空间中, 两者并不相同, 因此AMSAMP的通信必定是通过跨进程来进行的, 所以此处源码中所实现的实质为远程代理.

AMP在实际的逻辑处理中并未过多地被外部类使用, 因为在Android中管理与维护Activity相关信息的是另一个叫做ActivityManager的类, ActivityManager虽说管理着相关信息, 但是实质上其大多数逻辑都是由AMP承担的.

组合模式 Composite

模式介绍

结构性设计模式, 比较简单, 把一组相似的对象看做一个对象来处理, 并根据一个树状结构来组合对象, 然后提供一个统一的方法去访问相应的对象, 以此忽略掉对象与对象集合之间的差别.

  • 定义: 将对象组合成树形结构以表示整体-部分的层次结构, 使得用户对单个对象和组合对象的使用一致性
  • 场景:
    • 表示对象的部分-整体层次结构
    • 从一个整体中能够独立出部分模块或功能的场景

模式范例

一个很好的组合例子就是文件夹和文件之间的关系. 以此为例, 看看一个简单文件系统是如何构成的.

范例源码

UML_Composite.png

使用代码和结果

public static void main(String arg[]){
   // 构造一个目录对象表示c盘目录
   Folder diskC = new Folder("C");

   // C盘根目录下有一个文件 Log.txt
   diskC.addDir(new File("Lag.txt"));

   // C盘下还有3个子目录
   diskC.addDir(new Folder("目录1"));

   Folder dirs = new Folder("目录2");
   dirs.addDir(new File("null.txt"));
   diskC.addDir(dirs);

   diskC.addDir(new Folder("目录3"));

   // 打印文件结构
   diskC.print();
}

// =========> 结果
C (Lag.txt,目录1 (),目录2 (null.txt),目录3 ())

从根节点依次延伸可以很明显看出这是一个树状的嵌套结构. 这就是组合模式

Android源码对应实现

这个模式在Android有一个很经典的表示, 我们一直再使用, 就是ViewViewGroup结构.

composite-view.png

由于View的视图层级中使用的是安全的设计模式, 所以只能是ViewGroup才可以包含View,反之则不可以, 而上面的范例使用的是透明的组合模式. 可以观察一下具体有哪些不同.

适配器模式 Adapter

模式介绍

这也是一个我们从始至终都在使用的模式, ListView,GridView,RecycleView. 适器就是将两个不兼容的类融合在一起, 有点像粘合剂.

  • 定义: 把一个类的接口转换成客户端所期待的另一个接口, 从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作.
  • 场景:
    • 系统需要使用现有的类, 而此类的接口不符合系统的需要, 即接口不兼容
    • 想要建立一个可以重复使用的类, 用于与一些比起之间没有太大关联的一些类, 包括一些可能在将来引进的类一起工作
    • 需要一个统一的输出接口, 而输入端的类型不可预知

模式范例

软件开发有一句话: 任何问题都可以加一个中间层来解决. 正式对适配器模式的描述, 最常见的就是笔记本电脑一般用的5V电压, 但是生活中的电压都是标准的220V. 所以我们笔记本都通过一个电源适配器来解决此问题.

范例代码

类图关系很简单就不贴出来了

代码中有两种实现:

  • 类适配器模式 : 主要是Adapter角色是继承需要适配的角色.
  • 对象适配器模式: 通过在构造适配器的时候传入适配对象. 使用组合的形式实现接口兼容.

相比较, 使用对象适配器更加的灵活, 另一个好处就是被适配对象的方法不会暴露出来, 而类适配器由于继承了被适配的对象, 因此被适配对象类在Adapter类中同样存在, 这就使得Adapter出现了一些奇怪的方法, 用户的使用成本也较高.

Android源码对应实现

不用说Adapter大家都知道. Android的做法增加了个Adapter层来隔离变化, 将ListView需要的关于Item View接口抽象到Adapter对象中, 并且在ListView内部调用了Adapter这些接口完成布局等操作. 这样只要用户实现了Adapter的接口, 并且将该Adapter设置给ListView, ListView就可以按照用户设定的UI效果, 数量, 数据来显示每一项数据.

装饰模式 Decorator

模式介绍

也称包装模式, 结构性设计模式, 使用一种对客户端透明的方式来动态的扩展对象的功能, 同时他也是继承关系的一种替代方案之一

  • 定义: 动态地给一个对象添加一些额外的职责. 就增加功能来说, 装饰模式相比生成子类更加灵活.
  • 场景: 需要透明且动态地扩展类的功能时

模式范例

人穿衣服的例子

范例源码

UML_Decorator.png

其实可以这种扩展并非是直接修改原有方法逻辑或者结构, 更恰当的说, 仅仅是在另一个类中将原有方法和逻辑进行封装整合.

装饰模式代理模式有点类似, 比较容易混淆的是会把装饰模式当成代理模式. 装饰模式是以对客户端透明的方式扩展对象的功能, 是继承关系的一个替代方案. 而代理模式则是给一个对象提供一个对象代理, 并由代理对象来控制对原有对象的引用. 装饰模式应该为所装饰的对象增强功能; 代理模式对代理的对象施加控制, 但不对对象本身的功能增强.

Android源码对应实现

Context, 是不是熟悉的不能再熟悉了. 它的本质就是一个抽象类. 在装饰模式中相当于抽象组件. 虽然Activity继承了Context但是其中的startActivity(),startService()这些都是由另一个继承者来处理的的. 这个Context的另一个继承者就是ContextImpl.

ContextImpl内部实现了Context的抽象方法. 而Activity等组件只是将其中的方法进行了转发调用.

享元模式 Flyweight

模式介绍

用尽可能减少内存使用量, 它适合用于可能存在大量重复对象的场景, 来缓存可共享的对象, 达到对象共享, 避免创建过多对象的效果. 就可以提升性能, 避免内存抖动

  • 定义: 使用共享对象可有效地支持大量的相似对象
  • 场景:
    • 系统中存在大量的相似对象
    • 细粒度的对象都具备比较接近的外部状态, 而且内部状态与环境无关, 也就是说对象没有特定身份
    • 需要缓冲池的场景

模式范例

通过售票口的出票来为例

范例代码

UML_Flyweight.png

代码使用和结果

public class Client {

    public static void main(String arg[]){
        Ticket ticket = TicketFactory.getTicket("青岛", "北京");
        ticket.showTicketInfo("上铺");

        Ticket ticket1 = TicketFactory.getTicket("青岛", "上海");
        ticket1.showTicketInfo("上铺");

        Ticket ticket2 = TicketFactory.getTicket("青岛", "北京");
        ticket2.showTicketInfo("上铺");
    }
}

// ========> 
创建对象--> 青岛-北京
购买 从青岛  上铺 的北京火车票, 价格: 293
创建对象--> 青岛-上海
购买 从青岛  上铺 的上海火车票, 价格: 20
使用缓存--> 青岛-北京
购买 从青岛  上铺 的北京火车票, 价格: 141

其实主要思想就是: 让可复用的对象实现复用, 减少无用的重复创建的步骤.

Android源码对应实现

Message对象. 在使用Handler传递数据的时候. 不可避免的需要使用Message. 即使你通过Handler.post(Runnable)传递一个接口, 在源码内部同样会通过Message为载体挂到callback变量上传递. 看一下. 源码中是如何维护一个频繁需要使用对象的

private static Message sPool;  // 静态!

// 获取一个Message
public static Message obtain() {
   synchronized (sPoolSync) {
       if (sPool != null) {
           Message m = sPool;
           sPool = m.next;
           m.next = null;
           m.flags = 0; // clear in-use flag
           sPoolSize--;
           return m;
       }
   }
   return new Message();
}

// 回收, 实现缓存的方法
public void recycle() {
   if (isInUse()) {
       if (gCheckRecycle) {
           throw new IllegalStateException("This message cannot be recycled because it "
                   + "is still in use.");
       }
       return;
   }
   recycleUnchecked();
}

void recycleUnchecked() {
   flags = FLAG_IN_USE;
   what = 0;
   arg1 = 0;
   arg2 = 0;
   obj = null;
   replyTo = null;
   sendingUid = -1;
   when = 0;
   target = null;
   callback = null;
   data = null;

   synchronized (sPoolSync) {
       if (sPoolSize < MAX_POOL_SIZE) {
           next = sPool;
           sPool = this;
           sPoolSize++;
       }
   }
}

Android是在调用了recycle()方法的时候实现了缓存, 在obtain()的时候取缓存如果没有, 那么就会创建新的对象. 缓存实现的方式是一个单向链表, 每次调用recycle()会把这个对象挂在链表头.看一下如下的图.

fly-message.png

外观模式 Facade

模式介绍

使用频率很高, 也可以说是第三方SDK都会使用, 本质就是加上一个中间层的传递, 既可以做到统一一个高层类, 降低用户的使用成本, 也能屏蔽一些实现细节. 可能你不经意间使用很多次此模式, 只是没有在理论层面认知它的存在.

  • 定义: 要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行. 门面模式也就是Facade模式提供了一个高层次的接口.
  • 场景:
    • 为一个复杂子系统提供一个简单接口.
    • 当需要构建一个层次结构的子系统时. 使用外观模式定义子系统的每层的入口点. 如果子系统相互依赖可以仅通过facade进行通信.

模式范例

以手机的外观模式为例

范例代码

UML_Facade.png

Android源码对应实现

还是Context, Context对于开发者来说是最重要的高层接口. Context只是定义了很多接口的抽象类, 这些接口的功能实现并不是在Context以及子类中, 而是通过其他的子系统来完成的, 例如startActivity()的真正实现是通过AMS, 获取应用包信息是通过PMS. 而Centext只是做了一个高层次的统一封装.

好处显而易见, 对于开发者, 你只要知道这个高层类即可. 不需要知道太多的子系统就能完成开发.

桥接模式

模式介绍

结构性设计模式

  • 定义: 将抽象部分与实际部分分离, 使他们都可以独立地进行变化
  • 场景:
    • 一个类存在两个独立变化的维度, 且这两个维度都需要进行扩展
    • 对于那些不想使用继承或者因为多层次继承导致系统类的个数的急剧增加的系统, 也可以考虑使用此模式
    • 如果一个系统需要在构件的抽象化角色和具体化角色之间更加灵活, 避免在两个层次之间建立静态的继承联系, 可以通过桥接模式使他们在抽象层建立一个关联关系

模式范例

以喝咖啡为例子, 一个咖啡馆中咖啡有四种, 分别是大杯加糖, 小杯加糖, 大杯无糖, 小杯无糖. 但是对于一杯咖啡来说这4种状态中实际上就是两种变化. 糖的状态和杯的状态.

范例代码

代码使用以及结果:

public static void main(String args[]){

   // 原汁原味
   Ordinary ordinary = new Ordinary();

   // 准备糖类
   Sugar sugar = new Sugar();

   // 大杯咖啡原味
   LargeCoffee largeCoffee = new LargeCoffee(ordinary);
   largeCoffee.makeCoffee();

   // 小杯咖啡 原味
   SmallCoffee smallCoffee = new SmallCoffee(ordinary);
   smallCoffee.makeCoffee();

   // 大杯咖啡 加糖
   LargeCoffee larSugar = new LargeCoffee(sugar);
   larSugar.makeCoffee();

   // 小杯咖啡 加糖
   LargeCoffee smallSugar = new LargeCoffee(sugar);
   smallSugar.makeCoffee();
}
//=========>结果
大杯的 原味 咖啡
小杯的 原味 咖啡
大杯的 加糖 咖啡
大杯的 加糖 咖啡

这里CoffeeAdditives相当于作为了实现部分, 而Coffee则对应抽象部分, 模式中定义所谓的抽象实现实质上对应的是两个独立变化的维度. 也就是说任何多维度变化或者说多个树状类之间的耦合都可以使用桥接模式来解耦. 范例中的这两个基类, 并不一定就是所谓的对应的角色, 两者各自为一维度,独立变化.

如果需要增加口味的种类, 只需要继承CoffeeAdditives实现不同的子类即可完成加奶,加盐的新功能的添加. 不管是这两个角色谁变化了, 相对于对方而言都是独立的没有过多的交际.

Android源码对应实现

桥接模式Android中应用的比较广泛. 一般都是作用于大范围.

  • View的具体控件都定义了不同类型控件的所拥有的基本属性和行为, 但是将它们绘制到屏幕上的部分是与View相关的功能类DisplayList,Hardwarelayer,Canvas负责. 这俩个部分可以看做桥接
  • AdapterAdapterView之间也可以看做是桥接
  • WindowWindowManager之间的关系. WindowPhoneWindow构成窗口的抽象部分; WindowManagerWindowManagerImpl为实现部分; 实现部分的具体实现类WMI使用WindowManagerGlobal通过IWindowManager接口与WMS进行交互. 并由WMS完成具体的窗口工作.