设计模式之单例(Singleton)模式

在这里插入图片描述

一、单例模式

单例模式有3个要点:

1. 某个类只能有一个实例
2. 它必须自行创建这个实例
3. 它必须自行向整个系统提供这个实例

单例模式属于创建型模式的一种,创建型模式是一类最常用的设计模式,在软件开发中应用非常广泛。
创建型模式将对象的创建和使用分离,在使用对象时无需关心对象的创建细节,从而降低系统的耦合度,让设计方案更易于修改和扩展。

每一个创建型模式都在视图回答3个问题:
3W -> 创建什么(What)、由谁创建(Who)和何时创建(When)。

尽管在某种程度上,单例模式是限制而不是改进类的创建,但是它仍然和其他创建型模式分在同一组。

单例模式可以保证一个类有且只有一个实例,并且提供一个访问它的全局访问点。

在程序设计过程中,有很多情况下需要确保一个类只能有一个实例。
Example:

  1. 系统中只能有一个任务管理器
  2. 一个打印假脱机或者一个数据引擎的访问点
    在这里插入图片描述

相信大家都使用过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) 、主要优点

  1. 提供了对唯一实例的受控访问。单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
  2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑可以提高系统的性能。
  3. 允许可变数目的示例。基于单例模式,开发人员可以进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省系统资源,又解决了单例对象共享过多有损性能的问题。例如,数据库连接池,线程池,各种池。

2)、主要缺点

  1. 单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  2. 单例类的职责过重,在一定程度上违背了单一职责的原则。因为单例类既提供了业务方法,又提供了创建对象的方法(工厂方法),将对象的创建和对象本身的功能耦合在一起。不够,很多时候我们都需要取得平衡。
  3. 很多高级面向对象编程语言如C#和Java等都提供了垃圾回收机制,如果实例化的共享对象长时间不被利用,系统则会认为它是垃圾,于是会自动销毁并回收资源,下次利用时又得重新实例化,这将导致共享的单例对象状态的丢失。

3)、适用场景

  1. 系统只需要一个实例对象。例如:系统要求提供一个唯一的序列号生成器或者资源管理器,又或者需要考虑资源消耗太大而只允许创建一个对象。
  2. 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

关于设计模式的学习,待续>>>>>>>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值