最近C#版块中关于模式设计讨论比较多,偶也来参与参与,呵呵,与大家交流一下。本文就单例模式进行一些讨论,在实际应用中,单例模式是经常需要的一种模式,如在应用启动时,可以从数据库中读取一些配置,放在单例中,软件运行时,从单例中获取参数等,以及一些设备通讯控制的应用等。现假设有这样一个简单应用:
 
   从控制台读取一个字符串,然后将字符串打印在控制台上。定义两个类:
   
    public class TReader            // 负责从控制台上读取一串字符
    {
        public string Read()
        {
            Console.WriteLine("Please input a str:");
            return Console.ReadLine();
        }
    }    public   class TPrinter            // 将字符串打印在控制台上。
    {
        public void Print(string str)
        {
            Console.WriteLine("inputed:" + str);
        }
    }很简单,是吧, 但如果这里要求将TReader和TPrinter以单例模式运行,于是TReader的代码就变成了这个样子,TPrinter也类似.    public class TReader
    {
        public static TReader Instance
        {
            get
            {
                if (_Instance == null)
                {
                    _Instance = new TReader();
                }
                return _Instance;
            }
        }        private static TReader _Instance;
        public string Read()
        {
            Console.WriteLine("Please input a str:");
            return Console.ReadLine();
        }
    }    public   class TPrinter
    {
        public static TPrinter Instance
        {
            get
            {
                if (_Instance == null)
                {
                    _Instance = new TPrinter();
                }
                return _Instance;            }
        }        private static TPrinter _Instance;        public void Print(string str)
        {
            Console.WriteLine("inputed:" + str);
        }
    }
在每个类里各增加一个静态属性,我想说的问题就在这里,
1.单例模式是全局的行为,是类的外部应用,将这种静态属性设计在类里,显得很古怪。
2.两个类里的Instance实现代码几乎完全一模一样,代码重复,很不雅观!那么怎么改善这种结构呢,答案就是利用C#的 泛型,定义这样的一个泛型类,将Instance从TReader和TPrinter中提取出来 (含完整代码)。   public class G_<T> where T : class,new()
    {
        public static T Instance
        {
            get
            {
                if (_Instance == null)
                {
                    _Instance = new T();
                }
                return _Instance;
            }
            set
            {
                _Instance = value;
            }
        }
        private static T _Instance;
    }    public class TReader                // 负责从控制台上读取一串字符
    {
        public string Read()
        {
            Console.WriteLine("Please input a str:");
            return Console.ReadLine();
        }
    }    public   class TPrinter             // 将字符串打印在控制台上。
    {
        public void Print(string str)
        {
            Console.WriteLine("inputed:" + str);
        }
    }    class Program                    // 主程序
    {
        static void Main(string[] args)
        {
            string str = G_<TReader>.Instance.Read();   //从控制台上读取字符串
            G_<TPrinter>.Instance.Print(str);           // 将字符串打印在控制台上
            Console.ReadLine();
        }
    }
通过定义这样的一个泛型类,可实现所有类的单例模式,并且设计类时不再需要考虑
是不是要用到单例,提高了代码的可读性。
这样做实际上也有些缺点,如不能阻止在外部创建多个TPrinter或TReader的实例,一定
程度上违背了单例模式的原则。

解决方案 »

  1.   

    楼主提出了一种不错的思路,值得讨论。但是一般情况下,封闭性好的单例模式实现,应该把构造函数定义为私有,防止调用者绕过.Instance函数new对象。模板与封闭性似乎不能兼得啊。
      

  2.   


    对于有参数的类,需要在外部实例化,即在程序初始化时进行
    ....
    void AppInit()
    {
       G_<ClassName>.Instance = new ClassName( param1,param2);
    }....
      

  3.   

    楼主高手,我想请教一个与单例模式有关的问题。
    我新建一个winForm程序,出来默认产生的form1窗体,我有添加一个form窗体,我要把form2窗体做成单例窗体。代码如下:
     public partial class FrmSingleton : Form
        {
            public FrmSingleton()
            {
                InitializeComponent();
            }        private static FrmSingleton instance;        public static FrmSingleton Instance
            {
                get
                {
                    if (instance == null)
                        instance = new FrmSingleton();
                    return FrmSingleton.instance; 
                }
                
            }
        }
    然后我在form1中添加一个按钮,在click事件里添加如下代码:
    if (e.Button == MouseButtons.Right)
                    FrmSingleton.Instance.Show();
    来访问form2窗体做测试。
    运行后,在点击按钮,弹出form2没问题,然后关闭form2,form2窗体资源释放。
    这样我再点击按钮时,肯定不能弹出form2窗体。
    请问一下这个怎么解决?
      

  4.   

    楼上的问题最好是另开帖问,不过既然问了,俺就来按照俺的方法来解决下。
    设: Fom1 是主窗口,上面有一个按钮,按下之后,显示Form2.
    Form1中代码:    public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }       
            private void button1_Click(object sender, EventArgs e)
            {
                G_Form_<Form2>.Instance.Show();
                
            }
        }
    Form2.cs中代码 public partial class Form2 : Form
        {
            public Form2()
            {
                InitializeComponent();
            }    }    public class G_Form_<T> where T : Form, new()
        {
            public static T Instance
            {
                get
                {
                    if (_Instance == null)
                    {
                        _Instance = new T();
                        _Instance.FormClosed += new FormClosedEventHandler(_Instance_FormClosed);
                    }
                    return _Instance;
                }
                protected set
                {
                    _Instance = value;
                }
            }        static void _Instance_FormClosed(object sender, FormClosedEventArgs e)
            {
                _Instance = null;
            }
            private static T _Instance;
        }实际上每次调用都创建了一个新的窗口,因为俺没找到关闭窗体时不释放资源的方法,
      

  5.   

    override FormClosed
    {
        base.Hide();
    }
    拦截Closed,不要关闭,隐藏起来就可以了。当然,Show的时候也要做点事,例如初始化一些对象,变量,值,等。
      

  6.   


    你这样做有3个问题:
    1.泛型模板中的_instance = new T();代码要求类型必须具有默认无参数构造器,这样你不得不为每个具有参数构造器的类定义一个默认构造器。
    2.含参构造器的初始化G_<ClassName>.Instance = new ClassName( param1,param2);与其他构造器的单例引用
    方法不同,难以形成统一规范。况且程序初始化时实例化该类,一方面影响了程序加载性能,另一方面不一定做到"物有所需",即你的应用程序不一定会使用到该对象。甚至如果你的构造器参数包含其他对象,你不得不在之前初始化更多的对象。
    3.另附一个稍有点吹毛求疵的理由,不要介意。单一的Instance对象引用只能指向类的同一个实例对象,但如果你的应用程序同时需要一个类的两种实例版本,受单例的约束,你是不是不得不在单例模板中定义一个新的Instance2属性呢?当然,这种情况不是很多见,算是吹毛求疵吧。
      

  7.   

    1. 标准的单例模式中因为类的构造函数是私有的,所以也是不能带参数的。2. 程序初始化时实例化所有的全局对象应该是一种标准的做法。"先建立程序的结构,然后再让程序工作",按开放闭合原则(只增加代码,不修改已有代码),若需要增加或修改现有功能时,需先增加一个类,然后只修改初始化过程中的一处代码。3. 不需要修改现有的类,我要再增加一个泛型类    public class G2_<ClassName> : where T:class,new()
       {
           ...
       }
      

  8.   


     public class G2_<T> where T:class,new()
      {
      ...
      }
      

  9.   


    1.你这种泛型单例本来就不是标准的单例模式,是为了简化代码及编程推出的,既要利用泛型,又想控制私有函数,这是办不到的。况且"标准的"单例模式也确实允许构造一个带参数的构造函数。
    2.你没明白我的意思。我指的是应用程序的"按需加载",在需要该资源的位置创建它的实例。例如你的程序为了完成发邮件功能,可能需要某全局变量A,但用户可能根本不想发邮件,你在初始化整个应用程序的时候初始化这处资源就浪费了性能。
    3.增加新类的做法实际上也违反了程序设计的"dry原则"。
      

  10.   

    我提一个建议:将你的G_<T>的构造函数中传入一个匿名委托Fun<T>参数,由外部的匿名委托来返回一个对象的实例,这样你就不必关心构造函数是否带参数的情况。    public class SingletonGenerator<T>
        {
            private object locker;
            private bool initialized = false;
            private Func<T> func;
            private T instance;        public SingletonGenerator(Func<T> funcParam)
            {
                initialized = false;
                func = funcParam;
                locker = new object();
            }        public T Value
            {
                get
                {
                    if (!this.initialized)
                    {
                        lock (this.locker)
                        {
                            if (!this.initialized)
                            {
                                this.instance = this.func();
                                this.initialized = true;
                            }
                        }
                    }                return this.instance;
                }
            }
        }