JS设计模式---3.封装

封装之利

  • 保证内部数据完整,易于重构
  • 弱化模块间耦合,提高对象可重用性

封装之弊

  • 单元测试困难,错误调试困难
  • 过度封装会损失类的灵活性
  • 对新手不友好

创建对象的基本模式

需求:创建一个存储一本书的类,并为其实现一个以 HTML 形式显示数据的方法。你只负责创建这个类,别人会创建和使用其实例。

1
2
3
// 使用方式
var theHobbit = new Book('0-395-07122-4','The Hobbit','J.R.R. Tolkien');
theHobbit.display() // 以HTML形式显示数据
门户大开型对象
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
var Publication = new interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor',
'setAuthor', 'display'
])
var Book = function (isbn, title, author) {
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
checkIsbn = function (isbn) {
// ...
},
getIsbn: function () {
return this.isbn;
},
setIsbn: function (isbn) {
if (!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN');
this.isbn = isbn;
},
getTitle: function () {
return this.title;
},
setTitle: function (title) {
this.title = title || 'No title specified';
},
getAuthor: function () {
return this.author;
},
setAuthor: function (author) {
this.author = author || 'No author specified';
},
display: function () {
// ...
}
}

上述代码定义了一个接口,那么其他程序员就应该只能使用接口中定义的属性和方法。
这是门户大开型对象创建方式所能得到的最好结果。一个明确定义的接口,一些数据的赋值器和取值器,及一些检验方法。
缺点就是虽然我们定义了赋值器方法,但是这些属性仍然是公开的、可以直接设置的。

命名规范区分私有成员
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
var Book = function (isbn, title, author) {
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
_checkIsbn = function (isbn) {
// ...
},
getIsbn: function () {
return this._isbn;
},
setIsbn: function (isbn) {
if (!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN');
this._isbn = isbn;
},
getTitle: function () {
return this._title;
},
setTitle: function (title) {
this._title = title || 'No title specified';
},
getAuthor: function () {
return this._author;
},
setAuthor: function (author) {
this._author = author || 'No author specified';
},
display: function () {
// ...
}
}

这是一种程序员约定俗成的方法,加下划线表示私有变量。但是 JS 中跟本没有私有变量的定义,只能说是总所周知的命名规范。缺点也很明显,既然是约定,那么只有在遵守时才有效果。

闭包实现私用成员
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
var Book = function (newIsbn, newTitle, newAuthor) {
//私有属性
var isbn, title, author;
// 私有方法
function checkIsbn(isbn) {
// ...
};
// 特权方法
this.getIsbn = function () {
return isbn;
};
this.setIsbn = function (newIsbn) {
if (!checkIsbn(newIsbn)) throw new Error('Book:Invalid ISBN');
isbn = newIsbn;
};
this.getTitle = function () {
return title;
};
this.setTitle = function (newTitle) {
title = newTitle || 'NO title specified';
};
this.getAuthor = function () {
return author;
};
this.setAuthor = function (newAuthor) {
author = newAuthor || 'NO author specified';
};
// 构造函数 赋值
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
// 公共方法
Book.prototype = {
display() {
// ...
}
}

这种方式创建的对象具有真正的私有属性,解决了可以直接取值赋值的问题。缺点是会耗费更多的内存,而且不利于派生子类。在 JS 中,用闭包实现私用成员导致的派生问题被称为”继承破坏封装“。

MORE

静态成员

作用域和闭包可用于创建静态成员。大多数方法和属性所关联的是类的实例,而静态成员所关联的是类本身。

  • 一个栗子
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
41
42
43
44
45
46
47
48
49
50
51
52
53
var Book = (function () {
// 私有静态属性
var numOfBookes = 0;
// 私有静态方法
function checkIsbn(isbn) {
// ...
};
// 返回构造器
return function (newIsbn, newTitle, newAuthor) {
// 私有属性
var isbn, title, author;
// 特权方法
this.getIsbn = function () {
return isbn;
};
this.setIsbn = function (newIsbn) {
if (!checkIsbn(newIsbn)) throw new Error('Book:Invalid ISBN');
isbn = newIsbn;
};
this.getTitle = function () {
return title;
};
this.setTitle = function (newTitle) {
title = newTitle || 'NO title specified';
};
this.getAuthor = function () {
return author;
};
this.setAuthor = function (newAuthor) {
author = newAuthor || 'NO author specified';
};

numOfBookes++;
if (numOfBookes > 50) {
throw new Error('Book:Only 50 instance of book can be created.')
}

// 赋值
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
}
})();
// 公共静态方法
Book.converToTitleCase = function (inputString) {
// ...
};
// 公共方法 非特权
Book.prototype = {
display:function() {
// ...
}
}

闭包,返回一个构造器,私用成员和特权成员仍然在构造器中。但是闭包中可以访问静态成员。优点在于所有的静态成员只会存在一份,这样大大减小了内存的消耗。
q:如何判断是否设计成静态成员?
a:一般情况下,我们只需要看一个属性或者方法是否需要访问实例数据。如果不需要,那么设计成静态成员会更有效率。

常量

常量的取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Class = (function () {
var constans = {
UPPER_BOUND: 100,
LOWER_BOUND: -100
};
var ctor = function () {
// ...
}
// 特权静态方法
ctor.getConstans = function (name) {
return constans[name];
}
return ctor
})()
// 取值
Class.getConstans('UPPER_BOUND')