相信大家都曾经下定决心把23种设计模式牢记于心,每次看完之后过一段时间又忘记了~,又得回去看,脑子里唯一依稀记得的是少数设计模式的大致的定义。其实,网上很多文章讲得都非常好,我也曾经去看过各种文章。也曾一直苦恼这些难以永久记下的设计模式,直到我接触到了《Android源码设计模式解析与实战》——何红辉与关爱明著,发现原来其实我们在Android中都接触过这些设计模式,只是我们不知道而已。
既然我们都接触过,我们只需一一对号入座,对设计模式的记忆就不用死记硬背了!这里自愿无偿做个广告,《Android源码设计模式解析与实战》这本书真心不错,每个Android程序员最好都去翻翻…正如你所想的那样,本文是从这本书中的总结,相信你也会跟我一样,从中获益。
面向对象的六大原则
首先,我们为什么要学习设计模式。主要是这些模式是前人总结的经验,使用这些模式能让我们的程序更健壮、更稳定、容易扩展等等优点。在编写面向对象程序时,我们需要遵循以下6个原则,能让我们的程序维护起来更轻松~(当然还有其它好处)。
1 单一职责原则
单一原则很简单,就是将一组相关性很高的函数、数据封装到一个类中。换句话说,一个类应该有职责单一。
2 开闭原则
开闭原则理解起来也不复杂,就是一个类应该对于扩展是开放的,但是对于修改是封闭的。我们知道,在开放的app或者是系统中,经常需要升级、维护等,这就要对原来的代码进行修改,可是修改时容易破坏原有的系统,甚至带来一些新的难以发现的BUG。因此,我们在一开始编写代码时,就应该注意尽量通过扩展的方式实现新的功能,而不是通过修改已有的代码实现。
3 里氏替换原则
里氏替换原则的定义为:所有引用基类的地方必须能透明地使用其子类对象。定义看起来很抽象,其实,很容易理解,本质上就是说,要好好利用继承和多态。简单地说,就是以父类的形式声明的变量(或形参),赋值为任何继承于这个父类的子类后不影响程序的执行。看一组代码你就明白这个原则了:
//窗口类 public class Window(){ public void show(View child){ child.draw(); } } public abstract class View(){ public abstract void draw(); public void measure(int widht,int height){ //测量视图大小 } } public class Button extends View{ public void draw(){ //绘制按钮 } } public class TextView extends View{ public void draw(){ //绘制文本 } }
Window 类中show函数需要传入View,并且调用View对象的draw函数。而每个继承于View的子对象都有draw的实现,不存在继承于View但是却没实现draw函数的子类(abstract方法必须实现)。我们在抽象类设计之时就运用到了里氏替换原则。
4 依赖倒置原则
依赖倒置主要是实现解耦,使得高层次的模块不依赖于低层次模块的具体实现细节。怎么去理解它呢,我们需要知道几个关键点:
(1)高层模块不应该依赖底层模块(具体实现),二者都应该依赖其抽象(抽象类或接口)
(2)抽象不应该依赖细节(废话,抽象类跟接口肯定不依赖具体的实现了)
(3)细节应该依赖于抽象(同样废话,具体实现类肯定要依赖其继承的抽象类或接口)
其实,在我们用的Java语言中,抽象就是指接口或者抽象类,二者都是不能直接被实例化;细节就是实现类,实现接口或者继承抽象类而产生的类,就是细节。使用Java语言描述就简单了:就是各个模块之间相互传递的参数声明为抽象类型,而不是声明为具体的实现类;
5 接口隔离原则
接口隔离原则定义:类之间的依赖关系应该建立在最小的接口上。其原则是将非常庞大的、臃肿的接口拆分成更小的更具体的接口。
6 迪米特原则
描述的原则:一个对象应该对其他的对象有最少的了解。什么意思呢?就是说一个类应该对自己调用的类知道的最少。还是不懂?其实简单来说:假设类A实现了某个功能,类B需要调用类A的去执行这个功能,那么类A应该只暴露一个函数给类B,这个函数表示是实现这个功能的函数,而不是让类A把实现这个功能的所有细分的函数暴露给B。
开始学设计模式
学习了上面的六大原则之后,提前做了预热。现在开始,一起学习设计模式吧~
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);
其内部是通过单例的方式返回的,由于单例模式较简单,这里不去深究。
2 Builder模式
Builder模式是什么情况呢?我不想去提它的定义,因为他的定义:将一个复杂对象的构造与它的表示分离,使得同样的构造过程可以创建不同的表示。好吧,我还是提了。
但是看了这个定义并没有什么luan用。我们看看具体在什么情况下用到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 t; } 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();
这里的create()函数就想到上面代码中的build函数。看到这里是不是在内心中默默的把Builder设计模式拿下了?你并不用死记硬背~
3 原型模式
原型设计模式非常简单,就是将一个对象进行拷贝。对于类A实例a,要对a进行拷贝,就是创建一个跟a一样的类型A的实例b,然后将a的属性全部复制到b。
什么时候会用到原型模式呢?我个人认为,可以在类的属性特别多,但是又要经常对类进行拷贝的时候可以用原型模式,这样代码比较简洁,而且比较方便。
另外要注意的是,还有深拷贝和浅拷贝。深拷贝就是把对象里面的引用的对象也要拷贝一份新的对象,并将这个新的引用对象作为拷贝的对象引用。说的比较绕哈~,举个例子,假设A类中有B类的引用b,现在需要对A类实例进行拷贝,那么深拷贝就是,先对b进行一次拷贝得到nb,然后把nb作为A类拷贝的对象的引用,如此一层一层迭代拷贝,把所有的引用都拷贝结束。浅拷贝则不是。
原型模式比较简单,看看Android怎么运用原型模式:
Uri uri=Uri.parse("smsto:10086"); Intent shareIntent=new Intent(Intent.ACTION_SENDTO,uri); //克隆副本 Intent intent=(Intetn)shareIntent.clone(); startActivity(intent);
或许我们平时不会这么去写,但是Intent
对象确实提供了原型模式的函数clone()
4 工厂方法模式
定义:定义一个创建对象的接口,让子类决定实例化哪个类
先看一个例子:
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); }
5 抽象工厂模式
抽象工厂模式:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要制定他们的具体类
看个例子吧,将它跟工厂方法模式做个对比:
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(); } }