Android中应用到的设计模式

其实一般程序员写业务代码,用不了多少设计模式。写出易于理解、维护的代码,相对更重要。

一、引言

那设计模式是什么?

很多人,包括我,都曾迷陷于 23 种设计模式之中,初识设计模式,赞叹于其精妙,就像个萌新的江湖小生,偶然间掉到个山洞,一下就集齐了威震江湖的“7种武器”。然后……就开始了用长生剑切菜,用碧玉刀削瓜的“幸福生活”,不但把简单的事情搞得巨复杂。

回到设计模式,无非是在面向对象原则这些虚无缥缈的“心法”指导原则下,那些前辈大神们留下的“招式”或是“套路”而已。但招式和套路并不能致胜,它只是为我们这等小白提供了一个接近大神,理解心法的途径而已,通过长年累月的模仿去反思去领悟去体会“心法”的本质和精妙,这就能解释为什么使用同样的招式,大神们总能一击致命,而我们却总是一个迷途中的小生。

不断操练“重构到模式”,通过实际运用实战指导原则和手法,推演招式和套路,帮助我们将这两种方法融会贯通,相互结合,最终领悟那隐藏在一切表象背后的真理,达到无招胜有招,草木竹石皆可为剑,心随意走,人随心动的境界。

二、设计模式

Android 源码中涉及不少设计模式。

2.1 建造者模式

建造者模式最明显的标志就是 Build 类,而在 Android 中最常用的就是 Dialog 的构建,Notification 的构建也是标准的建造者模式。建造者模式比较好理解,如果一个类的构造需要很多参数,而且这些参数并不都是必须的,那么这种情况下就比较适合 Builder。

比如构建一个 AlertDialog,标题、内容、取消按钮、确定按钮、中立按钮,你可能只需要单独设置几个属性即可。

2.2 单例模式

单例在 Android 开发中经常用到,但是表现形式可能不太一样。以 ActivityManager 等系统服务来说,是通过静态代码块的形式实现单例,在首次加载类文件时,生成单例对象,然后保存在 Cache 中,之后的使用都是直接从 Cache 中获取。当然,还有更加明显的例子,比如 AccessibilityManager 内部自己也保证了单例,使用 getInstance 获取单例对象。

public static AccessibilityManager getInstance(Context context) {
    synchronized (sInstanceSync) {
        if (sInstance == null) {

            ......

            IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
            IAccessibilityManager service = iBinder == null
                ? null : IAccessibilityManager.Stub.asInterface(iBinder);
            sInstance = new AccessibilityManager(context, service, userId);
        }
    }
    return sInstance;
}

除此之外,还有一些伪单例,比如 Application,默认情况下在一个进程中只存在一个实例,但是 Application 不能算是单例,因为它的构造方法未私有,你可以生成多个 Application 实例,但是没有用,你没有通过 attach() 绑定相关信息,没有上下文环境。

public Application() {
    super(null);
}

单例的使用场景也很简单,就是一个 APP 只需要存在一个类实例的情况,或者是类的初始化操作比较耗费资源的情况。在很多开源框架中,我们只需要一个对象即可完成工作,比如各种网络框架和图片加载库。

除此之外,因为单例的实现方式很多,比如懒汉式、饿汉式、静态内部类、双重锁检查、枚举等方式,所以要清楚每种实现方式的主要特点和使用场景。

2.3 原型模式

原型模式在开发中使用的并不多,但是在源码中却有所体现。以 Intent 介绍了原型模式,是通过实现 Cloneable 接口来做的

public class Intent implements Parcelable, Cloneable {
    @Override
    public Object clone() {
        return new Intent(this);
    }
}

其实这样来看的话,就是你想更快的获取到一个相同属性的对象,那么就可以使用原型模式,比如这里就获取到了一个 Intent 对象,Intent 里面的属性与被 clone 的相同,但是两者并无关联,可以单独使用。

除了实现 Cloneable 接口,你完全可以自己定义一个方法,来获取一个对象。以 PhoneLayoutInflater 为例子介绍。

PhoneLayoutInflater 是 LayoutInflater 的子类,如果我们在 Activity 中获取 LayoutInflate 的话,是通过下面方法:

@Override 
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

可以看到,如果为 null,就会调用 cloneInContext(),这个方法在 LayoutInflate 是抽象方法,具体实现在 PhoneLayoutInflater中

public LayoutInflater cloneInContext(Context newContext) {
    return new PhoneLayoutInflater(this, newContext);
}

可以看到,这也是一个原型模式,所以我们不要太纠结于形式,更重要的是理解这样做的好处。

除了在源码中可以看到原型模式,在开源框架中也可以看到,比如 OkHttpClient 中就存在着下面的方法

/** Returns a shallow copy of this OkHttpClient. */
@Override
public OkHttpClient clone() {
    return new OkHttpClient(this);
}

可以看到,实现和前面的完全相同,也是new了一个对象返回,因为 OkHttpClient 的构造过程比较复杂,参数众多,所以用这种方式来直接生成新对象,成本很低,而且能保留之前对象的参数设置。

2.4 工厂方法模式

对于工厂方法模式的一个观点很新奇,就是 Activity.onCreate() 可以看做是工厂方法模式,来生成不同的 View 对象填充界面。

静态工厂方法在 Android 中比较明显的例子应该就是 BitmapFactory 了,通过各种 decodeXXX() 就可以从不同渠道获得 Bitmap 对象。

2.5 策略模式

策略模式就相当于一个影碟机,你往里面插什么碟子,就能放出什么电影。

策略模式的精髓就在于,你传入一个类,后面的处理就能按照这个类的实现去做。以动画为例,设置不同的插值器对象,就可以得到不同的变化曲线;以返回值解析为例,传入什么样的解析器,就可以把二进制数据转换成什么格式的数据,比如 String、Json、XML。

2.6 责任链模式

对于责任链模式选取的例子非常有代表性,那就是 Android 的触摸机制,这个看法让我们从另一个维度去理解 Android 中的触摸事件传递。

2.7 观察者模式

Android 中的观察者模式应该是用的非常频繁的一种模式了,对于这个模式的使用场景就一句话:你想在某个对象发生变化时,立刻收到通知。

观察者模式使用的是 ListView 的 Adapter 为例子,我之前知道 Adapter 属于适配器模式,不知道这里还有观察者模式的身影,学到了。

Android 里面的各种监听器,也都属于观察者模式,比如触摸、点击、按键等,ContentProvider 和广播接收者也有观察者模式的身影,可以说是无处不在。

除此之外,现在很多基于观察者模式的第三方框架也是非常多,比如 EventBus、RxJava 等等,都是对观察者模式的深入使用。

2.8 模板方法模式

这个模式我之前接触的比较少,但是理解之后,就会发现这个模式很简单。模板方法模式的使用场景也是一句话:流程确定,具体实现细节由子类完成。

这里要关注一下“流程”这个关键字,随便拿一个抽象类,都符合"具体实现细节由子类完成"的要求,关键就在于是否有流程,有了流程,就叫模板方法模式,没有流程,就是抽象类的实现。

这个模式用的是 AsyncTask,各个方法之间的执行符合流程,具体实现由我们完成,非常经典。

除了 Android 里面的模板方法模式,在其他开源项目中也存在着这个模式的运用。

2.9 代理模式和装饰器模式

之所以把这两个放在一起说,是因为这两种模式很像,所以这里简单介绍下他们之间的区别,主要有两点。

装饰器模式关注于在一个对象上动态的添加方法,而代理模式关注于控制对对象的访问

代理模式,代理类可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。而当我们使用装饰器模式的时候,通常的做法是将原始对象作为一个参数传给装饰者的构造器

这两句话可能不太好理解,没关系,下面看个例子。

代理模式会持有被代理对象的实例,而这个实例一般是作为成员变量直接存在于代理类中的,即不需要额外的赋值。

比如说 WindowManagerImpl 就是一个代理类,虽然名字上看着不像,但是它代理的是 WindowManagerGlobal 对象。从下面的代码中就可以看出来。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    ......

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

    @Override
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }
}

从上面的代码中可以看出,大部分 WindowManagerImpl 的方法都是通过 WindowManagerGlobal 实现的,而 WindowManagerGlobal 对象不需要额外的赋值,就存在于WindowManagerImpl 中。另外,WindowManagerGlobal 中其实有大量的方法,但是通过 WindowManagerImpl 代理之后,都没有暴露出来,对开发者是透明的。

我们再来看一下装饰器模式。装饰器模式的目的不在于控制访问,而是扩展功能,相比于继承基类来扩展功能,使用装饰器模式更加的灵活。

书中是以 Context 和它的包装类 ContextWrapper 讲解的,也非常的典型,贴出一些代码来说明装饰器模式的形式。

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
}

但是还有一个问题,就是在 ContextWrapper 中,所有方法的实现都是通过 mBase 来实现的,形式上是对上号了,说好的扩展功能呢?

功能扩展其实是在 ContextWrapper 的子类 ContextThemeWrapper 里面。

在 ContextWrapper 里面,获取系统服务是直接通过 mBase 完成的

@Override
public Object getSystemService(String name) {
    return mBase.getSystemService(name);
}

但是在 ContextThemeWrapper 里面,对这个方法进行了重写,完成了功能扩展

@Override 
public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
        if (mInflater == null) {
            mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
        }
        return mInflater;
    }
    return getBaseContext().getSystemService(name);
}

当然,如果不存在功能扩展就不算是装饰器模式了吗?其实设计模式本来就是“仁者见仁,智者见智”的事情。

三、总结

其实 Android 中的设计模式确实到处都存在,不是缺少设计模式,而是缺少去注意。当然,设计模式的提出是为了解决特定的问题,当我们遇到类似问题的时候,可以从设计模式的角度思考和解决问题。

设计模式不是万能的,它其实是中性的,无所谓好坏。