设计模式之单例模式

120 阅读3分钟

设计模式对于解决特定的问题是有价值的,是软件开发人员在软件开发过程中面临的一般问题的典型解决方案。如果一个项目没有这些问题,就没有必要应用它们

单例模式

单例是可以实例化一次的类,并且可以全局访问。这个单个实例可以在我们的整个应用程序中共享,这使得单例模式非常适合管理应用程序中的全局状态。

首先,让我们看看使用 es2015实现的单例模式是什么样的。对于这个例子,我们将构建一个 Counter 类,它有:

  • getInstance method that returns the value of the instance
  • 返回实例的 getInstance 方法
  • getCount method that returns the current value of the counter variable
  • 返回计数器变量的当前值的 getCount 方法
  • an increment method that increments the value of counter by one
  • 将计数器的值增加1的递增方法
  • decrement method that decrements the value of counter by one
  • 将计数器的值减一的递减方法
let counter = 0;

class Counter {
  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

但是,这个类不符合单例模式的规范!一个单例应该只能实例化一次。目前,我们可以创建 Counter 类的多个实例。

let counter = 0;

class Counter {
  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const counter1 = new Counter();
const counter2 = new Counter();

console.log(counter1.getInstance() === counter2.getInstance()); // false

通过两次调用new方法,我们只需将 counter1和 counter2设置为等于不同的class实例。getInstance 方法在 counter1和 counter2上返回的值是对不同实例的引用: 它们并不严格相等!

未命名文件.png

让我们确保只能创建 Counter 类的一个实例。

确保只能创建一个实例的一种方法是创建一个名为 instance 的变量。在Counter的构造函数中,我们可以在创建新实例时将实例设置为对实例的引用。我们可以通过检查实例变量是否已经有一个值来防止新的实例化。如果是这种情况,那么实例已经存在,此时应该抛出一个错误,让用户知道

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const counter = new Counter();
const counter2 = new Counter();
// Error: You can only create one instance!

让我们从 Counter.js 文件中导出 Counter 实例。但是在这样做之前,我们也应该冻结实例。Freeze 方法确保使用代码不能修改实例。无法添加或修改冻结实例上的属性,这将减少意外覆盖实例上的值的风险。

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;

单例优点: 将实例化限制为一个实例可能会节省大量内存空间。不必每次都为新实例设置内存,我们只需为该实例设置内存,该实例将在整个应用程序中被引用。

单例缺点:单例的实例可以在整个应用程序中共享,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。

React 中的状态管理

在React中,我们经常通过诸如 Redux 或 React Context 这样的状态管理工具来管理全局状态,而不是使用单例。尽管它们的全局状态行为看起来类似于单例,但是这些工具提供的是只读状态,而不是单例中的可变状态。当使用 Redux 时,只有纯函数reducers可以在组件通过dispatcher发送action之后更新状态。 虽然拥有全局状态的缺点不会通过使用这些工具而神奇地消失,但我们至少可以确保全局状态按照我们的意愿发生改变,因为组件不能直接更新状态。

本文搬运自www.patterns.dev/. Patterns.dev is free book on design patterns and component patterns for building powerful web apps with vanilla JavaScript and React.