设计模式之单例模式
创建型模式将对象的创建和使用分离,在使用对象时无需关注对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。
模式名称 | 定义 | 学习难度 | 使用频率 |
单例模式 | 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 | 一颗星 | 四颗星 |
简单工厂模式 | 定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类 | 两颗星 | 三颗星 |
工厂方法模式 | 定义一个用于创建对象的接口,让子类决定将哪一个类实例化 | 两颗星 | 五颗星 |
抽象工厂模式 | 提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类 | 四颗星 | 五颗星 |
原型模式 | 使用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象 | 三颗星 | 三颗星 |
建造者模式 | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 | 四颗星 | 两颗星 |
一、单例模式定义:确保某一个类只有一个实例,可以自行实例化并向整个系统提供这个实例,这个类成为单例类,它提供全局访问的方法。
二、实例:
1 class TaskManager 2 { 3 private static TaskManager tm = null; 4 5 private TaskManager(){...} //构造方法 6 public void displayProcess(){...} //自定义方法 7 public void displayServices(){...} //自定义方法 8 9 public static TaskManager GetInstance() 10 { 11 if(tm == null) 12 tm = new TaskManager(); 13 return tm; 14 } 15 }
1. 为了确保TaskManager实例的唯一性,需要禁止类的外部直接使用new来创建对象,因此需要将TaskManager的构造函数的可见性改为private。
2. 外部想要得到TaskManager类的实例,需要通过方法GetInstance(),获得TaskManager内部创建的实例tm。
3. 由于类外无法创建TaskManager对象,故需将GetInstance()定义为静态方法,方便类外可以直接通过类名访问。
4. 第一次调用GetInstance()方法时将创建唯一实例,再次调用时将返回第一次创建的实例。
三、饿汉式单例和懒汉式单例
1. 饿汉式单例:当类被加载时,静态变量tm会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
1 class TaskManager 2 { 3 private static TaskManager tm = new TaskManager(); 4 5 private TaskManager(){...} 6 public void displayProcess(){...} 7 public void displayServices(){...} 8 9 public static TaskManager GetInstance() 10 { 11 return tm; 12 } 13 }
2. 懒汉式单例:即在第一次调用GetInstance方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例。
为了避免多个线程同事调用GetInstance方法,可以使用锁。
class LazySingleton { private static LazySingleton instance = null; private object SyncSelectObject = new object(); private LazySingleton(){} public static LazySingleton GetInstance() { lock(SyncSelectObject ) { if (instance == null) { instance = new LazySingleton (); } return instance; } } }
上述代码虽然解决了线程安全问题,但是每次调用GetInstance时都需要进行线程锁定判断,将会导致系统性能大大降低。
饿汉式单例在类被加载时就将自己实例化,它的优点在于无序考虑多线程访问问题,可以确保实例的唯一性。
- 从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,隐藏要优于懒汉式单例。
- 但在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,且系统加载时间可能会比较长。
懒汉式单例实现了延迟加载,但必须处理好多个线程同时访问的问题,这意味着出现多线程同时首次引用此类的概率变的较大,需要通过加锁进行控制,这将导致系统性能受到一定影响。
四、IoDH(Initialization on Demand Holder)技术
IoDH能够将两种单例的缺点都克服,而将两者的有点合二为一。
实现IoDH时,需在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance方法返回给外部使用。
class Singleton { private Singleton(){} private static class HolderClass { private final static Singleton instance = new Singleton(); } private static Singleton GetInstance() { return HolderClass.instance; } public static void main(String args[]) { Singleton s1,s2; s1 = Singleton.GetInstance(); s2 = Singleton.GetInstance(); System.out.println(s1 == s2);//true } }
由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton。第一次调用GetInstance时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量。由于GetInstance()方法没有被任何线程锁定,因此其性能不会造成任何影响。