JavaScript设计模式(一):单例模式、工厂模式

设计模式原则

  • 单一功能原则(Single Responsibility Principle)
  • 开放封闭原则(Opened Closed Principle)
  • 里式替换原则(Liskov Substitution Principle)
  • 接口隔离原则(Interface Segregation Principle)
  • 依赖反转原则(Dependency Inversion Principle)

单例模式

单例模式

使用单例模式保证了只存在一个实例,每次获取返回的都是这个实例,当我们对一个类通过 new 关键字生成多个对象,这些对象其实都是同一个,也就是第一次 new 创建的实例。

方式一

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
static getInstance = function () {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}

const s1 = Singleton.getInstance();
const s2 = Singleton.getInstance();
console.log(s1 === s2) // true

这里的 s1 和 s2 都是通过 getInstance 方法创建的,getInstance 方法其实一直返回的都是 Singleton.instance 当初次调用 getInstance 方法时会创建一个 Singleton 实例赋值给 Singleton.instance 以后每次调用该方法返回的都是这个对象。

方式二(闭包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class 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)

上面两种方法有一个共同的缺点,就是我们获取实例还需要去调用 getInstance 方法,这其实带来很多不便,而且也不够直观透明,下面我们通过使用代理类的方式来创建一个透明的单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton {
// ...
}
const ProxySingleton = (function () {
let instance = null;
return function () {
if (!instance){
instance = new Singleton();
}
return instance;
}
})()

const s1 = new ProxySingleton();
const s2 = new ProxySingleton();
console.log(s1 === s2)

使用代理类后,我们直接获取代理类的实例,而代理类返回的就是我们 Singleton 的实例,这里就是通过创建一个构造方法的实例时,如果该方法返回的是一个 object 则实例就是这个返回值的小tips来实现我们的这个功能。

可以看到无论是哪种方法,最重要的一点就是要有一个值来存实例,每次返回的都是这个实例,这样才能保证每次都是相同的值。

工厂模式

工厂模式就是把我们重复的操作交给工厂来帮我们完成,使用工厂函数就需要我们去考虑哪些东西是不变的,又有哪些是变化的,不变的东西是不是就可以抽离出来单独封装。

例如我们现在有一个做蛋糕的工厂,需要对每款蛋糕信息进行统计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function CakeA (name, price) {
this.name = name;
this.price = price;
this.material = ['黄油', '巧克力', '面粉']
}
function CakeB (name, price) {
this.name = name;
this.price = price;
this.material = ['黄油', '芒果', '奶油', '面粉']
}
function CakeFactory (name, price, type) {
switch (type) {
case "A":
return new CakeA(name, price);
case "B":
return new CakeB(name, price);
//.....
default:
//.....
}
}

以上代码我们创建了两款蛋糕,再通过 CakeFactory 工厂来根据不同的类型创建不同的类型蛋糕。不过以上代码存在一个致命的问题,目前只有两款蛋糕还好说,如果有几十种岂不是要创建几十个蛋糕构造函数吗,对于不同类型的蛋糕来说,他们的”变”不就是材料的区别么,那我们把他们的名称,价格单独拿出来,只是根据类型去判断它的原材料不就可以了么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Cake(name, price, material){
this.name = name;
this.price = price;
this.material = material;
}
function CakeFactory (name, price, type) {
let material;
switch (type) {
case "A":
material = ['黄油', '巧克力', '面粉'];
break;
case "B":
material = ['黄油', '芒果', '奶油', '面粉'];
break;
//.....
default:
//.....
}
}

抽象工厂模式

首先理解抽象的概念,在 Java 中可以通过 abstract 来定义一个抽象类,继承该类就必须要实现它内部的抽象方法。那我们在 Javascript 中如何模拟呢?

还是一个蛋糕工厂,我们制作蛋糕就需要面粉和黄油,而面粉和黄油又各自有自己的工厂,下面我们用代码来模拟实现。

1
2
3
4
5
6
7
8
9
// 蛋糕工厂
class CakeFactory {
createButter() {
throw new Error("You need to make it happen yourself")
}
createFlour() {
throw new Error("You need to make it happen yourself")
}
}

我们通过抛出错误来模拟抽象类的效果,蛋糕工厂必须要有黄油和面粉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class FlourFactory {
getType() {
throw new Error("You need to make it happen yourself")
}
}
class TypeAFlourFactory extends FlourFactory {
getType() {
return "A类型面粉"
}
}

class ButterFactory {
getType() {
throw new Error("You need to make it happen yourself")
}
}

class TypeAButterFactory extends ButterFactory {
getType() {
return "A类型黄油"
}
}

然后面粉和黄油又各自有工厂并实现

1
2
3
4
5
6
7
8
class MyCakeFactory extends CakeFactory {
createButter() {
return new TypeAButterFactory();
}
createFlour() {
return new TypeAFlourFactory();
}
}

我们制作蛋糕就使用到了面粉和黄油工厂的产物

1
2
3
4
const cake = new MyCakeFactory();
const cakeFlour = cake.createFlour();
const cakeButter = cake.createButter();
console.log(`这个蛋糕由${cakeFlour.getType()}${cakeButter.getType()}做成的`) // 这个蛋糕由A类型面粉和A类型面粉做成的

最后完成制作蛋糕的过程。


JavaScript设计模式(一):单例模式、工厂模式
https://l1ushun.github.io/2023/09/13/design-mode/
作者
liu shun
发布于
2023年9月13日