大家好,今天第一次写设计模式文章,有点不知道怎么开始,我对面向对象的设计模式学习的还很粗浅,希望大家不要见笑。今天主要讲一下单例模式(Singleton)

先看看设计模式这本书对Singleton的定义吧:确保每个类只有一个实例,并提供它的全局访问点。

它可以解决什么问题,或者换句话说,我们使用它的动机是什么? 几乎在每个应用程序中,都需要有一个从中进行全局访问和维护某种类型数据的区域。 在面向对象的系统中也有这种情况,在此类系统中,在任何给定时间只应运行一个类或某个类的一组预定义数量的实例。 例如,当使用某个类来维护增量计数器时,此简单的计数器类需要跟踪在多个应用程序领域中使用的整数值。 此类需要能够增加该计数器并返回当前的值。 对于这种情况,所需的类行为应该仅使用一个类实例来维护该整数,而不是使用其它类实例来维护该整数。

最初,人们可能会试图将计数器类实例只作为静态全局变量来创建。 这是一种通用的方法,但实际上只解决一部分问题;它解决了全局可访问性问题,但没有采取任何措施来确保在任何给定的时间只运行一个类实例。 应该由类本身来负责只使用一个类实例,而不是由类用户(注:此用户是指使用这个类的人或者其它的类)来负责,也就是说负责只能产生一个实例的责任在于那个被产生的实例,而不能是使用这个类的用户, 应该始终不要让类用户来监视和控制运行的类实例的数量。这里说明一下,有的朋友会说我能够保证我在我的系统中我只实例化一次。我不需要对被调用类又任何的处理,这样肯定是不行的。

所需要的是使用某种方法来控制如何创建类实例,然后确保在任何给定的时间只创建一个类实例。 这会确切地给我们提供所需的行为,并使客户端不必了解任何类细节。

以下是Singleton的逻辑模型


Singleton 模型非常简单直观。 (通常)只有一个 Singleton 实例。 客户端通过一个已知的访问点来访问 Singleton 实例。 在这种情况下,客户端是一个需要访问唯一 Singleton 实例的对象。

以下是Singleton的物理模型


Singleton 模式的物理模型也是非常简单的。 但是,随着时间的推移,实现 Singleton 的方式也略有不同。 让我们看一下原始的 GoF Singleton 实现。

我们看到的是一个简单的类图表,显示有一个 Singleton 对象的私有静态属性以及返回此相同属性的公共方法 Instance()。 这实际上是 Singleton 的核心。 还有其他一些属性和方法,用于说明在该类上允许执行的其他操作。 为了便于此次讨论,让我们将重点放在实例属性和方法上。

客户端仅通过实例方法来访问任何 Singleton 实例。 此处没有定义创建实例的方式。 我们还希望能够控制如何以及何时创建实例。 在 OO 开发中,通常可以在类的构造函数中最好地处理特殊对象的创建行为。 这种情况也不例外。 我们可以做的是,定义我们何时以及如何构造类实例,然后禁止任何客户端直接调用该构造函数。 这是在 Singleton 构造中始终使用的方法。

代码实例前,我还是先说一下Singleton的几个要点吧:

1)Singleton中的实例构造器(构造方法)可以声明为protected保护类型,允许子类的派生。
2)Singleton不要实现ICloneable接口,以免实例被克隆。
3)Singleton不要支持序列化,以免被反序列化实例。
4)在多线程环境下使用,可能会导致多个实例。

 1class Singleton
 2{
 3    private static Singleton instance = null;
 4    public static Singleton Instance 
 5    {
 6        get
 7        {
 8            if ( instance == null ) 
 9            {
10                 instance = new Singleton();
11             }

12            return instance;
13        }
            
14    }

15    private Singleton() {}
16}



让我们先花点时间分析一下此代码。 该简单类有一个私有静态变量,此变量是指向该类自身。 注意,构造函数是受保护的,并且只有一个公共属性。 在属性实现中,有一个控制块 (if),它检查成员变量是否已初始化,如果没有的话,则创建一个新实例。 控制块中这种惰性初始化意味着仅在第一次调用 Instance() 方法时初始化或创建 Singleton 实例。 对于很多应用程序,这种方法效果很好。 但对于多线程应用程序,这种方法证明具有潜在危险的副作用。 如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。比如说当第一个线程进入判断结果是null,这时第二个线程也进入了判断,由于第一个线程刚刚判断完,还没有进行创建实例,第二个线程也得到了null的结果,所以两个线程都会创建实例。这个和刚刚的第四个要点正好吻合,那么难道就没有办法解决都线程的问题了吗?答案当然是可以解决。
 

 1class Singleton
 2{
 3    private static volatile Singleton instance = null;
 4    private static object lockObject = new object();
 5    public static Singleton Instance 
 6    {
 7        get
 8        {
 9            if ( instance == null ) 
10            {
11                lock( lockObject )
12                {
13                    if( instance == null )
14                    {
15                     instance = new Singleton();
16                    }

17                }

18             }

19            return instance;
20        }
            
21    }

22    private Singleton() {}
23}


看一下上面的代码,首先的改变是加了一个静态变量lockObject,就是一个object实例,第9行,判断后首先先使用lock方法(线程技术相关,不多作解释)第11行锁住了线程,第13行再次判断。做了一个双检查(double check)。

再来看一下第3行有的静态变量是加了一个volatile 关键字,因为.NET编译器会对我们的程序进行重新排序,是为了优化,volatile 关键字告诉编译器不要对代码重新排序,并且放弃优化。

目前为止我们已经解决了多线程的问题。但是Microsoft .NET 框架中有更好的解决方案解决所有这些问题,并更易于实现 Singleton,却不会产生我们目前讨论的不利副作用。Singleton 代码变为以下内容:

1sealed class Singleton
2{
3  private Singleton() {}
4  public static readonly Singleton Instance = new Singleton();
5}

此版本已大大简化并且更加直观。 它仍然是 Singleton 吗? 让我们看一下更改了哪些内容,然后再做决定。 我们修改了要密封的类本身(该类密封后是不可继承的),删除了惰性初始化代码,删除了 Instance 属性,并且对 instance 变量做了大量的修改。 对 instance 变量所做的更改包括修改对公共方法的访问级别,将变量标记为只读,以及在声明时初始化该变量。

关于解决缓式加载(惰性初始化)的问题.NET类中的的静态变量的声明必须要经过一个静态的构造器,它会做到惰性初始化,只要你访问Instance变量就会进入静态构造器,上面的那段代码等于下面的这段代码:

 1sealed class Singleton
 2{
 3  private Singleton()
 4  {
 5  }

 6  public static readonly Singleton Instance;
 7  static Singleton()
 8  {
 9    Instance = new Singleton();
10  }

11}

那么,线程安全呢?其实.NET框架也解决了这一问题,因为即使在多线程环境下.NET框架只允许一个线程执行静态构造器,也就是说关于多线程问题,.NET机制已经免费帮我们解决了。

相应的类实现代码以及示例客户端使用如下所示:

 1sealed class SingletonCounter 
 2{
 3  public static readonly SingletonCounter  Instance = new SingletonCounter();
 4  private long Count = 0;
 5  private SingletonCounter() {}
 6  public long NextValue() 
 7  {
 8   return ++Count;
 9  }

10}

11
12class SingletonClient 
13{
14  [STAThread]
15  static void Main() 
16  {
17    for (int i=0; i<20; i++
18    {
19      Console.WriteLine("Next singleton value: {0}",
20      SingletonCounter.Instance.NextValue());
21    }

22  }

23}

24

我们还创建了一个 Singleton 类来维护具有 long 类型的增量计数。 客户端是一个简单的控制台应用程序,它显示计数器类的 20 个值。 虽然此示例极其简单,但它却说明了如何使用 .NET 来实现 Singleton,然后将其用在应用程序中。

小结:Singleton 设计模式是一个非常有用的机制,可用于在面向对象的应用程序中提供单个对象访问点。 无论使用的是什么实现,该模式提供一个大家所熟知的概念,以便其在设计和开发小组之间方便地进行共享。 但是,正如我们所发现的一样,注意到这些实现有多大差异及其潜在的副作用也是非常重要的。 .NET 框架为模式实现者在设计所需的功能类型方面提供了很大的帮助,实现者无需处理本文中所讨论的很多副作用。 在正确实现后,可以证实模式的最初目的的有效性。

下一章我讲会讲(AbstractFactory)抽象工厂模式,因为Singleton模式的代码不是很多,所以没有单独用VS.NET做,下面可能会给出用VS.NET做出的例子。谢谢大家的关注。

posted on 2006-05-22 15:26  胡嘉  阅读(1272)  评论(1编辑  收藏  举报