单键模式

一、单件模式

本文试图描述单件模式在C#中的实际用法。由于理解上的偏差,在此仅描述我的观点,以及分析。
前几天写的该问有错误遗漏的地方,在此感谢指正。2006年6月15日

一、单件模式的意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。

我对这句话的理解是该类只能被实例化一次,然后提供一个全局的访问,在以后若干次的访问中,所有的访问都是访问 该全局的访问,而不再次进行实例化。

前段时间我把这个观点发上来以后,很多人都建议我去看看其他人写的设计模式(在此对这些温和的批评表示感谢,也坚定我更深入研究的决心)。争议主要是类是否只能实例化一次。因为单件模式的意图中从来没有说明单件模式是否只能实例化一次,而且我在很多设计模式的文章中都没有看到这些字眼。但是经过我仔细分析了很多实例,我认为实现单件模式,实际上也就是如何保证需要使用单件模式的类在生命周期内只实例化一次。

二、单件模式的主要示例
我将会使用到2书上和网上都有流行的例子。

示例一:
第一个实例就用我看的书《C#设计模式》中的第一个例子。

using System;

namespace GlobalSpooler
{
    
/// <summary>
    
/// Summary description for Spooler.
    
/// </summary>

    public class Spooler     {
        
private static bool instance_flag= false;
        
private Spooler()  {
        }

        
public static Spooler getSpooler() {
            
if (! instance_flag) 
                
return new Spooler ();
            
else
                
return null;
        }


    }

}
using System;

namespace GlobalSpooler
{
    
/// <summary>
    
/// Summary description for Class1.
    
/// </summary>

    class GlobSpooler
    
{
        
static void Main(string[] args)         {
            Spooler sp1 
= Spooler.getSpooler();
            
if (sp1 != null)
                Console.WriteLine (
"Got 1 spooler");
            Spooler sp2 
= Spooler.getSpooler ();
            
if (sp2 == null)
                Console.WriteLine (
"Can"'t get spooler");


            
//fails at compile time
            Spooler sp3 = new Spooler ();
        }

    }

}


这个是书上的实例。分析下程序是如何执行的:
1、调用程序入口点Main
2、调用Spooler类的静态方法Spooler.getSpooler();
3、因为instance_flag = false ,所以 返回
new Spooler (); ——实际上就是 Spooler sp1 = new Spooler ();
4、输出部分(在这里是用于调试)。这里输出了Got 1 spooler
5、  Spooler sp2 = Spooler.getSpooler ();的时候通过分析可以看出Spooler sp2 = new Spooler (); 这一事实。先放一边。
6、由于sp2!=null,因此没有输出。
7、Spooler 的构造函数是private,所以prooler sp3 = new Spooler ();产生错误。

实际上这个例子是和我的论点相冲突的。因为这个例子里类被实例化了2次。为了验证,我给程序加上了
if(sp1==sp2)
          Console.WriteLine ("They are same");
else
          Console.WriteLine ("They are not same");
判断以后发现sp1和sp2并不相等!所以书上的例子是错的!
要使sp1==sp2,那么sp1和sp2应该是同一个对象,而不应该实例化2次。

如果对程序进行修改,那么可以修改成

using System;

namespace GlobalSpooler
{
    
/// <summary>
    
/// Summary description for Spooler.
    
/// </summary>

    public class Spooler     {
        
private static Spooler MySpooler ;
        
private static bool instance_flag= false;
        
private Spooler()  {
            instance_flag 
= true;
        }

        
public static Spooler getSpooler() {
            
if (! instance_flag) 
                MySpooler 
= new Spooler ();
             
return MySpooler;
        }


    }

}

添加了全局访问对象MySpooler ,构造函数中添加了instance_flag=true; 虽然MySpooler 是私有的,但是访问过程中:instance_flag为false的时候MySpooler 成为类Spooler的实例,在以后的访问中将仅仅使用该实例而不再次实例化。看看示例二就是这样的。

示例二:

 1public sealed class Spooler 
 2{
 3    static Spooler instance=null;
 4
 5    private Spooler()
 6    {
 7    }

 8
 9    public static Spooler Instance
10    {
11        get
12        {
13            if (instance==null)
14            {
15                instance = new Spooler();
16            }

17            return instance;
18        }

19    }

20}

这个例子就比较清晰了,可以看出程序怎么执行只实例化一次该类,以后一直返回私有静态字段instance。
由于网上关于单件模式多线程的例子讲了很多,这里就不再敷述。
三、单件模式的特点
我觉得到现在再来讨论单件模式的特点才更加深刻。

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其它对象提供这一实例。

    以上特点摘自  吕震宇 老师的 C#设计模式(7)-Singleton Pattern

    单例类只能有一个实例——虽然没有说到类只能实例化一次,但是我认为在一个生命周期内是只能实例化一次的。
    单例类必须自己创建自己的唯一实例——意思和上句差不多,只是只能由自己创建。
    单例类必须给所有其它对象提供这一实例——这就不多解释了。

    我以前就是忽略了第二条,呵呵。这里讲下为什么需要自己创建自己的实例。我是这么理解的:如果不是由自己创建自己的实例,那么创建实例的方法是对外开放的,不遵守开发—关闭原则。至于为什么要有这个原则?答案是——这是大家在设计过程中的经验。当然你不遵守这个原则也能写出程序,但是你假设使用单件模式,你在类外部实例化类,那么你要随时注意该类在其它地方不能实例化,而且在实例化的地方使用的代码只执行一次实例化。

    四、C#中的单件模式
    以下摘自 吕震宇 老师的 C#设计模式(7)-Singleton Pattern 以便保持本文的完整。

    C#的独特语言特性决定了C#拥有实现Singleton模式的独特方法。这里不再赘述原因,给出几个结果:

    方法一:

    下面是利用.NET Framework平台优势实现Singleton模式的代码:

    sealed class Singleton
    {
       
    private Singleton();
       
    public static readonly Singleton Instance=new Singleton();
    }

    这使得代码减少了许多,同时也解决了线程问题带来的性能上损失。那么它又是怎样工作的呢?

    注意到,Singleton类被声明为sealed,以此保证它自己不会被继承,其次没有了Instance的方法,将原来_instance成员变量变成public readonly,并在声明时被初始化。通过这些改变,我们确实得到了Singleton的模式,原因是在JIT的处理过程中,如果类中的static属性被任何方法使用时,.NET Framework将对这个属性进行初始化,于是在初始化Instance属性的同时Singleton类实例得以创建和装载。而私有的构造函数和readonly(只读)保证了Singleton不会被再次实例化,这正是Singleton设计模式的意图。
    (摘自:http://www.cnblogs.com/huqingyu/archive/2004/07/09/22721.aspx

    不过这也带来了一些问题,比如无法继承,实例在程序一运行就被初始化,无法实现延迟初始化等。

    详细情况可以参考微软MSDN文章:《Exploring the Singleton Design Pattern》

    方法二:

    既然方法一存在问题,我们还有其它办法。

    public sealed class Singleton
    {
      Singleton()
      
    {
      }


      
    public static Singleton GetInstance()
      
    {
        
    return Nested.instance;
      }

        
      
    class Nested
      
    {
        
    // Explicit static constructor to tell C# compiler
        
    // not to mark type as beforefieldinit
        static Nested()
        
    {
        }


        
    internal static readonly Singleton instance = new Singleton();
      }

    }

    这实现了延迟初始化,并具有很多优势,当然也存在一些缺点。详细内容请访问:《Implementing the Singleton Pattern in C#》。文章包含五种Singleton实现,就模式、线程、效率、延迟初始化等很多方面进行了详细论述。

  • posted on 2006-06-15 10:02 BirdsHover 阅读(2112) 评论(15)  编辑  收藏 所属分类: Desin Patterns

    评论

    #1楼  2006-06-13 12:14 aaaaaaaaaa [未注册用户]

    我一直以为单件模式应该是这样的

    using System;

    namespace singleSpooler
    {
    /**//// <summary>
    /// Prototype of Spooler Singleton
    /// such that only one instane can ever exist.
    /// </summary>
    public class Spooler
    {
    private static Spooler instance = null;
    private Spooler()
    {
    }

    public static Spooler Instance
    {
    get
    {
    if (null == instance)
    {
    instance = new Spooler();
    }
    return instance;
    }
    }
    }
    }
      回复  引用  查看    

    #2楼 [楼主] 2006-06-13 12:54 谢平

    你那也是啊,换个检查的static 字段而已   回复  引用  查看    

    #3楼  2006-06-13 12:59 zwwon

    “以上实现单件模式都使用静态字段。其实只要公用的会话都可以完成该功能。”
    用Static就破坏了单件模式的初衷,
    单件模式原则是保证仅有一个实例,这里的仅有一个不是仅出现一次,而是不管实例化多少次,都保证是同一个对象!

    事实上,NET Framework中string 类就是单件模式应用的范例。   回复  引用  查看    

    #4楼  2006-06-13 13:12 ChuPaChuPs [未注册用户]

    这是什么。。。没看懂。。。   回复  引用  查看    

    #5楼  2006-06-13 13:14 brightheroes [未注册用户]

    LZ看的什么书,单件模式不应该是只能访问一次……
    这个书有严重问题   回复  引用  查看    

    #6楼 [楼主] 2006-06-13 13:55 谢平

    @brightheroes
    @zwwon

    我没有说单件模式就是只能访问一次
    我是认为在单件模式中 类只能实例化一次和类被实例化N次但只有一次执行的步骤和其它的不一样 就属于单件模式

      回复  引用  查看    

    #7楼  2006-06-13 15:46 laserman [未注册用户]

    LZ看的书有问题,推荐《设计模式》   回复  引用  查看    

    #8楼  2006-06-13 17:24 NeedForSleep

    园子里有很多高手写过设计模式,建议LZ看看。

    对于硬件的控制我常常使用单件模式,比如对COM之类的端口访问。

    可是,按照你这个思路,我的两个窗体同时共享访问同一个硬件的话,就不行了。   回复  引用  查看    

    #9楼  2006-06-15 10:55 TerryLee

    1.“所有的访问都是访问 该全局的访问”这句话比原来的意思更难懂;
     
    2.示例一有很多的问题,建议LZ看看原版的设计模式这本书;
     
    3.修改后的代码:
    using System;

    namespace GlobalSpooler
    {
        
    /**//// <summary>
        
    /// Summary description for Spooler.
        
    /// </summary>

        public class Spooler     {
            
    private static Spooler MySpooler ;
            
    private static bool instance_flag= false;
            
    private Spooler()  {
                instance_flag 
    = true;
            }

            
    public static Spooler getSpooler() {
                
    if (! instance_flag) 
                    MySpooler 
    = new Spooler ();
                 
    return MySpooler;
            }


        }

    }
    更复杂了,而且还有问题,引入了不必要的辅助变量!
      回复  引用  查看    

    #10楼 [楼主] 2006-06-15 11:53 谢平

    @TerryLee
    我确实是想写得更加容易理解点,有的词我会在想到更好的描述的时候更改一下

    示例一,我是根据原书改的,为了过渡不是很大才加的辅助变量。改完后你说的问题可能是多线程问题,我看网上很多文章都讲得很好,就象你写的里面的就很好就不再多说了,呵呵。

    我只是论证一下我的想法。   回复  引用  查看    

    #11楼  2006-06-16 09:39 SHY520

    原版的设计模式书可有下载的?   回复  引用  查看    

    #12楼  2006-06-16 13:43 TerryLee

    @SHY520
    有电子版下载的,PDF格式

    网上找一下有很多   回复  引用  查看    

    #13楼  2006-06-16 16:39 ew [未注册用户]

    ..   回复  引用  查看    

    #14楼  2006-06-25 12:13 Richard wu [未注册用户]

    单间模式是不是初始化一次要从两个角度来看,对类的使用者来说,可以初始化N次,他是看不见怎么初始化以及其过程的;对类的设计者来说,不管使用者怎么访问只初始化一次,同时不管要注意单进程的情况,还要注意多进程时单件模式的使用,否则,可能破坏单件模式的效果   回复  引用  查看    

    posted on 2007-12-25 16:22  RIVERSPIRIT  阅读(585)  评论(0)    收藏  举报