设计模式 单件模式(Signleton Pattern)
尽管在某种程度上,单件模式是限制而不是改进类的创建,但它仍和其他创建模式分在一组。单件模式可以保证一类有且只有一个实例,并提供一个访问它的全局访问点,在程序设计过程中,有很多情况需要确保一个类只能有一个实例。例如系统中能有一个窗口管理器,一个数据库引擎的访问点。PC机中可能有几个串口,但只能有一个COM1实例。
使用静态方法创建单件
让一个类只有一个实例,最容易的方法就是在类中嵌入一个静态变量,并在第一个类实例中设置改变量,而且每次进入构造函数都要检查。不管类有多少实例,静态变量只能有一个实例。为了防止类被多次实例化,我们把构造函数声明为私有的,这样只能在类的静态方法里创建一个实例。下面创建一个
getSpooler方法,返回Spooler的一个实例,如果类已经被实例化,返回值为null。
1 using System;
2
3 namespace GlobalSpooler
4 {
5 /// <summary>
6 /// Summary description for Spooler.
7 /// </summary>
8 public class Spooler {
9 private static bool instance_flag= false;
10 private Spooler() {
11 }
12 public static Spooler getSpooler() {
13 if (! instance_flag)
14 return new Spooler ();
15 else
16 return null;
17 }
18
19 }
20 }
这中方法的主要优点是,如果单件存在了,不需要考虑异常处理,因为只不过getSpooler方法返回一个空值而已。
1 Spooler sp1 = Spooler.getSpooler();
2 if (sp1 != null)
3 Console.WriteLine ("Got 1 spooler");
4 Spooler sp2 = Spooler.getSpooler ();
5 if (sp2 == null)
6 Console.WriteLine ("Can\'t get spooler");
如果视图直接创建Spoolet类的实例,编译会失败,因为构造函数被声明为私有的。
1 //fails at compile time
2 Spooler sp3 = new Spooler ();
最后,如果需要修改程序,允许该类有两个或者三个实例,则修改spooler类可以很容易实现这一点。
异常与实例
前面方法有个缺点,需要程序员检查方法的返回值,以确保它不为空。让程序员始终记得去检查错误的设想是导致失败的开始,应该尽量避免。我们换种方法,创建一个这样的类,如果视图多次实例化该类,它会抛出一个异常,这时才需要程序员采取行动,因而这时一个安全的方法,这里先为这个例子创建异常类。
1 using System;
2
3 namespace singleSpooler
4 {
5 /// <summary>
6 /// Summary description for SingletonException.
7 /// </summary>
8 public class SingletonException:Exception {
9 //new exception type for singleton classes
10 public SingletonException(string s):base(s) {
11 }
12 }
13 }
抛出异常
接下来给出PrintSpooler类的框架。这里略去打印方法,只集中在单件模式
1 using System;
2
3 namespace singleSpooler
4 {
5 /// <summary>
6 /// Prototype of Spooler Singleton
7 /// such that only one instane can ever exist.
8 /// </summary>
9 public class Spooler {
10 static bool instance_flag = false; //true if one instance
11 public Spooler() {
12 if (instance_flag)
13 throw new SingletonException("Only one printer allowed");
14 else
15 instance_flag=true; //set flag for one instance
16 Console.WriteLine ("printer opened");
17 }
18 }
19 }
创建一个实例
我们已经在PrintSpooler类里创建了一个简单的单件模式,接下来就去了解如果使用它
1 using System;
2
3 namespace singleSpooler
4 {
5 /// <summary>
6 /// Summary description for Class1.
7 /// </summary>
8 public class singleSpooler {
9 static void Main(string[] args) {
10 Spooler pr1, pr2;
11 //open one printer--this should always work
12 Console.WriteLine ("Opening one spooler");
13 try {
14 pr1 = new Spooler();
15 }
16 catch (SingletonException e) {
17 Console.WriteLine (e.Message);
18 }
19 //try to open another printer --should fail
20 Console.WriteLine ("Opening two spoolers");
21 try{
22 pr2 = new Spooler();
23 }
24 catch (SingletonException e) {
25 Console.WriteLine (e.Message);
26 }
27 }
28 }
29 }
执行结果
提供一个单件的全局访问点
由于使用电击模式提供一个类的全局访问点,即使C#中没有全局变量,设计程序是也必须为整个程序提供引用单件方法。一种解决方案是在程序的开头创建单件模式,并将其作为参数传递到使用它的类中。这种方法的 缺点是,在某次程序运行中,可能不需要所有的单件,这样会影响程序的性能。
1 prl = iSpooler.Instance();
2 Customers cust = new Customers(prl);
另一种更灵活的解决方案是,在程序中创建一个所有单件类的注册表,并使用注册表始终是可用的,每一次实例化一个组件,都将其记录在注册表中。程序的任何部分都是用标示符串访问任何一个单件实例,并能取回相应的实例变量。注册表的缺点是减少了类型检查,因为注册表中的单件可能把所有的单件都保存成对象型,例如,Hashtable中的对象类型,另外,注册表本身可能也是一个单件,必须使用构造函数或者其他set函数吧它传递给程序的所有部分。提供一个全局访问点的最常用的方式就是使用类的静态方法。类名始终是可用的,静态方法只能由类调用不能由类的实例调用,所以不管程序中有多少方法调用该方法,永远只能有一个这样的实例。
单例模式的其他效果
1.子类化一个单件很难,因为只有在基类单件没有实例化时,才能实现这一点。
2.可能很容易修改一个单件,使它有少数几个实例,这样做事允许的而且有意义的。