设计模式----单例模式
创建型模式
单例模式是某个类只需要一个实例,保证一个类有且只有一个实例,并提供一个访问他的全局访问点。比如对于一个统一的数据库的访问,在整个项目中只使用同一个实例。对于这种情况有个比较好的例子,就是一夫一妻制。
比如某个男子需要娶个女子结婚,那么就有下面的程序:wife类,代表女子,husband类,代表男子
1
2
3
4
5
6
7
8
|
public class Wife { public void show() { System.out.println( "the girl show themself" ); } public void marry() { System.out.println( "the girl gets married" ); } } |
1
2
3
4
5
6
7
8
9
10
11
|
public class Husband { private Wife myWife = null ; public void chooseGirl() { myWife = new Wife(); myWife.show(); } public void getMarry() { myWife.marry(); } } |
在主程序里就可以实例化husband类,然后依次调用chooseGirl()和getmarry()方法,就可以正常运行。但是如果先调用getMarry()呢?由于wife在choose里面实例化的,这是就会报错,将husband类修改如下:
1
2
3
4
5
6
7
8
9
10
11
12
|
public void chooseGirl() { if (myWife == null ) { myWife = new Wife(); } myWife.show(); } public void getMarry() { if (myWife == null ) { myWife = new Wife(); } myWife.marry(); } |
这样一来就可以保证一个丈夫只能有一个妻子了。而且两个函数没有调用的先后关系。
问题一:如果婆婆想要看儿媳妇呢?那就会有一个婆婆类,婆婆类里面又需要实例化一个wife类,这个实例化的wife类和husband的里面实例化的wife类是不同的,出现了多个实例化的问题,也就是婆婆看到的和儿子娶的不是同一个姑娘,这怎么行呢?这就需要修改wife类的代码,保证只有一个实例出现。wife类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Wife { public static Wife wife; //静态变量 private Wife(){ //私有构造方法,是的外部无法访问 } public static Wife getInstance(){ //静态方法来实例化 if (wife == null ) { wife = new Wife(); } return wife; } public void show() { System.out.println( "the girl show themself" ); } public void marry() { System.out.println( "the girl gets married" ); } } |
将构造函数设为私有,声明静态变量wife和静态方法getinstance来进行实例化。在需要实例化的地方用Wife wife=Wife.getInstance()来获得实例,就可以保证大家看到的是同一个姑娘。
问题二:如果婚礼现场需要直播,怎么办?可以使用多线程技术,一个线程A作为实地的婚礼,在wife中调用marry方法,另一个线程B作为直播,同样在线程中调用marry的,那么怎么能确定getInstance得到的是同一个新娘呢?如果线程A先运行到此,这是wife为null,那么进入到分支实例化wife,在未完成实例化的时候,线程B也来到了分支内,此时wife依然为null,线程B又会进行一次实例化,最终还是产生了两个实例,就是两个不同的新娘。
解决:引入锁的概念,lock的作用就是构造出一块临界区来控制线程对代码的访问,确保每次只有一个线程运行到临界区的代码,其他线程运行到临界区时,将会一直等待直到前面的线程运行出临界区为止。
在wife中修改代码:在if前加锁
1
2
3
4
5
|
lock.lock(); if (wife == null ) { wife = new Wife(); } lock.unlock(); |
这样就能保证在A实例化之前B不会进行null的判断。这样虽然满足了功能的要求,可是每次调用的getInstance的时候都要进行加锁工作,这将会影响程序的性能。所以对其进行改良。
1
2
3
4
5
6
7
8
9
10
|
public static Wife getInstance(){ //静态方法来实例化 if (wife == null ) { lock.lock(); if (wife == null ) { wife = new Wife(); } lock.unlock(); } return wife; } |
在临界区内在进行一次null的判断,只有wife为null的时候才会进入初始化。
单例模式本身的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
public class Singleton { public static Singleton singleton; //静态变量 private Singleton(){ ^&&& //私有构造方法,是的外部无法访问 } public static Singleton getInstance(){ //静态方法来实例化 if (singleton == null ) { singleton = new Singleton(); } return singleton; } } |
客户端代码:
1
2
3
4
5
|
Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); if (singleton1 == singleton2){ System.out.printle( "the teo object are the same one" ); } |
单例模式虽然很简单,但很实用,以下情况都适合实用:
1)当类只能有一个实例而且第三方可以从一个公共的访问点访问它时
2)当一个唯一的实例可以通过子类化来扩展,而且第三方需要在不更改代码的情况下就能实用一个扩展的实例时。
单例模式优点:
1)队唯一的实例做出访问控制
2)允许改变实例的个数,可以增加一个计数器来控制实例的个数,从而有双例模式,三利模式等。
总结:
单例并不是上面写的这么简单,里面涉及到很多的知识点,多线程、加锁、私有构造函数,静态构造函数,静态字段,readonly和const的区别等等。
向这里给出的单例版本就涉及到线程安全的问题,当2个请求同时方式这个类的实例的时候,可以会在同一时间点上都创建一个实例,虽然一般不会出异常错误,但是起码不是我们谈论的只保证一个实例了。每一个设计模式都值得我们深入的研究下去,有时间一定回头看看。