前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谈谈多例模式(multiton)

谈谈多例模式(multiton)

作者头像
河边一枝柳
发布2021-10-26 11:24:42
9760
发布2021-10-26 11:24:42
举报

在文章<<谈谈单例模式>>中介绍过单例模式, 一个类全局只会生成一个实例化的对象。而本文将从一些工程经验中,抽象出问题的场景,来谈一谈多例模式,便于还不熟悉的读者更好的理解。

设计模式是前辈们通过丰富的工程实践后总结出的经验。所以笔者认为对于设计模式的理解,并不仅仅在于看懂这个模式的实现方式,更重要的是明白什么时候该使用什么设计模式,而要做到这个,离不开的是不断地实践。注意这里的实践,并不是说手写一遍设计模式的实现,而是多做一些项目,从项目中汲取经验,才能理解的更加深刻,变为自己能够灵活运用的知识。

再聊单例模式

首先多例模式算是单例模式的一种扩展。在理解多例模式之前,本文我们换一个例子,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获取的实例对象是一样的。
代码语言:javascript
复制
#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改进我们的模式之改进单例模式>>: https://www.cnblogs.com/qicosmos/p/3145019.html

总结

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一个程序员的修炼之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 再聊单例模式
  • 多例模式的一个场景
  • 多例模式的实现
  • 总结
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档