• 设计模式之单例模式


    单例模式的基本概念

    单例模式是一种保证一个类仅有一个实例,并提供一个全局访问点的设计模式,它还有些许其他的叫法,比如说懒汉模式、单子模式等。那么这种设计模式解决了一个什么事情呢?我们来看下这样一段代码

    function Foo() {}
    
    const s1 = new Foo();
    const s2 = new Foo();
    console.log(s1 === s2); // false
    

    从结果上看,每通过构造函数创建一个对象,就会新开辟一片内存去存储,所以两个对象的值是不相等,而我们要做的事情是,让它每次创建出来的结果都是同一个,那这就是单例模式,运用到现实生活中的场景,比如说全局状态、前端页面中的模态框等等。下面跟着单例模式的实现,我们来一步一步地改造它。

    单例模式的实现

    随着 ECMAScript 标准的更新换代,最开始我们是通过函数+全局变量,或者函数加闭包的形式去实现单例模式,到后来 ES6 中有了 Class 的语法,我们可以用 Class 去写单例,所以本文通过三种方式介绍单例模式的实现。

    全局变量 + 函数

    function Singleton() {}
    
    Singleton.getInstance = function () {
      if (!window.instance) {
        window.instance = new Singleton();
      }
      return window.instance;
    };
    
    const s1 = Singleton.getInstance();
    const s2 = Singleton.getInstance();
    console.log(s1 === s2); // true
    

    这里思考下这样写有什么不好的吗?有的。例如程序员李雷在全局变量上挂载了一个 instance 属性,程序员韩梅梅也在全局变量 windows 上挂载了一个 instance 属性。他们互相都不告诉对方自己在 windows 上挂载了一个 instance 属性,那么这个时候是不是就会产生冲突呢?所以这样子写,不好。

    函数+闭包

    function Singleton() {}
    
    Singleton.getInstance = (function () {
      let instance = null;
      return function () {
        if (!instance) {
          instance = new Singleton();
        }
        return instance;
      };
    })();
    
    const s1 = Singleton.getInstance();
    const s2 = Singleton.getInstance();
    console.log(s1 === s2); // true
    

    ES 中面向对象的 Class

    class Singleton {
      static getInstance() {
        if (!Singleton.instance) {
          Singleton.instance = new Singleton();
        }
        return Singleton.instance;
      }
    }
    
    const s1 = Singleton.getInstance();
    const s2 = Singleton.getInstance();
    console.log(s1 === s2); // true
    

    单例模式的应用

    单例模式作为最简单的设计模式之一,在软件开发中应用也很广泛,下面笔者结合自己的经历,主要从前端和后端分别举一个例子来介绍设计模式的应用。

    单例模式在前端的应用

    前面我们说了模态框,比如说,你页面有个登录按钮,点击后会弹出一个登录框,这里每次点击登录都重新弹一个新的模态框,显然是不必要的,因为他们内容是一样的,所以我们期望把它缓存下来,核心代码如下:

    class Modal {
      static getInstance() {
        if (!Modal.instance) {
          Modal.instance = new Modal();
          Modal.instance.createElement();
        }
        return Modal.instance;
      }
    
      createElement() {
        this.div = document.createElement('div');
        this.div.id = 'modal';
        this.div.innerHTML = '全局模态框';
        this.div.style.display = 'none';
        document.body.appendChild(this.div);
      }
    
      open() {
        this.div.style.display = 'block';
      }
    
      close() {
        this.div.style.display = 'none';
      }
    }
    
    document.getElementById('BtnOpen').addEventListener('click', () => {
      const modal = Modal.getInstance();
      modal.open();
    });
    
    document.getElementById('BtnClose').addEventListener('click', () => {
      const modal = Modal.getInstance();
      modal.close();
    });
    

    具体的 demo 地址: https://zhengjiangtao.cn/show/design-mode/singleton.html

    单例模式在后端的应用

    这个是笔者在通过 nodejs 做微信开发的时候,借助单例模式的思想优化相关的业务代码的实践所得,就是不能每次前端这边来一个请求,或者别的地方引用或者使用到封装的微信接口 API,就重新创建一个新的,那么数据量上去了,这边开销是会很大的,比如百万、千万等等,所以我们期望把它缓存下来,然后用到直接取就好了。

    // 创建一个微信公众号相关的API类
    class WechatOfficalAccountApi {
      constructor(appId, appSecret, token) {
        // code...
      }
    }
    
    // 单例模式的实现
    const createWechatOfficalAccountApi = (function (appId, appSecret, token) {
      let instance = null;
      return function () {
        if (!instance) {
          instance = new WechatOfficalAccountApi(appId, appSecret, token);
        }
        return instance;
      };
    })();
    

    考虑到微信公众号的类另有用途,所以就没有都封装到类里面,而是单独抛出一个函数去做这件事,大家想一下这样写好不好啊? 是的,不好。问题就在于,比如说我创建了一个单例对象实例是去处理公众号”江涛学编程“的相关业务的,后来迫于生计,老板决定卖艺,又搞了个”江涛学音乐“,那么这个时候你这个单例就歇菜了,因为它只有一个实现例的全局访问点,而 appid 每个微信公众号都是不同的。

    考虑到楼上这个场景,其实不能简单地去像楼上去设计单例模式。我想到一个例子,就好比水产养殖这个专业,海王他就知道,单纯地在池子里养草鱼,草鱼会有点孤单,它会不会不快乐呢?它会不会绝食呢?于是它把龙虾也放了进来,这样子至少显得不那么孤单,可以聊聊天,龙虾你今天吃什么?草鱼你今天吃什么?池子里充满了欢声笑语,哦,我明白了,我也给咱微信 API 接口造一个池子,开干。

    // 创建一个连接池
    const wechatOfficalAccountApiPool = {};
    
    // 创建一个微信公众号相关的API类
    class WechatOfficalAccountApi {
      constructor(appId, appSecret, token) {
        // code...
      }
    }
    
    // 单例模式的实现
    function createWechatOfficalAccountApi(appId, appSecret, token) {
      let instance = wechatOfficalAccountApiPool[appId];
      if (!instance) {
        instance = new WechatOfficalAccountApi(appId, appSecret, token);
        wechatOfficalAccountApiPool[appId] = instance;
      }
      if (instance.appSecret !== appSecret || instance.token !== token) {
        throw new Error(
          `createWechatOfficalAccountApi(${appId}, ${appSecret}, ${token}): ` +
            `conflict with existing one: (${instance.appId}, ${instance.appSecret}, ${instance.token})`
        );
      }
      return instance;
    }
    

    为了更健壮鲁棒一点,我们已知微信的 appid 是唯一的,就以它作为 key 来搞,这样子的话就可以处理多个业务场景了,比如老板开了好多个媒体号,有“江涛学编程”,”江涛学音乐“,”江涛去旅行“等等,根据不同的业务场景和用途,就可以在最基础的通用性强的微信接口 API 上去扩展实现对应的业务场景的功能。

    参考文献

    原文地址:https://zhengjiangtao.cn/coding/singleton.html

  • 相关阅读:
    linux中添加ftp用户,并设置相应的权限
    mysql 命令管理
    阿里云服务器资料
    sql替换指定字段指定字符串
    apache本地域名ip重定向vhosts
    php数组序列化serialize与unserialize
    Android 开发第三天
    Android 开发第二天
    Android开发的第一天
    字符的截取方法使用的是Substring 和三目运算符
  • 原文地址:https://www.cnblogs.com/cnroadbridge/p/15925516.html
Copyright © 2020-2023  润新知