在native中,创建对象的方式千差万别,设计模式中的创建型则基于这些不同的方式

1.简单工厂模式:Simple Factory

工厂模式什么意思? 如果把我们编写业务代码的过程,比作造一台电视机,那么工厂模式就是先打造好生产电视机的流水线。如何打造生产线??? 我们将需要工厂制造的产物的特性抽象出来,在代码上表现为我们规定其属性名称,但其属性值又参数来决定。

1
2
3
4
5
6
7
function createBook(name, pages, author) {
let newBook = {};
newBook.name = name;
newBook.pages = pages;
newBook.author = author;
return newBook;
}

这是一种工厂模式,其实现方式与之前学基础的寄生继承有点相似,也是在一个新的变量上去添加属性,使用字面量方式创建对象会比使用new高效的多。

还有一种就是先将不同型号的电视机造出来,然后通过工厂来输送到不同的产品区。 这里的工厂不在制造,而是充当deliver的角色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function fictionBook() {
this.name = '九州缥缈录';
this.story = '铁甲依然在';
this.tellStory = function(){
console.log(this.story)
}
}

function classicBook() {
this.name = '假如给我三天光明';
this.author = '海伦凯勒';
this.bookauthor = function () {
console.log(this.author)
}
}

function createBook(type) {
switch (type) {
case 'fiction':
return new fictionBook();
case 'classic':
return new classicBook();
}
}

两种方式优缺点十分明显, 第一种常见方式需要我们固定某些参数类型,限制了其多样性,但相比第二种省事多了。第二种则相反,每创造一种不同型号的电视机就需要我们去创建一个新的类并向分发类添加该选项。

2.工厂方法模式: Factory Method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//安全模式    
const Factory = function (type, content) {
if (this instanceof Factory) {
return new this[type](content);
} else {
return new Factory(type, content);
}
}

Factory.prototype = {
show: function(content) {
this.content = content;
console.log(content);
}
}
const test = new Factory('show', '2');

安全模式的好处在于,在简单工厂模式上加了一层限制,如果没有创建工厂类,就直接执行该函数而不是返回工厂对象。

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
29
30
31
32
33
34
35
36
37
38
39
40
//抽象模式
const abstractFactory = function (superType, subType) {
if (typeof abstractFactory[superType] === 'function' ) {
function F() {};
F.prototype = new abstractFactory[superType]();
subType.constructor = subType;
subType.prototype = new F();
} else {
throw new Error('未创建该抽象类')
}
}

abstractFactory.desk = function() {
this.type = 'desk';
}

abstractFactory.desk.prototype = {
getDeskSize: function () {
return new Error('抽象方法不能调用')
},
getDeskMaterial: function() {
return new Error('抽象方法不能调用')
}
}
//实例化抽象desk类

// 1.子类构造方法
let machilusDesk = function(size, material) {
this.size = size;
this.material = material;
}
// 2. 将子类构造方法送入工厂,继承工厂抽象方法
abstractFactory('desk', machilusDesk);
// 3. 覆盖抽象方法
machilusDesk.prototype.getDeskSize = function() {
return this.size;
}
machilusDesk.prototype.getDeskMaterial = function() {
return this.material;
}

JS中,abstract是一个保留关键字,所以想要实现抽象类,是不可能像传统的面向对象语言那么方便,但由于其灵活性,我们可以模拟出抽象类的主要行为。

抽象方法时不可调用的,会报错,所以我们模拟出其这个行为,并且采用寄生继承来继承父类。当工厂生成子类后,覆盖抽象方法,就不会报错了。

抽象模式是提前定义了类的结构,而不是直接创建类。就像建房子先画好房子的图纸一样,先描述出其轮廓~

JS中不支持抽象化创建和虚拟方法,所以其引用并不广泛,但也是面向对象编程中很经典常见的一种模式。

3.建造者模式: Builder

如果说工厂模式的侧重点在创建的结果,那么建造者就是注重创建过程的一种模式。

建造者会注意创建过程的每个细节,就像我们捏橡皮人时,不光要注意身体的轮廓,还要注意头有多大,四肢有多长….理所当然的创建过程会复杂很多。 其过程就是将各种特性都封装成类,然后在主类里面根据不同参数,实例化不同的特性,组成新的子类,或者叫其复合对象。

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
function House(parmas) {
this.material = parmas && parmas.material || '**';
this.area = parmas && parmas.area || '**';
}
House.prototype = {
getMaterial: function() {
return this.material;
},
getArea: function() {
return this.area;
}
}

function material(material) {
this.material = material;
}

function area(m) {
this.area = m * m;
}

function newHouse(material, m) {
let _house = new House();
_house.material = new material(material);
_house.area = new area(area);
return _house;
}

就像搭积木一样,先把各块积木的形状找好,然后组合在一起。抛出一个问题,如果要在一个网页上呈现卡片堆砌的效果,你会怎么做呢? 个人理解,这个模式是我们经常解决生活中问题的一种方法的抽象,复杂的问题简单化,就是将一个复杂的事物拆分成多个简单的事物,再以某种方式组合起来,来解决问题的一种方法。

4.原型模式:prototype

其被称为语言之魂,它将原型对象指向创建对象的类,使这些类共享原型对象的方法和属性。

存在即合理,那么它解决了什么问题?在很多复杂的功能下,我们的子类需要继承父类的一些属性与方法,但是并非所有子类需求的属性和方法都是一致的,所以子类会从父类继承一些不必要的,增加额外开销的属性和方法。例如,一个车的父类,子类可能是跑车,轿车,SUV,越野,跑车需要整个车体结构和轿车不一样,但父类因为考虑整体性不会有特殊的车体结构,而是通用的轿车结构,那么子类就从父类继承了一些额外的负担性质的属性和方法。而子类每次实例化都会造成很多不可避免的额外开销。

原型概念就此而出,它将某些可复用,可共享的,开销大的属性和方法从父类提取出来放在原型之中,子类通过前文的某些继承方式来继承,直接获得这些属性和方法而不是每次实例化重新创建这些属性和方法,节省了很多不必要的开销。

js在原型模式下实现的原型链,让所有内置的属性和方法都享受到该模式的好处。

其实现方式就是对一个对象的拷贝,也是一种继承方式,但很特殊的一点,创建过程于它无瓜,理解这一点很重要~

5.单例模式: singleton

这是js编程中很常见的一种模式,只允许实例化一次的对象类。有时生成命名空间namespace。多人协作开发或者加载独立的第三方库的时候,我们的词语总是有限的,怎么让它们的命名互不影响呢?答案说都开辟独立的命名空间,使得其方法名都可以区分开。同时,对我们在管理静态变量也非常有帮助。

风靡很多时的Jquery就是经典的例子,其$符就是命名空间,$('className').function这样的调用方式使得代码清晰明了,一看就知道是Jquery的方法。

1
2
3
4
5
6
7
8
9
10
11
let mynamespace = {
utils: {
formate: function() {
.....
}
},
ajax: function() {
.....
}
MYHEIGHT: 180
}

其实就是封装在一个对象里面,延展开来就是模块机制,把整个js文件作为一个对象,存放不同的命名空间,避免方法名的覆盖。

怎么实现只能实例化一次的对象类? 妈耶,用个闭包记录,如果被实例化了,就return掉,easy。

什么? 怎么创建一个闭包??? 建议你去看看《你不知道的javascript》,书中详细的论述了闭包是什么,怎么创建闭包,闭包的用途又是什么,闭包背后的作用机制其实是保持对作用域的引用等等。下次有空,结合书本我们来详解一下js中的闭包,解开其神秘的面纱。

总结

创建型设计模式是一些处理对象创建的模式,通过某种方式控制对象的创建来避免基本对象创建时可能导致设计上的问题或增加设计上的复杂度。

创建单类简单对象的简单工厂模式,简单的缺点也很明显,于是工厂方法模式补足创建多类的实例(其实是传入不同参数的简单工厂模式和安全模式的混合)。建造者模式则是通过组合多种简单工厂模式,来创造复杂的对象。原型模式也被成为语言之魂,将某些属性和方法放在原型对象中作为所有对象的共有属性和方法,避免了子类每次实例化都会可能创建新的不必要的,开销大的属性和方法。单例模式则是为我们的代码管理做出了卓越的贡献。这五种模式中,原型模式并不关心对象创建过程,但对创建过程做出的贡献还是有目共睹,或许这就是大佬吧,润物细无声~

评论