设计模式:单件模式(Singleton Pattern)

 

    单件模式(Singleton Pattern)是一种用于确保整个应用程序中只有一个类实例且这个实例所占资源在整个应用程序中是共享时的程序设计方法(根据实际情况,可能需要几个类实例)。在某种程度上说,单件模式是限制而不是改进一个类的创建,但他却和其他创建模式分在一个组。

     单件模式可以保证一个类有且只有一个实例,并且提供一个访问他的全局访问点。在实际应用中有很多这样的情况,例如:窗口管理器,打印机或者一个COM1实例。

 

创建单件的方法

 

    让一个类只有一个实例有很多方法,最容易的方法是在类中设置一个静态变量,并且提供一个得到该实例对象的方法,在第一次实例对象的时候初始静态变量,以后再次实例化该类时都要判断是否初始化,因为静态变量只能有一个实例。为了防止多次实例化,把类的构造函数设置为私有属性。看下面的例子:

  public  class Spooler
        {
             private  static  bool instance_flag =  false// 静态变量,标识是否实例化该类
             private Spooler()
            { }



             ///   <summary>
            
///  提供静态获取该类对象的方法
            
///  如果已经有一个实例,再次请求返回null
            
///   </summary>
            
///   <returns></returns>
             public  static Spooler GetSpooler()
            {
                 if (!instance_flag)
                {
                     return  new Spooler();
                }
                 else
                {
                     return  null;
                }
            }

             public  void Something()
            { }
        }

这样做的好处是不用考虑异常,如果已经实例化过,返回一个空值而已。如果尝试直接创建对象编译器会提示构造函数私有,编译失败。调用就简单了

Spooler spooler = Spooler.GetSpooler();
spooler.Something();

Spooler spooler2= new Spooler(); // 提升错误:不可访问,因为它受保护级别限制 //

 如果需要实例化两个或者三个实例,修改静态变量值为整数,作为计数器可以了。

 

异常与实例

    上面的方法虽然可以实现单件模式,但有个缺点:需要程序员自己判断返回值是否为空。从程序设计角度来讲,这种情况应该尽量避免! 我们可以换一种方法来提示程序员已经实例过该对象。创建一个异常类,当多次实例化该类时,抛出一个异常,提升程序员采取行动。

 

         public  class Spooler
        {
             private  static  bool instance_flag =  false// 静态变量,标识是否实例化该类
             private Spooler()
            { }

             ///   <summary>
            
///  提供静态获取该类对象的方法
            
///  如果已经有一个实例,再次请求返回null
            
///   </summary>
            
///   <returns></returns>
             public  static Spooler GetSpooler()
            {
                 if (!instance_flag)
                {
                     return  new Spooler();
                }
                 else
                {
                     throw  new SingletonException( " 只能有一个实例 ");
                }
            }

             public  void Something()
            { }
        }

         public  class SingletonException : Exception
        {
             public SingletonException( string s) :  base(s) { 
            }
        }

 这样会等到我们想要的效果了。

 

还有一些补充 

     下面引用TerryLee写的关于线程安全方面一些补充,整理到一起方面学习。

     上面的写法不是线程安全的,当两个线程同时访问GetSpooler()方法,会得到两个Spooler实例,这样违背了我们的设计原则。可以用C#锁机制保证线程安全。

         public  class Spooler
        {
             private  static  readonly  object locker =  new  object(); 
             private  static  bool instance_flag =  false// 静态变量,标识是否实例化该类
             private Spooler()
            { }
             public  static Spooler GetSpooler()
            {
                 lock (locker)  // 锁定,保证线程安全
                {
                     if (!instance_flag)
                    {
                         return  new Spooler();
                    }
                     else
                    {
                         throw  new SingletonException( " 只能有一个实例 ");
                    }
                }
            }
        }

这种方式可以保证线程安全,因为当多个线程同时访问加锁对象时,只有一个线程可以进入。但是这种实现方式增加了额外的开销,损失了性能。

 

双重锁定

             public  static Spooler GetSpooler()
            {
                 if (!instance_flag)
                {
                     lock (locker)
                    {
                         if (!instance_flag)
                        {
                             return  new Spooler();
                        }
                         else
                        {
                             throw  new SingletonException( " 只能有一个实例 ");
                        }
                    }
                }
                 else
                     throw  new SingletonException( " 只能有一个实例 ");
            }

这种实现方式对多线程来说是安全的,同时线程不是每次都加锁,只有判断对象实例没有被创建时它才加锁,提高性能。不过对于这种情况我们有更好的解决方案。

 

静态初始化

public  class Spooler
{
    static  readonly Spooler instance =  new Spooler(); //线程安全
     static Spooler() 
    {
       
    }
    Spooler()
    {  }
     public  static Spooler GetSpooler()
    {
         return instance;
    }

对于该写法特点摘用TerryLee的原话:

在此实现中,将在第一次引用类的任何成员时创建实例。公共语言运行库负责处理变量初始化。该类标记为 sealed 以阻止发生派生,而派生可能会增加实例。此外,变量标记为 readonly,这意味着只能在静态初始化期间(此处显示的示例)或在类构造函数中分配变量。该实现与前面的示例类似,不同之处在于它依赖公共语言运行库来初始化变量。它仍然可以用来解决 Singleton 模式试图解决的两个基本问题:全局访问和实例化控制。公共静态属性为访问实例提供了一个全局访问点。此外,由于构造函数是私有的,因此不能在类本身以外实例化 Singleton 类;因此,变量引用的是可以在系统中存在的唯一的实例。由于 Singleton 实例被私有静态成员变量引用,因此在类首次被对 Instance 属性的调用所引用之前,不会发生实例化。这种方法唯一的潜在缺点是,您对实例化机制的控制权较少。在 Design Patterns 形式中,您能够在实例化之前使用非默认的构造函数或执行其他任务。由于在此解决方案中由 .NET Framework 负责执行初始化,因此您没有这些选项。在大多数情况下,静态初始化是在 .NET 中实现 Singleton 的首选方法。
 

延迟初始化

public  sealed  class Spooler
    {
        Spooler()
        {  }
         public  static Spooler Instance()
        {
             return Nested._instance;
        }
         public  static  string getStr() //不会初始化Spooler类
        {
             return ..............;
        }
         class Nested  // 所有初始化都在这儿
        {
             static Nested()
            { }
             internal  static  readonly Spooler _instance =  new Spooler();
        }
    }
从Singleton模式的实现和应用中也可以看出,优秀的设计模式往往都具有"简约之美"。它们采用一种"优雅"的方式,将那些成功的设计方法和体系结构能够得以被简单、方便地复用。这也是为什么现在的软件开发日益强调"设计模式"的原因之所在。
 
 
PS:学艺不精啊,写错了两个地方,改之

你可能感兴趣的:(Singleton)