更多文章参见: https://github.com/elevenbeans/elevenbeans.github.io

前言

本系列可以看作是我个人对于 Addy Osmani 的著作《Learning JavaScript Design Patterns》中内容的提炼,类似阅读笔记,目的是为了简单快速、又不失全面地了解设计模式的相关概念、特点、分类及其在 Javascript 中的实际用法。

分为五篇:

  1. 概述
  2. 创建型设计模式(本篇)
  3. 结构型
  4. 行为型
  5. MV*(待补充)

创建型设计模式

1. Constructor(构造器) 模式

基本的 Constructor

Javascript 不支持类,但支持 constractor 构造器函数。可以通过构造器前加new关键字告诉 Javascript 实例化一个对象。一个构造器的例子:

1
2
3
4
5
6
7
8
9
10
11
12
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
this.toString = function() {
return this.model + 'has done' + this.miles + 'miles';
};
}
var benz = new Car('GLS', 2018, 20000);
console.log(benz.toString()); // GLShas done20000miles

存在的问题是: this.toString() 这样的函数在每次创建新的对象的时候都会重新定义,这样很不理想,这类共有方法应该在所有实例间共享。

带原型的 Constructor

1
2
3
4
5
6
7
8
9
10
11
12
function Car(model, year, miles) {
this.model = model;
this.year = year;
this.miles = miles;
}
Car.prototype.toString = function() {
return this.model + 'has done' + this.miles + 'miles';
};
var benz = new Car('GLS', 2018, 20000);
var byd = new Car('qin', 2016, 68000);

如此一来,toString() 方法的单一实例就能通过原型链在 Car 的实例对象 benzbyd 中共享。

2. Factory(工厂) 模式

工厂模式并不显式要求使用构造函数。他通过提供一个通用的接口来创建对象,在接口的入参中指定需要创建的对象类型。具体例子如下:

首先定义不同类型的构造函数供给工厂消费。

1
2
3
4
5
6
7
8
9
10
11
12
// Car 构造函数
function Car(opt) {
this.doors = opt.doors || 4;
this.state = opt.state || 'new';
this.color = opt.color || 'slivler';
}
// Truck 构造函数
function Truck(opt) {
this.state = opt.state || 'used';
this.whellSize = opt. whellSize || 'large';
this.color = opt.color || 'blue';
}

然后定义 vehicle 工厂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vehicle 工厂
function VehicleFactory() { }
// 定义 vehicle 工厂的原型和默认工具(Car)
VehicleFactory.prototype.vehicleClass = Car;
// 定义工厂创建实例的 API: createVehicle
VehicleFactory.prototype.createVehicle = function (opt) {
if (opt.vehicleType === 'car') {
this.prototype.vehicleClass = Car;
} else if (opt.vehicleType === 'truck') {
this.prototype.vehicleClass = Truck;
} else {
this.prototype.vehicleClass = Car;
}
return new this.prototype.vehicleClass(opt);
};

工厂创建完毕,具体实例的创建方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个汽车(Car)实例
var factory = new VehicleFactory(); // 首先实例化一个 vehicle 工厂
var carIntance = factory.createVehicle({ // 使用工厂 API 创建 Car 实例
vehicleType: 'car',
color: 'yellow',
door: 6
});
// 创建一个卡车(Truck)实例
var trunkIntance = factory.createVehicle({ // 使用工厂 API 创建 Truck 实例
vehicleType: 'truck',
color: 'yellow',
state: 'new',
wheelSize: 'small'
});

一般来讲,Factory 模式适用于

  • 复杂度较高的对象和组件 / 处理很多共享属性的对象或组件
  • 需要在不同环境生成不同类型的实例对象的时候

3. Abstract(抽象) 模式

对于上述的工厂,createVehicle 一经定义,就只支持 Car 和 Truck 两种类型的实例产出,无法动态增加新的实例类型。

所以需要抽离出来实例对象的产出细节,封装一个通用工厂。

抽象模式(在工厂模式中的)实践如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var AbstractVehicleFactory = (function(){
var types = [];
return {
getVehicle: function (type, opt) {
var Vehicle = types[type];
return (Vehicle) ? return new Vehicle(opt) : null;
},
registerVehicle: function (type, Vehicle) {
var proto = Vehicle.prototype;
// 只对符合基本契约的进行注册
if (proto.drive && proto.breakDown) {
types[type] = Vehicle;
}
return AbstractVehicleFactory;
}
}
})();
// 用法
// 动态注册一个 Car
AbstractVehicleFactory.registerVehicle('car', Car);
// 实例化刚才注册的 Car
var car = AbstractVehicleFactory.getVehicle('car', {
color: 'yellow',
door: 6
});

4. Prototype(原型) 模式

原型模式是一种基于现有对象模版,通过克隆方式创建新对象的一种模式。

该模式是 Javascript 语言本身的优势。具体例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var car = {
name: 'Ford Escort',
drive: function () {
console.log('Weeee. I am driving');
},
panic: function () {
console.log('Wait. How do you stop this?');
}
}
// 使用 Object.create 实例化一个 Car
var myCar = Object.create(car);
// car 是 myCar 的原型
console.log(myCar)

当然,Object.create() 完全可以通过第二个参数实现差异化继承。

1
2
3
4
5
6
7
8
9
10
Object.create(orgObj, {
id: {
value: 23324324,
enumerable: true
},
'model': {
value: 'Ford',
enumerable: true
}
})

如果不用 Object.create() 实现 Prototype(原型) 模式也很简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
car.init = function (opt) {
this.name = opt;
};
function Car () {
function C() {};
C.prototype = car;
var c = new C();
return c;
}
var myCar = Car();
myCar.init('Benz');

本质其实就是将源对象赋值给目标对象的原型,初始化可以通过为源对象预留 API(本例中为init())来实现。

5. Singleton(单例) 模式

一句话来形容,Singleton(单例) 模式限制类的实例化次数只能是一次。也就是说,在不存在该类实例的情况下,可以通过方法来创建实例;如果实例已经存在,返回该实例对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Car(){
this.name = 'Singleton Car' ;
};
var Singleton = {
instance: null,
getInstance: function() {
if (!this.instance) {
this.instance = new Car();
}
return this.instance;
}
};

就像这样当我们每次调用 Singleton.getInstance() 时,就会得到唯一的实例。

如此,可以将我们反复使用的对象只创建一次。减少不必要开销。

Singleton 模式还可以结合 JS 闭包反复引用内存中的同一对象,使运用程序更快速的执行。

6. Builder(生成器) 模式

将一个复杂对象的构造与它的表示相分离,使同样的创建过程可有不同的表示,这就叫做建造者模式。主要角色:

  1. Builder 这个接口类,定义这个建造者,统一的可操作的行为方式,它表示一个复杂的结构对象;
  2. Director 这个指挥者,用于指导 Builder 实例的执行过程跟形式,用于与 Builder 的实例 表现 相分离,用于指导 这个 Builder 实例 按某规则顺序来创建生成 产品结果;
  3. ResultObject 创建的结果都会生成一个结果对象;这是具体创建者根据 Director 指导创建的结果;

建造者模式实际,就是一个指挥者,一个建造者,外加一个使用指挥者调用具体建造者工作、并得从具体建造者得出结果的客户。模拟场景:

一户家人要建房子(ResultObject / 结果),但房子主人或家里其他人是不懂得如何去建房子的,所以他得去请几个工人(Builder / 建造者),这个建房子的队伍还得有个工头(Director / 指挥者),来按房主人的想法来建一套房子,工头按房主人(客户)的要求设计要求工人如何如何做。

工头说,第一步先把房整体骨架搭起来,第二步睡房建造好,第三步把厨房装饰好,第四步把客厅建造装饰完毕…

工头是不做事的,但具体工人必须按照工头的要求来做,第一步,第二步的这样步骤来建造,直至整个房子完成;

工人必须要有创建这个房屋的所有技能,即建骨架,装饰卧室等,即工人所做的事,或所具有的能力,必须大于或等于工头要求要做的事,或具有的能力;

即工头是个组织者,而工人提供技能;

实例代码:

1. Builder 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function workerBuilder() {
this.workOne = function() {
// 建房子骨架
}
this.workTwo=function() {
// 建卧室
}
this.workThree=function() {
// 建厨房
}
this.workFour=function() {
// 建客厅
}
//....
this.getResult = function() {
// 建成房子
var house = new House();
// house.HouseFrame ...
return house;
}
}

workBuilder 是具体建造者类,workOne, Two是要做的事情,建骨架等。
当然 workBuilder 可以多建几个,用于表示建造者对于每个工作执行的方法不一样;但工作内容是一样的;

2. Director 类

1
2
3
4
5
6
7
8
9
10
11
function Director() {
this.construct = function(builder) {
builder.workOne();
builder.workTwo();
builder.workThree();
builder.workFour();
//...
//上面的内容,顺序可设置,并且工作项也可以设定
}
}

指挥者类下的指导方法,有对建造者的回调引用,内容包括建者工作内容几项或全部; 指挥者对建造者要做的事情进行组织跟安排。

3. ResultObject

1
2
3
4
5
6
7
function House() {
this.HouseFrame = '';
this.Room = '';
this.Kitchen = '';
this.LivingRoom = '';
//...
}

4. 使用方法

1
2
3
4
var director = new Director(); // 房主人请 Director 工头来建房子
var builder = new workBuilder(); // 工头带来了建造者
director.construct(builder); // 工头指挥建造者来建房子
var house = builder.getResult(); // 房主人从建造者那里取得建好的房子

Referrence

https://www.2cto.com/kf/201412/360781.html

创建型设计模式汇总介绍到此结束,持续关注请 Star and Watch This github repo, 谢谢 :)