前言
关于面向对象的六大原则请参考前面的博文
csdn上关于23种设计模式的专题
http://blog.csdn.net/column/details/pattern.html
单例模式
public class Singleton{
//volatile 关键字保证Singleton实例在内存总是最新的
private volatile static Singleton instance;
//将默认的构造函数私有化,防止其他类手动new
private Singleton(){};
//获取Singleton实例的唯一方式
public static Singleton getInstance(){
if(instance==null){
sychronized(Singleton.class){
if(instance==null)
instance=new Singleton();
}
}
return instatnce;
}
}
单例模式看起来简单,但其实还有几个地方需要注意的
1、volatile关键字的作用是:线程每次使用到被volatile关键字修饰的变量时,都会去堆里拿最新的数据。当一个线程在执行instance=new Singleton()时,其实执行了三个指令
1、给Singleton实例分配内存
2、调用Singleton()构造函数,初始化成员字段
3、将instance对象指向分配的内存(此时instance就不是null啦~)
在jvm执行指令的时候2、3的顺序是不确定的,也就是jvm有可能先初始化Singleton实例,然后再把实例只想分配的内存地址,或者先把实例指向内存地址在对实例进行初始化。考虑这样一种情况:一开始,第一个线程执行instance=new Singleton();这句时,JVM先指向一个堆地址,而此时,又来了一个线程2,它发现instance不是null,就直接拿去用了,但是堆里面对单例对象的初始化并没有完成,最终出现错误。
2、第一次空指针判断,这个比较好理解,为了防止实例已创建的情况下,避免多线程的不必要的同步阻塞。
3、第二次空指针判断,主要考虑这样一种情况:当有2个线程同时到达了sychronized{}处(当并发量很大的时候这种情况是存在的),其中一个线程拿到了 Singleton类锁(区别对象锁和类锁),进入了sychronized{}里面执行完了对象初始化并释放了类锁,同时由于第二个线程已经跨过了第一次空指针判断,所以它会拿到类锁进入sychronized{},此时的Singleton对象已经被第一个线程初始化了不为空,第二个线程直接跳出同步块,保证了单例的唯一性。
Builer模式
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模式比较简单就不解释了,当一个对象初始化时所需的参数过多,又不全是必要参数时相当有用,可以参考Android Dialog的创建,那里就使用了builder模式。
原型模式
原型设计模式比较简单,就是将一个对象进行拷贝,但对象必须要实现Cloneable 接口,像android中的View及其之类就没有实现Cloneable 不能进行clone。同时要区分原型模式和传引用的区别。原型模式会创建一个新的对象,而传引用只是新创建了一个引用但指向的是同一个对象。同时原型模式又分为浅拷贝和深拷贝。
浅拷贝:就是拷贝了【基本数据类型和引用类型的引用】
深拷贝:【引用类型内的所有基本数据类型的值】
参考:https://juejin.im/entry/593dee6c128fe1006aec7c3e
普通工厂模式
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();
}
}
其实普通工厂方法就是一种工厂生产一种产品,工厂和产品都继承对应的抽象类。比较简单就不多分析了,大家自己看代码把。
抽象工厂模式
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();
}
}
从代码中可用看到,抽象工厂和普通工厂很相似,唯一不同的是普通工厂每个工厂对应只生产一种产品,而抽象工厂每个工厂会生产一组相关或者相互依赖的产品。
策略模式
策略模式从字面来理解其实就是一种编程策略(这不是废话吗,说跟没说一样),方便我们只要进行简单的对象替换,就能实现功能相同,但实现方式不同的算法。几个例子:我们现在要对数组进行排序,我们有好多排序算法,冒泡,二分法,选择等等。假如没有采用策略模式,而是采用用编码 我们有可能会这样做(js 方便点)
function executeSort(type){
if(type === "冒泡"){
....................
}else if(type === "二分法"){
.....................
}else if(type === "选择"){
.....................
}
.......................
}
executeSort("冒泡")
executeSort("二分法")
executeSort("选择")
貌似也还行,不算分复杂吗,但是这个的前提我们事先已经定义好了这几种算法,可是万一有一天,我们发现现有的算法满足不了我们的需求,我们要新增一种排序算法,加入就叫"史上最快排序"算法,我们怎么办,我们要新增
else if(type === "史上最快排序"){
.....................
}
executeSort("史上最快排序")
也还好,改的不多,然后产品所我们要新增"史上最快排序1"~"史上最快排序9",我估计你会看着一大串的 else if想要爆粗口吧,最后恋看都不想看一下代码。好的,让我们换一种方式来实现:
interface AbstractSort{
void sort();
}
class 史上最快排序1 implements AbstractSort{
void sort(){
}
}
.......
class 史上最快排序9 implements AbstractSort{
void sort(){
}
}
void executeSort(AbstractSort sort){
sort.sort()
}
executeSort(new 史上最快排序1() )
要新增的时候我们只需要新增一个类并修改一行代码就够了,每个类实现自己的算法,多么清晰。
状态模式
状态模式和策略模式在编码方式上很像,但是要表达东西和概念却不一样。
状态模式中,行为是有状态来决定的,不同的状态下会执行不同的行为。举个例子把,比如电视,电视有2个状态,一个是开机,一个是关机,开机时可以切换频道,关机时切换频道不做任何响应。
策略模式:每个模式是相互独立的,可以相互替换,得到的结果是一样的,只是实现方式不同
状态模式:平行的不可替换的,因为每种状态下表现出来的行为是不一样的。
责任链模式
多个对象都有机会处理请求,从而避免请求的发送者和接受者直接的耦合关系,将这些对象连成一条链,并沿这条链传递该请求,直到有对象处理它为止。
几个例子,Android中的view事件传递就用到了责任链模式,最外层的Activity首先捕获到了触摸事件,如果自己不处理,则将事件传递给子view处理,一直到有view处理为止(其实这跟js中的原型链有点像,感兴趣的朋友可以去了解一下)。这里就不贴代码了,大家可以去看下Android事件传递的源码。里面涉及到好多设计模式(最主要的是策略+责任链)。
解释器模式
概念:给定一个语言,定义它的语法,并定义一个解释器,这个解释器用于解析语言。
举个例子,比如我们在xml中定义的布局如何转化成我们常见的对象,这其实就用到了解释器模式。解释器模式的侧重点在于解析。
适配器模式
概念:把一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
举个最简单的例子: 比较典型的有ListView和RecyclerView,其实适配器模式就是把2中不相关的对象连接在一起了,因为listview其实只关心itemview,不关心data,那怎样将数据展现在listview里呢,这里就用到了adapter。其实适配器模式的作用就是适配和连接。我们生活中的电源线插头其实就是一种适配器模式。插头将我们的各种电器和插口连接起来,每种电器都有可能有不同的接口,但是电源线将这种连接给屏蔽掉了。仔细观察,生活当中处处都是知识点啊。
命令模式
举个生活中的日子:我点击电脑的关机键,我们就会得到一个关机的最终结果,那至于在电脑处于关机状态之前发生了什么(保存数据,结束进程,切断电源等等),不需要我们关心。
所以简单来讲就是将一系列相关的动作封装在一起,提供给调用者一个统一的接口,通过调用不同的参数,最终展现出基于参数的唯一结果(感觉有点类似函数式编程中的纯函数概念)。
观察者模式
观察者模式我们平时用得比较多,存在1对n的概念,当1发生变化时,所有的n都会接收到通知,比如rxjava就是充分展现了观察者模式的精髓,大家有时间可以去看下相关源码,里面有三个主要的角色:观察者、被观察者、订阅,当订阅关系发生时,观察者就能接收到被观察所触发的行为。
备忘录模式(参考网上的定义)
备忘录模式定义:在不破坏封闭的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态,这样,以后就可将对象恢复到原先保存的状态中。
其实就是相当于一个提前备份,一旦出现啥意外,能够恢复。像我们平时用的word软件,意外关闭了,它能帮我们恢复。其实就是它自动帮我们备份过。
那么Android哪里用到了备忘录模式呢?Activity的onSaveInstanceState和onRestoreInstanceState就是用到了备忘录模式,分别用于保存和恢复
迭代器模式(参考网上的定义)
迭代器模式定义:提供一种方法顺序访问一个容器对象中的各个元素,而不需要暴露该对象的内部表示。
相信熟悉Java的你肯定知道,Java中就有迭代器Iterator类,本质上说,它就是用迭代器模式。
按照惯例,看看Android中哪里用到了迭代器模式,Android源码中,最典型的就是Cursor用到了迭代器模式,当我们使用SQLiteDatabase的query方法时,返回的就是Cursor对象,通过如下方式去遍历:
cursor.moveToFirst();
do{
//cursor.getXXX(int);
}while(cursor.moveToNext);
模板方法模式
模板方法其实在工作中用到得比较多,一般用于业务流程中,我们在抽象中定义执行路口,提供给外部调用,然后把具体的实现方法通过继承的方式让之类去实现。他们根本不需要去关心整体的业务流程,只需要去实现自己的具体方法就行了。
直接拿Android中的源码来说事!我们知道,启动一个Activity过程非常复杂,如果让开发者每次自己去调用启动Activity过程无疑是一场噩梦。好在启动Activity大部分代码时不同的,但是有很多地方需要开发者定制。也就是说,整体算法框架是相同的,但是将一些步骤延迟到子类中,比如Activity的onCreate、onStart等等。这样子类不用改变整体启动Activity过程即可重定义某些具体的操作了~。
访问者模式
这种模式好像在实际开发中运用的并不多,以下文字网上博文。
定义:封装一些作用于某种数据结构中各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
访问者模式是23种设计模式中最复杂的一个,但他的使用率并不高,大部分情况下,我们不需要使用访问者模式,少数特定的场景才需要。
Android中运用访问者模式,其实主要是在编译期注解中,编译期注解核心原理依赖APT(Annotation Processing Tools),著名的开源库比如ButterKnife、Dagger、Retrofit都是基于APT。
中介者模式
中介者模式感觉就像一个大管家,在某一项具体的业务中,全部都可以通过他来完成,而不需要对接这个模块中的其他对象。看看生活中的房产中介应该就比较好理解了,他连接了买方和卖方,而这2种角色却可以不用直接对接。用另一句话来说就是专业的人做专业的事,当然不同的对象就要负责自己擅长的事。
我们知道系统启动时,各种系统服务会向ServiceManager提交注册,即ServiceManager持有各种系统服务的引用 ,当我们需要获取系统的Service时,比如ActivityManager、WindowManager等(它们都是Binder),首先是向ServiceManager查询指定标示符对应的Binder,再由ServiceManager返回Binder的引用。并且客户端和服务端之间的通信是通过Binder驱动来实现,这里的ServiceManager和Binder驱动就是中介者
代理模式&装饰模式
装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。可能还算比较模糊,用网上的2中uml图来理解一下。
代理模式:
装饰者模式
从图中我们可以看到2总模式非常类似,唯一不同的是在代理模式中,代理类似直接持有被代理对象的,而在调用时也是直接创建的代理类,初始化代理类的同时也初始化了被代理类。而装饰着模式中,装饰类和被装饰类往往不存在直接的引用,装饰类持有双方共同的抽象类,在调用的时候通过构造函数传入。
总结
1、从语意上讲,代理模式是为控制对被代理对象的访问,而装饰模式是为了增加被装饰对象的功能
2、代理类所能代理的类完全由代理类确定,装饰类装饰的对象需要根据实际使用时客户端的组合来确定
3、被代理对象由代理对象创建,客户端甚至不需要知道被代理类的存在;被装饰对象由客户端创建并传给装饰对象
组合模式
例子:对于一家大型公司,每当公司高层有重要事项需要通知到总部每个部门以及分公司的各个部门时,并不希望逐一通知,而只希望通过总部各部门及分公司,再由分公司通知其所有部门。这样,对于总公司而言,不需要关心通知的是总部的部门还是分公司
很简单 直接上uml图
享元模式(来自于网络)
定义:使用享元对象有效地支持大量的细粒度对象。
享元模式我们平时接触真的很多,比如Java中的常量池,线程池等。主要是为了重用对象。
在Android哪里用到了享元模式呢?线程通信中的Message,每次我们获取Message时调用Message.obtain()其实就是从消息池中取出可重复使用的消息,避免产生大量的Message对象。
外观模式
定义:要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。
怎么理解呢,举个例子,我们在启动计算机时,只需按一下开关键,无需关系里面的磁盘、内存、cpu、电源等等这些如何工作,我们只关心他们帮我启动好了就行。实际上,由于里面的线路太复杂,我们也没办法去具体了解内部电路如何工作。主机提供唯一一个接口“开关键”给用户就好。
那么Android哪里使用到了外观模式呢?依然回到Context,Android内部有很多复杂的功能比如startActivty、sendBroadcast、bindService等等,这些功能内部的实现非常复杂,如果你看了源码你就能感受得到,但是我们无需关心它内部实现了什么,我们只关心它帮我们启动Activity,帮我们发送了一条广播,绑定了Activity等等就够了。
桥接模式
桥接模式(Bridge Pattern),将抽象部分与它的实现部分分离,使它们都可以独立地变化。更容易理解的表述是:实现系统可从多种维度分类,桥接模式将各维度抽象出来,各维度独立变化,之后可通过聚合,将各维度组合起来,减少了各维度间的耦合。
对比一下一下2张图
不必要的继承导致类爆炸
从上图可以看到,对于每种组合都需要创建一个具体类,如果有N个维度,每个维度有M种变化,则需要MNMN个具体类,类非常多,并且非常多的重复功能。
如果某一维度,如Transmission多一种可能,比如手自一体档(AMT),则需要增加3个类,BMWAMT,BenZAMT,LandRoverAMT。
桥接模式类图
从上图可知,当把每个维度拆分开来,只需要M*N个类,并且由于每个维度独立变化,基本不会出现重复代码。
此时如果增加手自一体档,只需要增加一个AMT类即可