这一讲主要讲的是创建型模式中的单例模式
首先说明了模式的分类
从目的来看:
- 创建型(Creational)模式:负责对象创建。
- 结构型(Structural)模式:处理类与对象间的组合。
- 行为型(Behavioral)模式:类与对象交互中的职责分配。
从范围来看:
- 类模式处理类与子类的静态关系。
- 对象模式处理对象间的动态关系。
在设计模式中为什么会存在单例模式?
在软件系统中,有时候需要一个类在系统中只存在一个实例,才能保证逻辑的正确性和良好的效率。
在Gof中是这样描述单例模式的意图保证一个类仅有一个实例,并提供一个该实例的全局访问点。
首先来看单线程的单例模式的实现方式



























这是一个单例模式的例子。在这里,我们将默认构造函数设置私有的,如果我们不设置这个私有的构造函数,编译器将为我们创建一个默认的公开的默认构造函数,那么就可以在外部实例化,所以我们要将默认构造函数私有化。在Instance的属性中,我们判断instance是否为空,如果为空,那么就实例化,不为空,那么我们就直接返回,这要保证Instance属性被调用多次,返回的始终是一个实例。如果在c++中,instance是一个指针,并且需要在类外声明并初始化它。在.net中我们还可以这样来实现一个Singleton:





















这种方式也实现了单例模式,但在c++中是不可行的。因为c++在不支持成员变量在声明时就初始化。
单线程单例模式的几个要点:
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不要支持ICloneable接口,因为这可能会导致多个对象实例,与Singleton模式的初衷违背。
- Singleton模式一般不要支持序列化,因为这也有可能导致多个对象实例,同样与Singleton模式的初衷违背。
- Singletom模式只考虑到了对象创建的管理,没有考虑对象销毁的管理。就支持垃圾回收的平台和对象的开销来讲,我们一般没有必要对其销毁进行特殊的管理。因为只有一个实例。在C++中,我们随时可以销毁单例模式产生的一个实例。但是这样也带来了一个问题,就是我销毁以后,在调用GetInstance得到一个实例,这个实例与上一个实例,不是同一个实例了。所以最好是在程序结束运行时销毁单例。
- 不能应对多线程环境:在多线程环境下,使用Singleton模式仍然有可能得到Singleton类的多个实例对象
多线程的单例模式:



































volatile 关键字表示字段可能被多个并发执行线程修改。声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。这样可以确保该字段在任何时间呈现的都是最新的值。
在代码中我们看到了检查了两次instance是否为空,这个是Double Check,为什么要进行Double Check?假设有两个线程同时调用单例模式的Instance属性,那么这时候都检测到instance为空,然后有一个线程A锁住标记,另一个线程B阻塞。如果在lock里没有再进行一次判断,当线程A释放锁,那么线程B将会得到一个新的Instance实例,这就与单例模式相冲突了。
多线程的单例模式在.net中还有一种实现方式












它等同于下面的实现

















readonly关键字,表明了这个变量是初始化后是只读的,不可在更改。那么static构造函数保证了在使用类的静态字段的时候就被调用。不实用静态变量就不会调用static构造函数,并且staic构在函数保证了在多线程环境下只调用一次,不会被多次调用,这是.net的机制保证的。但是带readonly的单例模式不支持带参数构造函数,staic构造函数不支持参数并且也是私有的,外表无法显示调用。当然可以用变通的方法来解决这个问题。可以添加一个方法比如叫InitSington,带有参数,在的到单例的时候马上调用InitSingleton来初始化单例的内部成员。
Singleton的模式扩展
- 将一个实例扩展到n个实例,例如对象池的实现。可以通过添加计数器来控制,或者提供一个方法,调用这个方法就实例化n个实例,然后再不会创建实例。
- 将new 构造器的调用转移到其他类中,例如多个类协同工作环境中,某个局部环境只需要拥有某个类的一个实例。如.net中同一类型多个实例调用GetType()得到的Type类型是一个实例。我简单的模拟了一下实现方式
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;
namespace ConsoleApplication3
...{
class Program
...{
static void Main(string[] args)
...{
MyClassA a1 = new MyClassA();
MyClassA a2 = new MyClassA();
MyClassB b1 = new MyClassB();
if (a1.GetMyType() == a2.GetMyType())
...{
Console.WriteLine("a1 equals a2");
}
else
...{
Console.WriteLine("a1 does not equal a2");
}
if (a1.GetMyType() != b1.GetMyType())
...{
Console.WriteLine("a1 does not equal b1");
}
else
...{
Console.WriteLine("a1 equals b1");
}
Console.WriteLine(a1.GetMyType().TypeName);
Console.WriteLine(b1.GetMyType().TypeName);
Console.Read();
}
}
public class MyType
...{
private string m_TypeName;
public MyType(string typeName)
...{
m_TypeName = typeName;
}
public string TypeName
...{
get
...{
return m_TypeName;
}
}
}
public class MyObject
...{
private static Dictionary<string, MyType> myTyp = new Dictionary<string,MyType>();
protected string MyTypeName;
public MyType GetMyType()
...{
if(myTyp.ContainsKey(MyTypeName))
...{
return myTyp[MyTypeName];
}
else
...{
myTyp.Add(MyTypeName, new MyType(MyTypeName));
}
return myTyp[MyTypeName];
}
}
public class MyClassA : MyObject
...{
public MyClassA()
...{
base.MyTypeName = "MyClassA";
}
}
public class MyClassB : MyObject
...{
public MyClassB()
...{
base.MyTypeName = "MyClassB";
}
}
}
当然Type类的实现方式肯定跟我这个不一样,我这里只是简单的模拟
- 理解和扩展Singleton模式的核心是"如何控制用户使用new对一个类的实例构造器的任意调用"。