上一章,我们学习了设计模式的概念,以及为什么要学习设计模式,还有在进行系统设计时应当遵守的六大原则,本章我们就来开始一一的学习GOF当中的二十三钟设计模式。
我一会在思考如何去诠释这么多设计模式,因为网上有很多现成的,可供学习的资料,我在想有什么地方可以让各位跟着我的节奏去学习,而不是网上的那些资料,优势在哪里,思考很久,我觉得唯一的优势,或者说我能有的优势,就是简单通俗易懂。
遵循着中心思想通俗易懂,我们首先来回顾一下单例模式为何要出现,又或者说什么样的类可以做成单例的。
在我的工作过程中,我发现所有可以使用单例模式的类都有一个共性,那就是这个类没有自己的状态,换句话说,这些类无论你实例化多少个,其实都是一样的。
我稍微总结一下,一般最容易区别的地方就在于,这些类,都没有非静态的,可设置值的属性。
我来举个反面教材,即一个不能被做成单例模式的类。
[java]
public class NoSingleton {
private int changedField;
private static int staticChangedField;
private void setChangedField(int value){
this.changedField = value;
}
private static void setStaticChangedField(int value){
staticChangedField = value;
}
}
public class NoSingleton {
private int changedField;
private static int staticChangedField;
private void setChangedField(int value){
this.changedField = value;
}
private static void setStaticChangedField(int value){
staticChangedField = value;
}
} 上面这个类,有两个属性,一个是静态的,一个是非静态的,而且非静态的属性我们提供了set方法(暂且不考虑突破属性访问权限的反射机制),那么这个类就是具有自己的状态的,因为它有一个实例级别的变量changedField,这个变量在每个实例当中都有可能是不一样的。而静态的则不会有这种担忧,因为它是被这个类的所有实例共享的。
所以上述这个类不能做成单例的类,一般可以做成单例的类的样子都有以下特点。
1.所有的对外可变属性都是静态的。
2.所有的非静态属性都是私有的,并且没有对外公开的可以设置值的方法。
下面,我们就来看一下做成单例的几种方式。
首先,我们来看一下最标准也是最原始的单例模式的构造方式。
[java]
public class Singleton {
//一个静态的实例
private static Singleton singleton;
//私有化构造函数
private Singleton(){}
//给出一个公共的静态方法返回一个单一实例
public static Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public class Singleton {
//一个静态的实例
private static Singleton singleton;
//私有化构造函数
private Singleton(){}
//给出一个公共的静态方法返回一个单一实例
public static Singleton getInstance(){
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
} 这是在不考虑并发访问的情况下标准的单例模式的构造方式,这种方式通过几个地方来限制了我们取到的实例是唯一的。
1.静态实例,带有static关键字的属性在每一个类中都是唯一的,此为保证单例的最重要的一步。
2.限制客户端随意创造实例,即私有化构造方法。
3.给一个公共的获取实例的静态方法,注意,是静态的方法,因为这个方法是在我们未获取到实例的时候就要提供给客户端调用的,所以如果是非静态的话,那就变成一个矛盾体了,因为非静态的方法必须要拥有实例才可以调用。
4.判断只有持有的静态实例为null时才调用构造方法创造一个实例,否则就直接返回。
假如你去面试一家公司,给了你一道题,让你写出一个单例模式的例子,那么如果你是刚出大学校门的学生,你能写出上面这种示例,假设我是面试官的话,满分100的话,我会给90分,剩下的那10分算是给更优秀的人一个更高的台阶。但如果你是一个有过两三年工作经验的人,如果你写出上面的示例,我估计我最多给你30分,甚至心情要是万一不好的话可能会一分不给。
为什么同样的示例放到不同的人身上差别会这么大,就是因为前面我提到的那个情况,在不考虑并发访问的情况下,上述示例是没有问题的。
至于为什么在并发情况下上述的例子是不安全的呢,我在这里给各位制造了一个并发的例子,用来说明,上述情况的单例模式,是有可能造出来多个实例的,我自己测试了约莫100次左右,最多的一次,竟然造出了3个实例。下面给出代码,大约运行10次(并发是具有概率性的,10次只是保守估计,也可能一次,也可能100次)就会发现我们创造了不只一个实例。
[java]
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestSingleton {
boolean lock ;
public boolean isLock() {
return lock;
}
public void setLock(boolean lock) {
this.lock = lock;
}
public static void main(String[] args) throws InterruptedExcept