文章目录
为对象创建私用成员是任何面向对象语言中最基本和有用的特性之一。通过将一个方法或属性声明为私用的,可以让对象的实现细节对其他对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束。在代码有许多人参与设计的情况下,这也可以使代码更可靠、更易于调试。简而言之,封装是面向对象的设计的基石。
尽管JavaScript是一种面向对象的语言,它并不具备用以将成员声明为公用或私用的任何内置机制。目前有几种办法可以用来创建具有公用、私用和特权方法的对象,它们各有优缺点。
3.1 信息隐藏原则
3.1.1 封装与信息隐藏
可以把封装和信息隐藏视为同一个概念的两种表述。信息隐藏是目的,封装是达到这个目的的技术。
封装可以被定义为对对象的内部数据表现形式和实现细节进行隐藏。要想访问封装过的对象中的数据,只有使用已定义的操作这一种办法。通过封装可以强制实施信息隐藏。许多面向对象语言都使用关键字来说明某些方法和属性应被隐藏。但在JavaScript中没有这样的关键字,我们将使用闭包的概念来创建只允许从对象内部访问的方法和属性。这比使用关键字的办法更复杂。
3.1.2 接口扮演的角色
在向其他对象隐藏信息的过程中接口是如何发挥作用的呢?
接口提供了一份记载着可供公众访问的方法的契约。它定义了两个对象间可以具有的关系。只要接口不变,这个关系的双方都是可替换的。大多数情况下,你将发现对可以使用的方法加以记载会很有好处。不是有了接口就万事大吉,你应该避免公开未定义于接口中的方法。否则其他对象可能会对那些并不属于接口的方法产生依赖,这是不安全的。因为这些方法随时都可能发生改变或被删除,从而导致整个系统失灵。
一个理想的软件系统应该为所有类定义接口。这些类只向外界提供它们实现的接口中规定的方法,任何别的方法都留作自用。其所有属性都是私有的,外界只能通过接口中定义的存取操作与之打交道。但实际的系统很少能真正达到这样的境界。优质的代码应尽量向这个目标靠拢,但又不能过于刻板,把那些并不需要这些特性的简单项目复杂化。
3.2 创建对象的基本模式
JavaScript创建对象的基本模式有3种
- 门户大开型,这是最简单的一种,但它只能提供公用成员。
- 使用下划线表示私用方法或属性。
- 使用闭包来创建真正的私用成员,这些成员只能通过一些特权方法访问。
以Book为例,该类满足这样的需求:存储关于一本书的数据,并实现一个以HTML形式显示这些数据的方法。
你只负责创建这个Book类,别人会创建并使用其实例。它会被这样使用:
// Book (isbn, title, author)
var theHobbit = new Book('0-395-07122-4', 'The Hobbit', 'J. R. R. Tolkien');
theHobbit.display();//通过创建HTML element显示数据
3.2.1 门户大开型对象
用一个函数来做其构造器,它的所有属性和方法都是公开的、可访问的。这些公用属性需要使用this关键字来创建。
var Book = function(isbn, title, author) {
if(isbn == undefined) throw new Error('Book constructor requires an isbn.');
this.isbn = isbn;
this.title = title || 'No title specified';
this.author = author || 'No author specified';
};
Book.prototype.display = function () {
...
}
好像提供了ISBN就可以查到书籍了,可是这里有一个最大的问题,你无法检验ISBN数据的完整性,而不完整的ISBN数据有可能导致display方法失灵。如果Book对象在创建的时候没有什么问题,那么在display时也能正常工作才对,但是由于没有进行完整性检查,就不一定了。下面的版本强化了对ISBN的检查。
var Book = function(isbn, title, author) {
if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.');
this.isbn = isbn;
this.title = title || 'No title specified';
this.author = author || 'No author specified';
}
Book.prototype = {
checkIsbn: function(isbn) {
if(isbn == undefined || typeof isbn != 'string'){
return false;
}
isbn = isbn.replace(/-/,'');
if(isbn.length != 10 && isbn.length !=13) {
return false;
}
var sum = 0;
if(isbn.length === 10) {
//10位的ISBN
if(!isbn.match(/^\d{9}/)) {
return false;
}
for(var i = 0; i < 9; i++) {
sum += isbn.charAt(i) * (10-i);
}
var checksum = sum % 11;
if(checksum === 10) checksum = 'X';
if(isbn