谈谈多例模式(multiton)的使用

之前的文章<<谈谈单例模式>>介绍过单例模式, 其全局只会生成一个实例化的对象。当初采用的是全局配置文件的实践案例, 因为比较适合采用单例模式。
设计模式是前辈们通过丰富的工程实践后总结出的经验。所以笔者认为对于设计模式的理解,并不仅仅在于看懂这个模式的实现方式,更重要的是明白什么时候该使用什么设计模式,而要做到这个,离不开的是不断地实践。注意这里的实践,并不是说手写一遍设计模式的实现,而是多做一些项目,从项目中汲取经验,才能理解的更加深刻,变为自己能够灵活运用的知识。

而本文也将从实际的一些工程经验中,抽象出问题的场景,便于还不熟悉的读者更好的理解。

再聊单例模式

首先多例模式算是单例模式的一种扩展。在理解多例模式之前,本文我们换一个例子,Redis连接池,再来加深下对单例模式的认识。一般在工程实践中,为了减少TCP连接带来的额外时间消耗,以及可能存在的认证过程,一般采用长连接(Persistent Connection)的方式维护和利用连接,即将连接存放在一个池子中(连接池, 一般可以采用容器存储),当应用访问redis的时候,从池子里取出一个连接对象,然后复用这个连接。当然本文重点讲解的不是这个连接池,想了解的同学可以看一看<<对象池的使用场景以及自动回收技术>>

以上就是一个场景的铺垫,当一个应用程序第一次引入Redis的时候,会使用如下图所示,这个模块会调用Redis连接池,访问Redis。
在这里插入图片描述
这样的使用一点都没有问题, 但当又有个同学写了另一个模块发现也要用Redis,然后就变成了这样。当项目复杂之后,多人协作,就容易写出下面的方式,每一个模块都使用了一个连接池。如果是小型程序,完全没有关系,但是如果你的服务是一个可以自动伸缩扩展机器的SaaS服务呢?又或者某天另一个同学又实现了一个类似的需要访问Redis的模块n. 那么会发生什么? 一般连接池会保持一个最小连接数,假设这个最小连接数是3,那么有n个模块一台机器就创建了n3个连接。那如果是m台机器,那么就会有mn*3个连接,这样也许会把单点的Redis连接数量给撑满,从而导致服务使用问题。
在这里插入图片描述
理论上一个应用程序使用一个Redis连接池,一般就足够了,而不需要每个模块都使用一个连接池。那么这就提到了本章节的主人公单例模式,如果第一个写Redis连接池的同学,将其实现为单例模式是不是,就可以从防御性编程角度出发,避免其他同学实现其他模块的时候,也只会用同一个实例化的Redis连接池,这就是我认为单例的好处之一。模式变为了如下:
在这里插入图片描述
我们还是从连接池的角度出发,接着看一个多例模式的场景。

多例模式的一个场景

有了上一个章节做铺垫,我们应该理解了单例模式的重要任务,就是只实例化一个对象。而多例模式顾名思义,就是实例化多个对象。这么一听,是不是就是可以直接实例化对象了?不是的。多例模式一般实例化的对象是有有限的数量,每一个实例的对象一般都关联一个索引的Key,根据Key的只会构造/对应一个实例化对象。 是不是这么说还有点抽象,我们用一个样例来阐述阐述下。
SaaS环境中,经常会切分为多个服务,在不同的机器运行,当你所编写的服务要访问另一个服务Ahttp是常见的一种方式,于是你实现了一个http的连接池(准确来说应该tcp连接池,http相当于基于tcp的一个应用封装),这个连接池只实例化了一个对象在应用程序中专门用来访问服务A,于是这位同学先将这个连接池做成了一个单例模式。
在这里插入图片描述
但是这个时候又出现了一个需求,需要访问服务B于是结构变成了这样, 两个连接池。这两个连接池有什么共同特点? 其实对于对象的接口和方法实现都是一样的,唯一不一样的就是连接地址。那么这个时候,单例模式肯定不能满足了,单例模式本来是为了整个进程只有一个实例访问服务A,那么这个时候是不是就可以有两个实例了,一个连接池实例用来访问服务A,一个连接池实例访问服务B。这也就是我们要实现的多例模式,而上面所说的Key,你就可以定义为ServiceAServiceB, 分别对应两个实例化的连接池,内部采用map存储查找。
在这里插入图片描述
根据上面描述,可以实现为如下图所示。关于代码实现也比较简单,我们接着来看下一个章节:
在这里插入图片描述

多例模式的实现

先画出类图,主要差别于单例模式的是,采用unordered_map存储多个实例,然后使用strInstanceKey去查询关联的实例,如果没有创建则创建相应的实例。
在这里插入图片描述
以下是实现的一个线程安全的多例模式实现。这个实现展现了多例的细节,并不会包含HttpPool的具体实现(不是本文的重点)。代码就不逐行解析了主要挑几个点说一下:

  1. GetInstance函数,第一个参数strInstanceKey就是用来表示一个实例的名字
  2. GetInstance函数可变参数,通过std::forward实现完美转发,在GetInstance内部作为HttpPoolMultiton的构造函数参数。当然也可以将这些可变参数,直接写成和构造函数一样,这种完美转发主要常见于模板的实现。
  3. 生成实例使用shared_ptr管理其生命周期,通过unordered_map容器存储这些实例;相同的strInstanceKey获取的实例对象是一样的。
#include <iostream>
#include <mutex>
#include <unordered_map>
#include <memory>

class HttpPoolMultiton
{
public:
	using HttpPoolMultitonSPtr = std::shared_ptr<HttpPoolMultiton>;

	//Args is the constructor parameters
	template<typename... Args>
	static HttpPoolMultitonSPtr GetInstance(const std::string& strInstanceKey, Args&&... args)
	{
			std::lock_guard<std::mutex> guard(m_mutex);
			if (m_mapObjects.count(strInstanceKey) == 0)
			{
				m_mapObjects.emplace(strInstanceKey, new HttpPoolMultiton(std::forward<Args>(args)...));
			}
			return m_mapObjects[strInstanceKey];
	}

	~HttpPoolMultiton() { ; };

private:
	HttpPoolMultiton(const std::string& strHost, const unsigned short uhPort) 
	{ 
		//Here need to do some initialization 
	};
	HttpPoolMultiton(const HttpPoolMultiton&) = delete;
	HttpPoolMultiton& operator= (const HttpPoolMultiton&) = delete;

private:
	static std::unordered_map<std::string, HttpPoolMultitonSPtr> m_mapObjects;
	static std::mutex       m_mutex;
};

std::unordered_map<std::string, HttpPoolMultiton::HttpPoolMultitonSPtr> HttpPoolMultiton::m_mapObjects;
std::mutex HttpPoolMultiton::m_mutex;

int main()
{
	auto httpPoolA = HttpPoolMultiton::GetInstance("ServiceA", std::string("192.168.1.1"), 7777);
	auto httpPoolA1 = HttpPoolMultiton::GetInstance("ServiceA", std::string("192.168.1.1"), 7777);
	auto httpPoolB = HttpPoolMultiton::GetInstance("ServiceB", std::string("192.168.1.2"), 8888);

	if (httpPoolA != httpPoolB)
	{
		std::cout << "httpPoolA is not be the same with httpPoolB!" << std::endl;
	}

	if (httpPoolA == httpPoolA1)
	{
		std::cout << "httpPoolA is the same with httpPoolA1!" << std::endl;
	}
	return 0;
}

本文并没有采用模板的方式实现,便于经验还不够丰富的同学查看。如果想要学习模板实现可以参照qicosmos所写的<<c++11改进我们的模式之改进单例模式>>

总结

多例模式简单来说就是单例模式的扩充,可以根据使用场景,实例化指定数量的实例化对象,通过Key来查找相应的对象,如果查找不到,则创建一个新的对象,即每个Key关联一个实例化的对象。
真正的理解设计模式,一定要多进行项目实践,才能体会的更加深刻。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式是一种创建型设计模式,它保证一个类只有一个实例,并提供一个全局访问点。单例模式通常用于管理全局状态或提供共享资源。 创建单例模式需要以下步骤: 1. 将类的构造函数声明为私有的,以防止外部代码创建新的实例。 2. 在类中创建一个静态方法,该方法将返回类的唯一实例。 3. 在静态方法中,如果类的实例不存在,则创建一个新的实例,然后返回该实例。 4. 如果类的实例已经存在,则直接返回该实例。 以下是一个示例单例模式的实现: ```python class Singleton: _instance = None def __new__(cls): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance ``` 多例模式是一种创建型设计模式,它允许创建多个类的实例,但是实例的数量是有限的,并且由该类的开发者预先设置。多例模式通常用于限制系统中某些资源的数量。 创建多例模式需要以下步骤: 1. 在类中创建一个静态属,该属将存储所有实例的列表。 2. 在类中创建一个静态方法,该方法将返回类的下一个可用实例。 3. 在静态方法中,使用类的实例列表来查找下一个可用实例。如果没有可用实例,则创建一个新的实例,并将其添加到实例列表中。 4. 在使用实例时,必须从静态方法中获取实例,而不是使用类的构造函数。 以下是一个示例多例模式的实现: ```python class Multiton: _instances = {} _max_instances = 2 def __new__(cls, instance_id): if len(cls._instances) < cls._max_instances: if instance_id not in cls._instances: cls._instances[instance_id] = super().__new__(cls) return cls._instances[instance_id] else: raise ValueError("Max number of instances reached.") ``` 在上面的示例中,Multiton 类允许创建最多两个实例。在创建实例时,必须提供一个 instance_id 参数,以便区分不同的实例。如果尝试创建超过两个实例,则会引发 ValueError 异常。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值