一、单例模式
单例模式有3个要点:
1. 某个类只能有一个实例
2. 它必须自行创建这个实例
3. 它必须自行向整个系统提供这个实例
单例模式属于创建型模式的一种,创建型模式是一类最常用的设计模式,在软件开发中应用非常广泛。
创建型模式将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。
每一个创建型模式都在视图回答3个问题:
3W -> 创建什么(What)、由谁创建(Who)和何时创建(When)。
尽管在某种程度上,单例模式是限制而不是改进类的创建,但是它仍然和其他创建型模式分在同一组。
单例模式可以保证一个类有且只有一个实例,并且提供一个访问它的全局访问点。
在程序设计过程中,有很多情况下需要确保一个类只能有一个实例。
Example:
- 系统中只能有一个任务管理器
- 一个打印假脱机或者一个数据引擎的访问点
相信大家都使用过Windows任务管理器,我们可以做一个尝试:在Windows任务栏的右键菜单上多次点击“启动任务管理器”,看能否打开多个任务管理器窗口。正常情况下,无论我们启动多少次,Windows系统始终只能弹出一个任务管理器窗口。也就是说,在一个Windows系统中,任务管理器存在唯一性。
在实际开发中,我们经常也会遇到类似的情况,为了节约系统资源,有时候需要确保系统中某个类只有唯一一个实例,当这个唯一实例创建成功之后,无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,可以通过创建单例模式来实现,这也就是单例模式的动机所在。
二、使用静态方法创建单例
让一个类只有一个实例,最简单的方法就是在类中嵌入一个静态变量,并在第一个类实例中设置该变量,并且在每次进入构造函数都要做检查。不管类有多少个实例,静态变量只能有一个实例。
为了防止类被多次实例化,我们把构造函数声明为private私有类型,这样只能在类的静态方法里创建一个实例。
下面创建一个getSpooler方法,它返回Spooler的一个实例,如果类已经被实例化,返回值为NULL。
public class Spooler()
{
private static bool instance_flag = false;
private Spooler()
{
}
private static Spooler getSpooler()
{
if(!instance_flag)
return new Spooler();
else
return NULL;
}
}
这个方法的主要优点是,如果单例已经存在,不需要考虑异常处理,因为只不过从getSpooler方法返回了一个NULL空值而已。
{
Spooler sp1 = Spooler.getSpooler();
if(sp1 != null)
Console.WriteLine("Get 1 spooler");
Spooler sp2 = Spooler.getSpooler();
if(sp2 == null)
Console.WriteLine("Can\'t get spooler");
}
如果试图直接创建Spooler类的实例,编译会失败,因为构造函数被声明为private私有类型。
Spooler sp3 = new Spooler(); //编译失败
最后,如果需要修改程序,允许该类有两个或三个实例,则修改Spooler类可以很容易实现这一点。
三、异常与实例
前面的方法有个缺点,需要程序员检查getSpooler方法的返回值,以确保他不是空的。
让程序员始终记得去检查错误的设想是招致失败的开始,应该尽力避免。
既然这样,那我们换种方法,创建一个这样的类,如果试图多次实例化该类,它就会抛出一个异常,这时才需要程序员采取行动,因而这是一个安全的办法。为这个例子创建一个自己的异常类
public class SingletonException:Exception()
{
public SingletonException(string s):base(s)
{}
}
这里注意的是除了通过base调用了父类的构造函数之外,新的异常类实际上并没有做什么,什么也没做,尽管如此,拥有自己命名的异常类还是很方便的,在我们创建一个Spooler的实例时候,如果抛出了这种类型的异常,系统就会警告我们。
四、抛出异常
下面的代码给出了PrintSpooler类的框架,但是这里略去了所有的打印方法,只集中于正常实现单例模式
public class Spooler()
{
static bool instance_flag = false; //如果单例则true
public Spooler()
{
if(instance_flag)
{
throw new SingletonException("Only one printer allowed");
}
else
instance_flag=true;
Console.WriteLine("printer opened");
}
}
五、创建一个类实例
我们已经在PrintSpooler类里创建了一个简单的单例模式,接下来就是了解如何去使用。
这里要注意的是,应该把可能抛出异常的每一个方法都包含在try-catch块中。
public class singleSpooler()
{
static void Main(string[] args)
{
Spooler pr1,pr2;
Console.WriteLine("Opening one spooler");
try
{
pr1=new Spooler();
}
catch(SingletonException e)
{
Console.WriteLine(e.Message);
}
Console.WriteLine("Opening two spoolers");
try
{
pr2 = new Spooler();
}
catch
{
Console.WriteLine(e.Message);
}
}
}
该程序的执行结果如下:
Opening one spooler
printer opened
Opening two spoolers
Only one printer allowed
正如我们所料,最好一行的输出表名已经抛出了一个异常。
六、提供一个单例的全局访问点
由于使用单例可以提供一个类的全局访问点,即使C#中没有全局变量,设计程序时也必须为整个程序提供引用单例的方法。
一种解决方案就是在程序的开头创建单例,并且将它作为参数传递到需要使用它的类。
pr1=iSpooler.Instance();
Customers cust = new Customers(pr1);
这种方法的缺点是,在某次程序运行中,可能不需要所有的单例,这样会影响程序的性能。
提供一个全局访问点最常用的方式是使用类的静态方法。类名始终是可用的,静态方法只能由类调用,不能由类的实例调用,所以,不管程序中有多少个地方调用该方法,永远只能有一个这样的类实例。
七、单例模式总结
单例模式目标明确,结构简单,在软件开发中使用频率相当高。
1) 、主要优点
- 提供了对唯一实例的受控访问。单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
- 允许可变数目的示例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了单例对象共享过多有损性能的问题。例如,数据库连接池,线程池,各种池。
2)、主要缺点
- 单例模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了单一职责的原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。不够,很多时候我们都需要取得平衡。
- 很多高级面向对象编程语言如C#和Java等都提供了垃圾回收机制,如果实例化的共享对象长时间不被利用,系统则会认为它是垃圾,于是会自动销毁并回收资源,下次利用时又得重新实例化,这将导致共享的单例对象状态的丢失。
3)、适用场景
- 系统只需要一个实例对象。例如:系统要求提供一个唯一的序列号生成器或者资源管理器,又或者需要考虑资源消耗太大而只允许创建一个对象。
- 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
关于设计模式的学习,待续>>>>>>>