前言

单例模式在强类型语言中常常是使用一个类来实现,哪怕在ts中,常见的单例示例也是使用class来实现,但是事实上在js中,不一定非得要用类来实现,我们不应该将实现方式看的太死板,使用class也只是手段的一种,在js中我们有很多种方式达到相同的效果。

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

拙劣的模仿

在强类型语言中,会给类一个getInstance的方法用于获取实例对象,在js中我们也可以这么写:

function User(name) {
  this.name = name;
  this.instance = null;
}

User.prototype.getInstance = function (name) {
  if (!this.instance) {
    this.instance = new User(this.name);
  }
  return this.instance;
};

const a = User.getInstance("a");
const b = User.getInstance("b");

console.log(a === b); // true

可以看到我们约定俗成的使用方式下,也可以模仿出相同的效果,但是这种方式有个弊端,他没法通过new去创建实例,且无法使得new出的实例永远是同一个。

为此我们可以使用闭包的方式。

var Singleton = (function () {
  var instance = null;

  function User(name) {
    if (instance) return instance;
    this.name = name;

    return (instance = this);
  }

  User.prototype.getInstance = function (name) {
    if (!this.instance) {
      this.instance = new User(this.name);
    }
    return this.instance;
  };

  return User;
})();

const a = new Singleton("a");
const b = new Singleton("b");

console.log(a === b); // true

通过闭包创建局部变量instance,然后判断局部变量是否有值,这个值就是User的实例对象,返回即可。

我们还可以看一下ts中单例的实现与转化为js后的代码:

class Singleton {
    private static instance: Singleton;

    private constructor() { }

    public static getInstance(): Singleton {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    }

    public someMethod() {
        console.log("调用了单例对象的方法");
    }
}

// 使用单例模式
const singletonInstance1 = Singleton.getInstance();
const singletonInstance2 = Singleton.getInstance();

console.log(singletonInstance1 === singletonInstance2); // 输出 true,说明两个实例是同一个
singletonInstance1.someMethod(); // 调用单例对象的方法

转换为js后:

"use strict";
var Singleton = /** @class */ (function () {
    function Singleton() {
    }
    Singleton.getInstance = function () {
        if (!Singleton.instance) {
            Singleton.instance = new Singleton();
        }
        return Singleton.instance;
    };
    Singleton.prototype.someMethod = function () {
        console.log("调用了单例对象的方法");
    };
    return Singleton;
}());
// 使用单例模式
var singletonInstance1 = Singleton.getInstance();
var singletonInstance2 = Singleton.getInstance();
console.log(singletonInstance1 === singletonInstance2); // 输出 true,说明两个实例是同一个
singletonInstance1.someMethod(); // 调用单例对象的方法

你可以发现其实是差不多的原理。

饿汉式和懒汉式

他们的区别就是饿汉式在代码没有被实际用户使用时就已经创建好了对应的实例,而懒汉式则是用户实际用到了才会去创建,上面的代码都是懒汉式的。

泛用型单例封装

在实际的代码中,单例模式的使用,往往除了上面的代码量,还会有很多其他的业务逻辑,我们常常会陷入一个很头疼的选择,如何保证代码的单一职责

理论上单例模式本身是一种职责,而具体的业务是另一种职责,他们应该分开来做最合适,但是在一些强类型语言,或者说在ts中,想要做到这种解耦是很难的,但是在js中,你会发现解耦这种操作,简直小菜一碟。

function getSingleton(fn) {
    var instance = null;

    return function() {
        if (instance) return instance;
        return (instance = fn.apply(this, arguments));
    };
}

function User(name) {
    this.name = name;
}

var userSingleton = getSingleton(User, "a");

const a = userSingleton();
const b = userSingleton();

console.log(a === b); // true

如果我们想要在ts中也实现这种泛用性的单例封装,就得使用一个工厂类来做更加合适:

class SingletonFactory {
    private static instances: { [key: string]: any } = {};

    public static getInstance<T>(clazz: { new(): T }): T {
        const className = clazz.name;
        if (!SingletonFactory.instances[className]) {
            SingletonFactory.instances[className] = new clazz();
        }
        return SingletonFactory.instances[className];
    }
}

// 示例业务类
class BusinessClassA {
    public someMethod() {
        console.log("调用了业务类A的方法");
    }
}

class BusinessClassB {
    public someMethod() {
        console.log("调用了业务类B的方法");
    }
}

// 使用单例工厂类
const instanceA1 = SingletonFactory.getInstance(BusinessClassA);
const instanceA2 = SingletonFactory.getInstance(BusinessClassA);

当然这里投机取巧用了class类的一个name属性,虽然说没什么问题,但是实际上会有大问题,因为我们的代码在实际打包后会编译混淆,可能会有存在同名类的情况,我们可以改用map类型来存储,这样的话可以避免重复的问题,使得代码更加健壮。

class SingletonFactory {
    private static instances: Map<any, any> = new Map();

    public static getInstance<T>(clazz: { new(): T }): T {
        if (!SingletonFactory.instances.has(clazz)) {
            SingletonFactory.instances.set(clazz, new clazz());
        }
        return SingletonFactory.instances.get(clazz);
    }
}

// 示例业务类
class BusinessClassA {
    public someMethod() {
        console.log("调用了业务类A的方法");
    }
}

class BusinessClassB {
    public someMethod() {
        console.log("调用了业务类B的方法");
    }
}

// 使用单例工厂类
const instanceA1 = SingletonFactory.getInstance(BusinessClassA);
const instanceA2 = SingletonFactory.getInstance(BusinessClassA);

const instanceB1 = SingletonFactory.getInstance(BusinessClassB);
const instanceB2 = SingletonFactory.getInstance(BusinessClassB);

console.log(instanceA1 === instanceA2); // 输出 true,说明两个实例是同一个
console.log(instanceB1 === instanceB2); // 输出 true,说明两个实例是同一个

instanceA1.someMethod(); // 调用业务类A的方法
instanceB1.someMethod(); // 调用业务类B的方法

对于不想使用any的人来说,可能会很难受吧,哈哈哈。

分类: JavaScript设计模式与开发实践 标签: JavaScript模式单例模式泛用型

评论

暂无评论数据

暂无评论数据

目录