谈谈多例模式(multiton)的使⽤
之前的⽂章介绍过单例模式, 其全局只会⽣成⼀个实例化的对象。当初采⽤的是全局配置⽂件的实践案例, 因为⽐较适合采⽤单例模式。设计模式是前辈们通过丰富的⼯程实践后总结出的经验。所以笔者认为对于设计模式的理解,并不仅仅在于看懂这个模式的实现⽅式,更重要的是明⽩什么时候该使⽤什么设计模式,⽽要做到这个,离不开的是不断地实践。注意这⾥的实践,并不是说⼿写⼀遍设计模式的实现,⽽是多做⼀些项⽬,从项⽬中汲取经验,才能理解的更加深刻,变为⾃⼰能够灵活运⽤的知识。
⽽本⽂也将从实际的⼀些⼯程经验中,抽象出问题的场景,便于还不熟悉的读者更好的理解。
再聊单例模式
⾸先多例模式算是单例模式的⼀种扩展。在理解多例模式之前,本⽂我们换⼀个例⼦,Redis连接池,再来加深下对单例模式的认识。⼀般在⼯程实践中,为了减少TCP连接带来的额外时间消耗,以及可能存在的认证过程,⼀般采⽤长连接(Persistent Connection)的⽅式维护和利⽤连接,即将连接存放在⼀个池⼦中(连接池, ⼀般可以采⽤容器存储),当应⽤访问redis的时候,从池⼦⾥取出⼀个连接对象,然后复⽤这个连接。当然本⽂重点讲解的不是这个连接池,想了解的同学可以看⼀看。
以上就是⼀个场景的铺垫,当⼀个应⽤程序第⼀次引⼊Redis的时候,会使⽤如下图所⽰,这个模块会调⽤Redis连接池,访问Redis。
这样的使⽤⼀点都没有问题, 但当⼜有个同学写了另⼀个模块发现也要⽤Redis,然后就变成了这样。当项⽬复杂之后,多⼈协作,就容易写出下⾯的⽅式,每⼀个模块都使⽤了⼀个连接池。如果是⼩型程序,完全没有关系,但是如果你的服务是⼀个可以⾃动伸缩扩展机器的SaaS服务呢?⼜或者某天另⼀个同学⼜实现了⼀个类似的需要访问Redis的模块n. 那么会发⽣什么? ⼀般连接池会保持⼀个最⼩连接数,假设这个最⼩连接数是3,那么有n个模块⼀台机器就创建了n3个连接。那如果是m台机器,那么就会有m n*3个连接,这样也许会把单点的Redis连接数量给撑满,从⽽导致服务使⽤问题。
理论上⼀个应⽤程序使⽤⼀个Redis连接池,⼀般就⾜够了,⽽不需要每个模块都使⽤⼀个连接池。那么这就提到了本章节的主⼈公单例模式,如果第⼀个写Redis连接池的同学,将其实现为单例模式是不是,就可以从防御性编程⾓度出发,避免其他同学实现其他模块的时候,也只会⽤同⼀个实例化的Redis连接池,这就是我认为单例的好处之⼀。模式变为了如下:
我们还是从连接池的⾓度出发,接着看⼀个多例模式的场景。
多例模式的⼀个场景
单例模式的几种实现方式有了上⼀个章节做铺垫,我们应该理解了单例模式的重要任务,就是只实例化⼀个对象。⽽多例模式顾名思义,就是实例化多个对象。这么⼀听,是不是就是可以直接实例化对象了?不是的。多例模式
⼀般实例化的对象是有有限的数量,每⼀个实例的对象⼀般都关联⼀个索引
的Key,根据Key的只会构造/对应⼀个实例化对象。 是不是这么说还有点抽象,我们⽤⼀个样例来阐述阐述下。
在SaaS环境中,经常会切分为多个服务,在不同的机器运⾏,当你所编写的服务要访问另⼀个服务A,http是常见的⼀种⽅式,于是你实现了⼀个http的连接池(准确来说应该tcp连接池,http相当于基于tcp的⼀个应⽤封装),这个连接池只实例化了⼀个对象在应⽤程序中专门⽤来访问服务A,于是这位同学先将这个连接池做成了⼀个单例模式。
但是这个时候⼜出现了⼀个需求,需要访问服务B于是结构变成了这样, 两个连接池。这两个连接池有什么共同特点? 其实对于对象的接⼝和⽅法实现都是⼀样的,唯⼀不⼀样的就是连接地址。那么这个时候,单例模式肯定不能满⾜了,单例模式本来是为了整个进程只有⼀个实例访问服务A,那么这个时候是不是就可以有两个实例了,⼀个连接池实例⽤来访问服务A,⼀个连接池实例访问服务B。这也就是我们要实现的多例模式,⽽上⾯所说的Key,你就可以定义为ServiceA和ServiceB, 分别对应两个实例化的连接池,内部采⽤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& Args>
static HttpPoolMultitonSPtr GetInstance(const std::string& strInstanceKey, Args&&... args)
{
std::lock_guard<std::mutex>guard(m_mutex);
if(unt(strInstanceKey)==0)
{
place(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;
}
return0;
}
本⽂并没有采⽤模板的⽅式实现,便于经验还不够丰富的同学查看。如果想要学习模板实现可以参照qicosmos所写的。总结
多例模式简单来说就是单例模式的扩充,可以根据使⽤场景,实例化指定数量的实例化对象,通过Key来查相应的对象,如果查不到,则创建⼀个新的对象,即每个Key关联⼀个实例化的对象。
真正的理解设计模式,⼀定要多进⾏项⽬实践,才能体会的更加深刻。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论