到底什么是设计模式
其实模式这个概念是从建筑那里引进过来的,就如同正能量其实是从物理学(天体物理学)那里引进的一样,设计模式的定义是:解决软件设计中给定背景下普遍存在的问题,并且具备一般性、可复用性解决方案;
设计模式虽然多,但是也有一些相同之处,就如同编程语言虽然多,但是都有数据类型、控制语句等相同的部分,描述设计模式的常见格式一般包括:1.别名:指出相应模式的其他名字;
2.背景:列出相应设计模式所解决的问题的产生背景,或者屙屎应用相应的设计模式的背景
3.问题:指出相应的设计模式所要解决的问题;
4.解决方案:描述设计模式所要解决的问题的解决方案;
5.结果:对设计模式进行评价,主要是分析使用相应的设计模式的优势和劣势;
6.相关模式:描述当前的设计模式和其他的设计模式之间的关系;
第一种:不可变对象模式(lmmutable Object)
不可变对象模式主要是为了不使用锁的情况下还能保证线程的安全,因为多线程共享变量的情况下,为了保证数据的一致性,往往需要对这些变量的访问进行加锁,而加锁本身又会带来新的问题和开销;
也就是说,不可变对象模式就是为了减少锁的这一块的开销而生的;
说到开销,具体是什么开销?
在多线程的环境下,一个对象往往会被多个线程共享,那么, 如果存在多个线程并发的修改该对象的状态,或者,一个线程访问该对象的状态的同时另一个线程试图修改该对象的状态,为了保证数据一致性(要是转账操作可就麻烦了!)我们必须做同步访问控制;
而这些同步访问控制(显式锁、CAS之类的),会带来上下文切换、等待时间还有ABA问题等开销,而不可变对象是要怎么做呢?通过使用对外可见的状态不可变的对象,使得被共享对象“继承”了线程安全性,从而为了同步访问控制也就免了;
什么是状态不可变对象?对象一旦创建成功,其对外可见的状态就不可改变,java中的大名鼎鼎的中间数据类型:String就是这样的,还有不太为人所熟知的包装类,比如Integer,都是这样的;
//举个非线程安全的例子
public class Location{
private double a;
private double b;
public Location (double a,double b){
this.a=a;
this.b=b;
}
public double getA(){
return a;
}
public double getB(){
return b;
}
public void setAB(double a.double b){
this.a=a;
this.b=b;
}
}
当系统接收到一个新的坐标数据的时候,需要的调用setAB来更新位置信息,而setAB又是非线程安全的,因为A和B并不是原子操作,setAB被调用的时候,如果在A写入完成,而B开始写之前有其他线程读取数据,则有可能读取的是脏数据;
//状态不可变
public final class Location {
public final double a;
public final double b;
public Location(double a,double b){
this.a=a;
this.b=b;
}
}
解剖不可变模式的骨架
不可变模式把现实世界中状态可变的实体建模为状态不可变对象,并通过创建不同的状态不可变的对象来反映实体的状态的变化;
这是不可变模式的类图:
ImmutableObject:负责存储一组不可变状态,该参与者不对外暴露任何可以修改其状态的方法;
getStateX(),getStateN():这些函数主要返回其所属的ImmutableObject实例所维护的状态相关变量的值,这些变量在对象实例化的时候通过其构造器的参数获得值;
getStateSnapshot():返回其所属的ImmutableObject实例维护的一组状态的快照;
Manipulator:负责维护ImmutableObject所建模的实体的状态的变更,当相应的实体的状态变化的时候,该参与者负责生成新的ImmutableObject的实例用来反映新的状态;
changeStateTo():根据新的状态值生成新的ImmutableObject实例:
解释一下这张图(因为空间不够就分成两张来截取):第一步到第四步:客户端代码获取当前的ImmutableObect实例的各个 状态值;
第五步:客户端代码调用Manipulator的changeStateTo()函数来更新应用的状态;
第六步到第七步:changeStateTo()函数创建新的ImmutableObect实例反映应用的新状态并返回;
第八步到第九步:客户端代码获取新的ImmutableObect实例的状态快照;
不可变对象的使用主要是这几种类型: 1.获取单个状态的值:通过调用不可变的对象的相关的getter函数来实现;
2.获取一组状态的快照:不可变对象可以提供一个getter函数,这个函数需要对其返回值做防御性的复制或者是返回一个只读的对象,主要是为了避免其状态对外泄漏而被改变;
3.生成新的不可变的对象的实例:当被建模对象的状态发生改变的时候,通过创建新的不可变对象实例来反映;
一个不可变对象必须要满足以下条件:第一:类本身必须使用final来进行修饰,主要是为了防止子类改变其定义的行为;
第二:所有的属性必须使用final来进行修饰,这里面还有一层考虑就是多线程环境下JMM被修饰字段所引用的对象的初始化安全,也就是final修饰的字段在其他线程可见时,他必须是初始化的;
第三:在对象的创建过程中,this关键字不能泄漏给其他的类,主要是为了防止以其他的类在对象创建的过程中修改其状态;
第四:任何一个属性,如果应用了其他状态可变的对象(数组,集合等),则必须用private来修饰,如果有相关的方法返回属性的值的话,那就必须进行防御性复制;
不可变对象模式适用的场景:
1.被建模对象的状态变化不频繁;2.同时对一组相关的数据进行写操作(要保持原子性!);3.使用某个对象作为安全的HashMap的Key;
不可变对象使用的时候需要注意的问题:
1.被建模对象的状态变更比较频繁;
2.使用的等效或者近似的不可变对象;
3.防御性复制;
总结
这就是不可变对象模式,下一篇讲Guarded Suspension(保护性暂挂)模式;