【设计模式】创建型——单例模式(Singleton)学习笔记 C++实现

1. 意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2. 动机

对一些类来说,只有一个实例是很重要的。全局变量使得一个对象可以被访问,但不能防止你实例化多个对象。

一个更好的办法是,让类自身负责保存它的唯一实例。这个类可以保证没有其它实例可以被创建,并且它可以提供一个访问该实例的方法。这就是Singleton模式。

3. 适用性

  • 当类只能有一个实例且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

4. 结构

在这里插入图片描述

5. 参与者

  • Singleton
    ——定义一个Instance操作,允许客户访问他的唯一实例。Instance是一个类操作(C++中的一个静态成员函数)
    ——可能负责创建它自己的唯一实例

6. 协作

  • 客户只能通过Singleton的Instance操作访问一个Singleton实例

7. 效果

Singleton模式有许多优点:
1)对唯一实例的受控访问 因为Singleton类封装它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它。
2)缩小名字空间 Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染名字空间。
3)允许对操作和表示的精化 Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时配置应用。
4)允许可变数目的实例 这个模式使得你易于改变你的想法,并允许Singleton类的多个实例。此外,你可以用相同的方法来控制应用所使用的实例的数目。只有允许访问Singleton实例的操作需要改变。
5)比类操作更灵活 另一种封装单件功能的方式是使用类操作(即C++中的静态成员函数)。但这两种语言技术都难以改变设计以允许一个类有多个实例。此外,C++中的静态成员函数不是虚函数,因此子类不能多态地重新定义他们。

8.实现

下面是使用Singleton模式时所要考虑的实现问题:
1)保证一个唯一的实例 Singleton模式使得这个唯一实例是类的一般实例,但该类被写成只有一个实例能被创建。做到这一点的一个常用方法是将创建这个实例的操作隐藏在一个类操作(即一个静态成员函数或者是一个类方法)后面,由它保证只有一个实例被创建。这个操作可以访问保存的唯一实例的变量,而且它可以保证这个变量在返回值之前用这个唯一实例初始化。这种方法保证了单件在它的首次使用前被创建和使用。
在C++中你可以用Singleton类的静态成员函数Instance来定义这个类操作。Singleton还定义了一个静态成员变量_instance,它包含了一个指向它的唯一实例的指针。

Singleton类定义如下:

class Singleton {
public:
	static Singletion* Instance();
protected:
	Singleton();
private:
	static Singleton* _instance;
};

相应的实现是:

Singleton* Singleton::_instance = 0;

Singleton* Singleton::Instance() {
	if (_instance == 0) {
		_instance = new Singleton;
	}
	return _instance;
}

客户仅通过Instance函数访问这个单件。变量_instance初始化为0,而静态成员函数返回该变量值,如果其值为0则用唯一实例初始化它。Instance使用惰性(lazy)初始化,它的返回值直到被第一次访问时才创建和保存。

因为构造函数是保护类型,所以试图实例化Singleton的客户将得到一个编译时的错误信息,这就保证了仅有一个实例可以被创建。

2)创建Singleton类的子类 与其说是定义子类不如说是建立它的唯一实例,这样客户就可以使用它。事实上,指向单件实例的变量必须用子类的实例进行初始化。最简单的技术实在Singleton的Instance操作中决定你想使用的是哪一个单件。

另一个选择Singleton的子类的方法是将Instance的实现从父类(即MazeFactory)中分离出来并将它放入子类。这就允许C++程序员在链接时决定单件的类(即通过链入一个包含不同实现的对象文件),但对单件的客户则隐藏这一点。

链接的方法在链接时确定了单件类的选择,这使得难以在运行时选择单件类。使用条件语句来决定子类更加灵活一些,但这硬性限定了可能的Singleton的类的集合。这两种方法不是在所有的情况都足够灵活。

一个更灵活的方法是使用一个单件注册表(rregistry of singleton)。可能的Singleton类的集合不是由Instance定义的,Singleton类可以根据名字在一个众所知的注册表中注册他们的单件实例。

这个注册表在字符串名字和单件之间建立映射。当Instance需要一个单件时,他参考注册表,根据名字请求单件。

注册表查询相应的单件(如果存在的话)并返回它。这个方法使得Instance不再需要知道所有可能的Singleton类或实例。它所需要的只是所有Singleton类的一个公共的接口,该接口包括了对注册表的操作。

class Singleton{
public:
	static void Register(const char* name, Singleton*);
	static Singleton* Instance();
protected:
	static Singleton* Lookup(const char* name);
private:
	static Singleton* _instance;
	static List<NameSingletonPair>* _registry;
};

Register以给定的名字注册Singleton实例。为保证注册表简单,我们将让它储存一列NameSingletonPair对象。每个NameSingletonPair将一个名字映射到一个单件。Lookup操作根据给定单件的名字进行查找。我们假定一个环境变量制定了所需要的单件的名字。

Singleton* Singleton::Instance() {
	if (_instance == 0) {
		const char* singletonName = getenv("SINGLETON");
		// user or environment supplies this at startup
		
		_instance = Lookup(singletonName);
		// Lookup returns 0 if there's no such singleton
	}
	return _instance;
}

Singleton类在何处注册自己?一种可能是在其构造器中。例如,MySingleton子类可以像下面这样做:

MySingleton::MySingleton() {
	// ...
	Singleton::Register("MySingleton", this);
}

当然,除非实例化类否则这个构造器不会被调用,这正反应了Singleton模式试图解决的问题!在C++中我们可以定义Singleton的一个静态实例来避免这个问题。例如,可以在包含MySingleton实现的文件中定义:

static MySingleton theSingleton;

Singleton类不再负责创建单件。它的主要职责是使得供选择的单件对象在系统中可以被访问。静态对象方法还是有一个潜在的缺点——所有可能的Singleton子类的实例都必须被创建,否则他们不会被注册。

9. 代码示例

假定我们定义一个MazeFactory类用于建造在本章前面所描述的迷宫。MazeFactory定义了一个建造迷宫的不同部件的接口。子类可以重定义这些操作以返回特定产品的实例,如用BombedWall对象代替普通的Wall对象。

此处相关的问题是Maze应用仅需迷宫工厂的一个实例,且这个实例对建造迷宫任何部件的代码都是可用的。这样就引入了Singleton模式。将MazeFactory作为单件,我们无须借助全局变量就可使迷宫对象具有全局可访问性。

为简单起见,假定不会生成MazeFactory的子类。我们通过增加静态的Instance操作和静态的用来保存唯一实例的成员_instance,在C++中生成一个Singleton类。我们还必须保护构造器以防止意外的实例化,因为意外的实例化可能会导致多个实例。

class MazeFactory {
public:
	static MazeFactory* Instance();

	// existing interface goes here
protected:
	MazeFactory();
private:
	static MazeFactory* _instance;
}

相应的实现是:

MazeFactory* MazeFactory::_instance = 0;
MazeFactory* MazeFactory::Instance() {
	if (_instance == 0) {
		_instance = new MazeFactory;
	}
}

现在我们考虑当存在MazeFactory的多个子类,而且应用必须决定使用哪个子类时的情况。我们将通过环境变量选择迷宫的种类并根据该环境变量的值增加代码用于实例化适当的MazeFactory子类。Instance操作是增加这些代码的好地方,因为它已经实例化了MazeFactory:

MazeFactory* MazeFactory::Instance() {
	if (_instance == 0) {
		const char* mazeStyle = getenv("MAZESTYLE");

		if (strcmp(mazeStyle, "bombed") == 0) {
			_instance = new BombedMazeFactory;
		} else if (strcmp(mazeStyle, "enchanted") == 0) {
			_instance = new EnchantedMazeFactory;
			// ... other possible subclasses
		} else {	// default
			_instance = new MazeFactory;
		}
	}
	return _instance;
}

注意,无论何时定义一个新的MazeFactory的子类,Instance都必须被修改。在这个应用中可能没什么关系,但对于定义在又给框架中的抽象工厂来说,这可能是一个问题。

一个可能的解决办法将是使用在实现一节中所描述过的注册表的方法。此处动态链接可能也很有用——它使得应用不需要装载那些用不着的子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超级D洋葱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值