android设计模式

Android设计模式之23种设计模式一览

设计模式的分类

设计模式分为三种类型:

(1)创建型模式5种:单例模式,抽象工厂模式,工厂模式,原型模式,建造者模式。

(口诀:单原建造者,东西二厂)

(2)结构型模式7种:适配器模式,桥接模式,装饰模式,组合模式,外观模式,享元模式,代理模式。

(口诀:一器一桥一元一代理;装饰组合外观)

(3)行为型模式11种:观察者模式,中介者模式,访问者模式,解释器模式,迭代器模式,备忘录模式,责任链模式,状态模式,策略模式,命令模式,模板模式。

(口诀:三者两器、一录一链一模板,状态策略命令)

Android中的设计模式

(1)单例模式:

定义:确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。

什么时候需要使用单例模式呢:如果某个类,创建时需要消耗很多资源,即new出这个类的代价很大;或者是这个类占用很多内存,如果创建太多这个类实例会导致内存占用太多。

关于单例模式,虽然很简单,无需过多的解释,但是这里还要提个醒,其实单例模式里面有很多坑。我们去会会单例模式。最简单的单例模式如下:

public class Singleton{
    private static Singleton instance;
    //将默认的构造函数私有化,防止其他类手动new
    private Singleton(){};
    public static Singleton getInstance(){
        if(instance==null)
            instance=new Singleton();
         return instatnce;
    }
如果是单线程下的系统,这么写肯定没问题。可是如果是多线程环境呢?这代码明显不是线程安全的,存在隐患:某个线程拿到的 instance可能是 null,可能你会想,这有什么难得,直接在 getInstance()函数上加 sychronized关键字不就好了。可是你想过没有,每次调用 getInstance()时都要执行同步,这带来没必要的性能上的消耗。注意,在方法上加 sychronized关键字时,一个线程访问这个方法时,其他线程无法同时访问这个类其他 sychronized方法。的我们看看另外一种实现:

public class Singleton{
    private static Singleton instance;
    //将默认的构造函数私有化,防止其他类手动new
    private Singleton(){};
    public static Singleton getInstance(){
        if(instance==null){
            sychronized(Singleton.class){
                if(instance==null)
                    instance=new Singleton();
            }
        }
        return instatnce;
    }
}
为什么需要2次判断是否为空呢?第一次判断是为了避免不必要的同步,第二次判断是确保在此之前没有其他线程进入到sychronized块创建了新实例。这段代码看上去非常完美,但是,,, 却有隐患!问题出现在哪呢?主要是在 instance=new Singleton();这段代码上。这段代码会编译成多条指令,大致上做了3件事:

(1)给Singleton实例分配内存
(2)调用Singleton()构造函数,初始化成员字段
(3)将instance对象指向分配的内存(此时instance就不是null啦~)

上面的(2)和(3)的顺序无法得到保证的,也就是说,JVM可能先初始化实例字段再把instance指向具体的内存实例,也可能先把instance指向内存实例再对实例进行初始化成员字段。考虑这种情况:一开始,第一个线程执行instance=new Singleton();这句时,JVM先指向一个堆地址,而此时,又来了一个线程2,它发现instance不是null,就直接拿去用了,但是堆里面对单例对象的初始化并没有完成,最终出现错误~ 。
看看另外一种方式:

public class Singleton{
    private volatile static Singleton instance;
    //将默认的构造函数私有化,防止其他类手动new
    private Singleton(){};
    public static Singleton getInstance(){
        if(instance==null){
            sychronized(Singleton.class){
                if(instance==null)
                    instance=new Singleton();
            }
        }
        return instatnce;
    }
}

相比前面的代码,这里只是对instance变量加了一个volatile关键字volatile关键字的作用是:线程每次使用到被volatile关键字修饰的变量时,都会去堆里拿最新的数据。换句话说,就是每次使用instance时,保证了instance是最新的。注意:volatile关键字并不能解决并发的问题,关于volatile请查看其它相关文章。但是volatile能解决我们这里的问题。

那么在安卓中哪些地方用到了单例模式呢?其实,我们在调用系统服务时拿到的Binder对象就是个单例。比如:

//获取WindowManager服务引用
WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);  
  • 1
  • 2
  • 3

其内部是通过单例的方式返回的,由于单例模式较简单,这里不去深究。

示例:Android中的系统级服务都是通过容器的单例模式实现方式,以单例形式存在,减少了资源消耗。

比如LayoutInflater Service,将这些服务以键值对的形式存储在一个HashMap容器中,用户使用时只需要根据key来获取到对应的ServiceFetcher,然后通过ServcieFetcher对象的getService函数来获取到具体的服务对象,第一次获取时会调用ServcieFetcher的createService函数创建服务对象,然后将该对象缓存到一个列表中,下次再取时直接从缓存中获取,避免重复创建对象,从而达到单例的效果。

(2)工厂模式:

简介:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。

示例:BitmapFactory位图工厂,专门用来将指定的图片转换为指定的位图Bitmap。

public abstract class Product{
    public abstract void method();
} 

public class ConcreteProductA extends Prodect{
    public void method(){
        System.out.println("我是产品A!");
    }
}

public class ConcreteProductB extends Prodect{
    public void method(){
        System.out.println("我是产品B!");
    }
}
public  abstract class Factory{
    public abstract Product createProduct();
}

public class MyFactory extends Factory{

    public Product createProduct(){
        return new ConcreteProductA();
    }
}

看到上面的代码,是不是觉得工厂模式很简单呢?还可以通过传参的方式,让MyFactory的createProduct方法根据传入的参数决定是创建ConcreteProductA还是ConcreteProductB。

同样的,我们不希望记住这个例子,而是通过Android中的代码来记忆:
其实,在getSystemService方法中就是用到了工厂模式,他就是根据传入的参数决定创建哪个对象,当然了,由于返回的都是以单例模式存在的对象,因此不用new了,直接把单例返回就好。

public Object getSystemService(String name) {
    if (getBaseContext() == null) {
        throw new IllegalStateException("System services not available to Activities before onCreate()");
    }
    //........
    if (WINDOW_SERVICE.equals(name)) {
         return mWindowManager;
    } else if (SEARCH_SERVICE.equals(name)) {
        ensureSearchManager();
        return mSearchManager;
    }
    //.......
    return super.getSystemService(name);
  }

(3)抽象工厂模式:

简介:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

示例:Android底层对MediaPlayer的创建。

MediaPlayerFactory是Android底层为了创建不同的MediaPlayer所定义的一个类。

看个例子吧,将它跟工厂方法模式做个对比

public abstract class AbstractProductA{
    public abstract void method();
}
public abstract class AbstractProdectB{
    public abstract void method();
}

public class ConcreteProductA1 extends AbstractProductA{
    public void method(){
        System.out.println("具体产品A1的方法!");
    }
}
public class ConcreteProductA2 extends AbstractProductA{
    public void method(){
        System.out.println("具体产品A2的方法!");
    }
}
public class ConcreteProductB1 extends AbstractProductB{
    public void method(){
        System.out.println("具体产品B1的方法!");
    }
}
public class ConcreteProductB2 extends AbstractProductB{
    public void method(){
        System.out.println("具体产品B2的方法!");
    }
}

public abstract class AbstractFactory{
    public abstract AbstractProductA createProductA();

    public abstract AbstractProductB createProductB();
}

public  class ConcreteFactory1 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA1();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB1();
    }
}

public  class ConcreteFactory2 extends AbstractFactory{
    public  AbstractProductA createProductA(){
        return new ConcreteProductA2();
    }

    public  AbstractProductB createProductB(){
        return new ConcreteProductB2();
    }
}

其实Android源码中对抽象工厂出现的比较少,好在抽象工厂方法并不复杂,很容易记住,我们可以从Service中去理解,Service的onBind方法可以看成是一个工厂方法,从framework角度来看Service,可以看成是一个具体的工厂,这相当于一个抽象工厂方法模式的雏形。

public class BaseService extends Service{
    @Nullable
    @Override
    public IBinder onBind(Intent intent){
        return new Binder();
    }
}


(4)原型模式:

简介:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

示例:比如我们需要一张Bitmap的几种不同格式:ARGB_8888、RGB_565、ARGB_4444、ALAPHA_8等。那我们就可以先创建一个ARGB_8888的Bitmap作为原型,在它的基础上,通过调用Bitmap.copy(Config)来创建出其它几种格式的Bitmap。另外一个例子就是Java中所有对象都有的一个名字叫clone的方法,已经原型模式的代名词了。在系统中要创建大量的对象,这些对象之间具有几乎完全相同的功能,只是在细节上有一点儿差别。

(5)建造者模式:(Builder模式):

简介:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

在什么情况下用到Builder模式:主要是在创建某个对象时,需要设定很多的参数(通过setter方法),但是这些参数必须按照某个顺序设定,或者是设置步骤不同会得到不同结果

举个非常简单的例子:

public class MyData{
    private int id;
    private String num; 
    public void Test(){
    } 
    public void setId(int id){
        this.id=id;
    }

    public void setNum(String num){
        this.num=num+"id";
    }
}
当然了,没有人会这么去写代码。这里只是举例子,或者是有时候很多参数有这种类似的依赖关系时,通过构造函数未免太多参数了。回到主题,就是如果是上面的代码,该怎么办呢?你可能会说,那还不简单,先调用 setId函数,再调用 setNum函数。是的,没错。可是,万一你一不小心先调用了 setNum呢?这是比较简单的示例,如果是比较复杂的,有很多变量之间依赖的关系,那你每次都得小心翼翼的把各个函数的执行步骤写正确。

我们看看Builder模式是怎么去做的:

public class MyBuilder{
    private int id;
    private String num;
    public MyData build(){
        MyData d=new MyData();
        d.setId(id);
        d.setNum(num);
        return d;
    }
    public MyBuilder setId(int id){
        this.id=id;
        return this;
    }
    public MyBuilder setNum(String num){
        this.num=num;
        return this;
    }

}

public class Test{
    public static void  main(String[] args){
        MyData d=new MyBuilder().setId(10).setNum("hc").build();
    }

}

注意到,Builer类的setter函数都会返回自身的引用this,这主要是用于链式调用,这也是Builder设计模式中的一个很明显的特征。

Android中用过的代码来记忆

记忆我这个例子没啥意义,我们前面说过,要通过Android中用过的代码来记忆,这样才可以不用死记硬背。那么在Android中哪里用到了Builder设计模式呢?在创建对话框时,是不是跟上面有点类似呢?

AlertDialog.Builer builder=new AlertDialog.Builder(context);
builder.setIcon(R.drawable.icon)
    .setTitle("title")
    .setMessage("message")
    .setPositiveButton("Button1", 
        new DialogInterface.OnclickListener(){
            public void onClick(DialogInterface dialog,int whichButton){
                setTitle("click");
            }   
        })
    .create()
    .show();

示例:AlertDialog.Builder   ImageLoader的初始配置。

(6)适配器模式:

简介:将一个类的接口转换成客户希望的另外一个接口。

其实适配器模式很容易理解,我们在Android开发时也经常用到。比较典型的有ListView和RecyclerView。为什么ListView需要使用适配器呢?主要是,ListView只关心它的每个ItemView,而不关心这个ItemView具体显示的是什么。而我们的数据源存放的是要显示的内容,它保存了每一个ItemView要显示的内容。ListView和数据源之间没有任何关系,这时候,需要通过适配器,适配器提供getView方法给ListView使用,每次ListView只需提供位置信息给getView函数,然后getView函数根据位置信息向数据源获取对应的数据,根据数据返回不同的View。

示例:不同的数据提供者使用一个适配器来向一个相同的客户提供服务。

ListView或GridView的Adapter。

(7)桥接模式:

简介:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

其实就是,一个类存在两个维度的变化,且这两个维度都需要进行扩展。

在Android中桥接模式用的很多,举个例子,对于一个View来说,它有两个维度的变化,一个是它的描述比如Button、TextView等等他们是View的描述维度上的变化,另一个维度就是将View真正绘制到屏幕上,这跟Display、HardwareLayer和Canvas有关。这两个维度可以看成是桥接模式的应用。

示例:Window和WindowManager之间的关系。

在FrameWork中Window和PhoneWindow构成窗口的抽象部分,其中Window类为该抽象部分的抽象接口,PhoneWindow为抽象部分具体的实现及扩展。而WindowManager则为实现部分的基类,WindowManagerImpl则为实现部分具体的逻辑实现。

(8)装饰模式:

简介:动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。

示例:Activity继承自ContextThemeWrapper,ContextThemeWrapper继承自ContextWrapper,ContextWrapper才是继承自Context。ContextWrapper就是我们找的装饰者。

(9)组合模式:

简介:将对象组合成树形结构以表示“部分-整体”的层次结构。

上面的定义不太好理解,我们直接从Android中用到的组合模式说起。我们知道,Android中View的结构是树形结构,每个ViewGroup包含一系列的View,而ViewGroup本身又是View。这是Android中非常典型的组合模式。

示例:View和ViewGroup的组合

(10)外观模式

简介:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,统一编程接口。

怎么理解呢,举个例子,我们在启动计算机时,只需按一下开关键,无需关系里面的磁盘、内存、cpu、电源等等这些如何工作,我们只关心他们帮我启动好了就行。实际上,由于里面的线路太复杂,我们也没办法去具体了解内部电路如何工作。主机提供唯一一个接口“开关键”给用户就好。

那么Android哪里使用到了外观模式呢?依然回到Context,Android内部有很多复杂的功能比如startActivty、sendBroadcast、bindService等等,这些功能内部的实现非常复杂,如果你看了源码你就能感受得到,但是我们无需关心它内部实现了什么,我们只关心它帮我们启动Activity,帮我们发送了一条广播,绑定了Activity等等就够了。

示例:ContextImpl

(11)享元模式:

简介:运用共享技术有效地支持大量细粒度的对象。

享元模式我们平时接触真的很多,比如Java中的常量池,线程池等。主要是为了重用对象。

在Android哪里用到了享元模式呢?线程通信中的Message,每次我们获取Message时调用Message.obtain()其实就是从消息池中取出可重复使用的消息,避免产生大量的Message对象。

(12)代理模式:

简介:为其他对象提供一个代理以控制对这个对象的访问。

示例:所有的AIDL都一个代理模式的例子。假设一个Activity A去绑定一个Service S,那么A调用S中的每一个方法其实都是通过系统的Binder机制的中转,然后调用S中的对应方法来做到的。Binder机制就起到了代理的作用。

(13)观察者模式:

简介:一个对象发生改变时,所有信赖于它的对象自动做相应改变。

观察者模式一个比较经典的应用就是:订阅——发布系统。很容易理解,发布消息时,将消息发送给每个订阅者。我们常用的微信公众号就是典型,当我们关注某个公众号时,每当公众号推送消息时,我们就会去接收到消息,当然了,每个订阅(关注)公众号的的人都能接收到公众号推送的消息。

那么Android哪里用到了观察者模式呢?我们看看ListView的适配器,有个函数notifyDataSetChanged()函数,这个函数其实就是通知ListView的每个Item,数据源发生了变化,请各位Item重新刷新一下。

示例:我们可以通过BaseAdapter.registerDataSetObserver和BaseAdapter.unregisterDataSetObserver两方法来向BaseAdater注册、注销一个DataSetObserver。这个过程中,DataSetObserver就是一个观察者,它一旦发现BaseAdapter内部数据有变量,就会通过回调方法DataSetObserver.onChanged和DataSetObserver.onInvalidated来通知DataSetObserver的实现类。事件通知也是观察者模式。

(14)中介者模式:

简介:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

什么时候用中介者模式呢?其实,中介者对象是将系统从网状结构转为以调停者为中心的星型结构。

举个简单的例子,一台电脑包括:CPU、内存、显卡、IO设备。其实,要启动一台计算机,有了CPU和内存就够了。当然,如果你需要连接显示器显示画面,那就得加显卡,如果你需要存储数据,那就要IO设备,但是这并不是最重要的,它们只是分割开来的普通零件而已,我们需要一样东西把这些零件整合起来,变成一个完整体,这个东西就是主板。主板就是起到中介者的作用,任何两个模块之间的通信都会经过主板协调。

示例:Binder机制。

那么Android中那些地方用到了中介者模式呢?在Binder机制中,就用到了中介者模式,对Binder不是很熟悉的童鞋请参考《 简单明了,彻底地理解Binder》。我们知道系统启动时,各种系统服务会向ServiceManager提交注册,即ServiceManager持有各种系统服务的引用 ,当我们需要获取系统的Service时,比如ActivityManagerWindowManager等(它们都是Binder),首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManagerBinder驱动就是中介者。

(15)访问者模式:

简介:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

访问者模式是23种设计模式中最复杂的一个,但他的使用率并不高,大部分情况下,我们不需要使用访问者模式,少数特定的场景才需要。

Android中运用访问者模式,其实主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT。

示例:编译时注解中的ElementVisitor中定义多个Visit接口,每个接口处理一种数据类型,这就是典型的访问者模式,访问者模式正好解决了数据结构和数据操作分离的问题,避免某些操作污染了数据对象类。

(16) 解释器模式:

简介:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

从定义中看起来比较抽象,其实,很简单,很容易理解!就是相当于自定义一个格式的文件,然后去解析它。不用理解的那么复杂!

我们看看Android中哪里用到了,从我们第一次学Android时就知道,四大组件需要在AndroidManifest.xml中定义,其实AndroidManifest.xml就定义了等标签(语句)的属性以及其子标签,规定了具体的使用(语法),通过PackageManagerService(解释器)进行解析。

示例:PackageParser这个类对AndroidManifest.xml这个配置文件的解析过程,

(17)迭代器模式

简介:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

相信熟悉Java的你肯定知道,Java中就有迭代器Iterator类,本质上说,它就是用迭代器模式。

按照惯例,看看Android中哪里用到了迭代器模式,Android源码中,最典型的就是Cursor用到了迭代器模式,当我们使用SQLiteDatabasequery方法时,返回的就是Cursor对象,通过如下方式去遍历:

cursor.moveToFirst();
do{
//cursor.getXXX(int);
}while(cursor.moveToNext);3

示例:在Android中除了各种数据结构体,如List,Map,等包含的迭代器以外,Android源码中也提供了迭代器遍历模式,比如数据库查询使用Cursor,当我们使用SQLiteDataBase的query方法查询数据库时,会返回一个Cursor游标对象,该游标对象实际上就是一个具体的迭代器。

(18)备忘录模式

简介:不需要了解对象的内部结构的情况下备份对象的状态,方便以后恢复。

其实就是相当于一个提前备份,一旦出现啥意外,能够恢复。像我们平时用的word软件,意外关闭了,它能帮我们恢复。其实就是它自动帮我们备份过。

示例:Activity的onSaveInstanceState和onRestoreInstanceState就是通过Bundle这种序列化的数据结构来存储Activity的状态,至于其中存储的数据结构,这两个方法不用关心。

(19)责任链模式

简介:有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。

相信聪明的你很容易理解吧,基本不需要例子来解释了,直接进如到Android源码中哪里用到了责任链:在Android处理点击事件时,父View先接收到点击事件,如果父View不处理则交给子View,依次往下传递~

示例:  责任链模式在Android源码中比较类似的实现莫过于对事件的分发处理,每当用户接触屏幕时候,Android都会将对应的事件包装成一个事件对象从ViewTree的顶部至上而下的分发传递。ViewGroup事件投递的递归调用就类似一条责任链,一旦寻找到责任者,那么就由责任者持有并消费该次事件,具体的体现在View的onTouchEvent方法中的返回值,如果OnTouchEvent返回false,那么意味着当前View不会是该次事件的责任人,将不会对该事件持有。

(20)策略模式

简介:定义了一系列封装了算法、行为的对象,他们可以相互替换。

举个例子来理解吧,比如,你现在又很多排序算法:冒泡、希尔、归并、选择等等。我们要根据实际情况来选择使用哪种算法,有一种常见的方法是,通过if…else或者case…等条件判断语句来选择。但是这个类的维护成本会变高,维护时也容易发生错误。

如何使用策略模式呢,我不打算写示例代码了,简单描述一下,就将前面说的算法选择进行描述。我们可以定义一个算法抽象类AbstractAlgorithm,这个类定义一个抽象方法sort()。每个具体的排序算法去继承AbstractAlgorithm类并重写sort()实现排序。在需要使用排序的类Client类中,添加一个setAlgorithm(AbstractAlgorithm al);方法将算法设置进去,每次Client需要排序而是就调用al.sort()。

示例:Java.util.List就是定义了一个增(add)、删(remove)、改(set)、查(indexOf)策略,至于实现这个策略的ArrayList、LinkedList等类,只是在具体实现时采用了不同的算法。但因为它们策略一样,不考虑速度的情况下,使用时完全可以互相替换使用。

(21)状态模式:

简介:状态发生改变时,行为改变。

示例:View.onVisibilityChanged方法,就是提供了一个状态模式的实现,允许在View的visibility发生改变时,引

发执行onVisibilityChanged方法中的动作。



(22)命令模式

简介:把请求封装成一个对象发送出去,方便定制、排队、取消。

举个例子来理解:当我们点击“关机”命令,系统会执行一系列操作,比如暂停事件处理、保存系统配置、结束程序进程、调用内核命令关闭计算机等等,这些命令封装从不同的对象,然后放入到队列中一个个去执行,还可以提供撤销操作。

示例:Handler.post后Handler.handleMessage。

(23)享元模式

简介:运用共享技术有效地支持大量细粒度的对象。

享元模式我们平时接触真的很多,比如Java中的常量池,线程池等。主要是为了重用对象。

在Android哪里用到了享元模式呢?线程通信中的Message,每次我们获取Message时调用Message.obtain()其实就是从消息池中取出可重复使用的消息,避免产生大量的Message对象。

示例:Message.obtainMessage通过重用Message对象来避免大量的Message对象被频繁的创建和销毁。


你可能感兴趣的:(随笔)