欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

面向?qū)ο蟮腏avascript之三(封裝和信息隱藏)

 更新時(shí)間:2012年01月27日 21:07:57   作者:  
在"初識(shí)Javascript"章節(jié)中,提到通過(guò)作用域和閉包來(lái)隱藏內(nèi)部細(xì)節(jié),并給外部提供訪問(wèn)接口,就初次接觸到了信息隱藏的概念了
同時(shí),我們知道在面向?qū)ο蟮母呒?jí)語(yǔ)言中,創(chuàng)建包含私有成員的對(duì)象是最基本的特性之一,提供屬性和方法對(duì)私有成員進(jìn)行訪問(wèn)來(lái)隱藏內(nèi)部的細(xì)節(jié)。雖然JS也是面向?qū)ο蟮?,但沒(méi)有內(nèi)部機(jī)制可以直接表明一個(gè)成員是公有還是私有的。還是那句話,依靠JS的語(yǔ)言靈活性,我們可以創(chuàng)建公共、私有和特權(quán)成員,信息隱藏是我們要實(shí)現(xiàn)的目標(biāo),而封裝是我們實(shí)現(xiàn)這個(gè)目標(biāo)的方法。我們還是從一個(gè)示例來(lái)說(shuō)明:創(chuàng)建一個(gè)類來(lái)存儲(chǔ)圖書(shū)數(shù)據(jù),并實(shí)現(xiàn)可以在網(wǎng)頁(yè)中顯示這些數(shù)據(jù)。

1. 最簡(jiǎn)單的是完全暴露對(duì)象。使用構(gòu)造函數(shù)創(chuàng)建一個(gè)類,其中所有的屬性和方法在外部都是可以訪問(wèn)的。
復(fù)制代碼 代碼如下:

var Book = function(isbn, title, author) {
if(isbn == undefined) {
throw new Error("Book constructor requires a isbn.");
}
this.isbn = isbn;
this.title = title || "";
this.author = author || "";
}
Book.prototype.display = function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}

display方法依賴于isbn是否正確,如果不是你將無(wú)法獲取圖像以及鏈接。考慮到這點(diǎn),每本圖書(shū)isbn必須存在的,而圖書(shū)的標(biāo)題和作者是可選的。表面上看只要指定一個(gè)isbn參數(shù)似乎就能正常運(yùn)行。但卻不能保證isbn的完整性,基于此我們加入isbn的驗(yàn)證,使圖書(shū)的檢查更加健壯。
復(fù)制代碼 代碼如下:

var Book = function(isbn, title, author) {
if(!this.checkIsbn(isbn)) {
throw new Error("Book: invalid ISBN.");
}
this.isbn = isbn;
this.title = title || "";
this.author = author || "";
}
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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
display: function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}
};

我們添加了checkIsbn()來(lái)驗(yàn)證ISBN的有效性,確保display()可以正常運(yùn)行。但是需求有變化了,每本書(shū)可能有多個(gè)版本,意味著同一本可能有多個(gè)ISBN號(hào)存在,需要維護(hù)單獨(dú)的選擇版本的算法來(lái)控制。同時(shí)盡管能檢查數(shù)據(jù)的完整性,但卻無(wú)法控制外部對(duì)內(nèi)部成員的訪問(wèn)(如對(duì)isbn,title,author賦值),就談不上保護(hù)內(nèi)部數(shù)據(jù)了。我們繼續(xù)改進(jìn)這個(gè)方案,采用接口實(shí)現(xiàn)(提供get訪問(wèn)器/set存儲(chǔ)器)。
復(fù)制代碼 代碼如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "checkIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(isbn, title, author) {
// implements Publication interface
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
getIsbn: function() {
return this.isbn;
},
setIsbn: function(isbn) {
if(!this.checkIsbn(isbn)) {
throw new Error("Book: Invalid ISBN.");
}
this.isbn = isbn;
},
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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
getTitle: function() {
return this.title;
},
setTitle: function(title) {
this.title = title || "";
},
getAuthor: function() {
return this.author;
},
setAuthor: function(author) {
this.author = author || "";
},
display: function() {
return "Book: ISBN: " + this.isbn + ",Title: " + this.title + ",Author: " + this.author;
}
};

現(xiàn)在就可以通過(guò)接口Publication來(lái)與外界進(jìn)行通信。賦值方法也在構(gòu)造器內(nèi)部完成,不需要實(shí)現(xiàn)兩次同樣的驗(yàn)證,看似非常完美的完全暴露對(duì)象方案了。雖然能通過(guò)set存儲(chǔ)器來(lái)設(shè)置屬性,但這些屬性仍然是公有的,可以直接賦值。但此方案到此已經(jīng)無(wú)能為力了,我會(huì)在第二種信息隱藏解決方案中來(lái)優(yōu)化。盡管如此,此方案對(duì)于那些沒(méi)有深刻理解作用域的新手非常容易上手。唯一的不足是不能保護(hù)內(nèi)部數(shù)據(jù)且存儲(chǔ)器增加了多余的不必要代碼。
2. 使用命名規(guī)則的私有方法。就是使用下劃線來(lái)標(biāo)識(shí)私有成員,避免無(wú)意中對(duì)私有成員進(jìn)行賦值,本質(zhì)上與完全暴露對(duì)象是一樣的。但這卻避免了第一種方案無(wú)意對(duì)私有成員進(jìn)行賦值操作,卻依然不能避免有意對(duì)私有成員進(jìn)行設(shè)置。只是說(shuō)定義了一種命名規(guī)范,需要團(tuán)隊(duì)成員來(lái)遵守,不算是一種真正的內(nèi)部信息隱藏的完美方案。
復(fù)制代碼 代碼如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(isbn, title, author) {
// implements Publication interface
this.setIsbn(isbn);
this.setTitle(title);
this.setAuthor(author);
}
Book.prototype = {
getIsbn: function() {
return this._isbn;
},
setIsbn: function(isbn) {
if(!this._checkIsbn(isbn)) {
throw new Error("Book: Invalid ISBN.");
}
this._isbn = isbn;
},
_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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
},
getTitle: function() {
return this._title;
},
setTitle: function(title) {
this._title = title || "";
},
getAuthor: function() {
return this._author;
},
setAuthor: function(author) {
this._author = author || "";
},
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

注意:除了isbn,title,author屬性被加上"_"標(biāo)識(shí)為私有成員外,checkIsbn()也被標(biāo)識(shí)為私有方法。

3. 通過(guò)閉包來(lái)真正私有化成員。如果對(duì)閉包概念中的作用域和嵌套函數(shù)不熟悉的朋友,可以參考"面向?qū)ο蟮腏avascript之一(初識(shí)Javascript)"文章,這里不再詳細(xì)論述。
復(fù)制代碼 代碼如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// private method
function checkIsbn(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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// previleged method
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 || "";
},
this.getAuthor: function() {
return author;
},
this.setAuthor: function(newAuthor) {
author = newAuthor || "";
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

這種方案與上一種有哪些不同呢?首先,在構(gòu)造器中使用var來(lái)聲明三個(gè)私有成員,同樣也聲明了私有方法checkIsbn(),僅僅在構(gòu)造器中有效。使用this關(guān)鍵字聲明特權(quán)方法,即聲明在構(gòu)造器內(nèi)部但卻可以訪問(wèn)私有成員。任何不需要訪問(wèn)私有成員的方法都在Book.prototype中聲明(如:display),也即是將需要訪問(wèn)私有成員的方法聲明為特權(quán)方法是解決這個(gè)問(wèn)題的關(guān)鍵。但此訪問(wèn)也有一定缺陷,如對(duì)每一個(gè)實(shí)例而言,都要?jiǎng)?chuàng)建一份特權(quán)方法的副本,勢(shì)必需要更多內(nèi)存。我們繼續(xù)優(yōu)化,采用靜態(tài)成員來(lái)解決所面臨的問(wèn)題。順便提一句:靜態(tài)成員僅僅屬于類,所有的對(duì)象僅共用一份副本(在"面向?qū)ο蟮腏avascript之二(實(shí)現(xiàn)接口)中有說(shuō)明,參見(jiàn)Interface.ensureImplements方法"),而實(shí)例方法是針對(duì)對(duì)象而言。
復(fù)制代碼 代碼如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = (function() {
// private static attribute
var numsOfBooks = 0;
// private static method
function checkIsbn(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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// return constructor
return function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// previleged method
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!Book.checkIsbn(newIsbn)) {
throw new Error("Book: Invalid ISBN.");
}
isbn = newIsbn;
}
this.getTitle = function() {
return title;
},
this.setTitle = function(newTitle) {
title = newTitle || "";
},
this.getAuthor = function() {
return author;
},
this.setAuthor = function(newAuthor) {
author = newAuthor || "";
}
Book.numsOfBooks++;
if(Book.numsOfBooks > 50) {
throw new Error("Book: at most 50 instances of Book can be created.");
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
})();
// public static methods
Book.convertToTitle = function(title) {
return title.toUpperCase();
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() + ",Author: " + this.getAuthor();
}
};

這種方案與上種相似,使用var和this來(lái)創(chuàng)建私有成員和特權(quán)方法。不同之處在于使用閉包來(lái)返回構(gòu)造器,并將checkIsbn聲明為私有靜態(tài)方法。可能有人會(huì)問(wèn),我為什么要?jiǎng)?chuàng)建私有靜態(tài)方法,答案在于使所有對(duì)象公用一份函數(shù)副本而已。我們這里創(chuàng)建的50個(gè)實(shí)例都只有一個(gè)方法副本checkIsbn,且屬于類Book。根據(jù)需要,你也可以創(chuàng)建公有的靜態(tài)方法供外部調(diào)用(如:convertToTitle)。這里我們繼續(xù)考慮一個(gè)問(wèn)題,假設(shè)以后我們需要對(duì)不同的書(shū)做限制,比如<<Javascript高級(jí)編程>>最大印發(fā)量為500,<<.NET>>最大印發(fā)量為1000,也即說(shuō)需要一個(gè)最大印發(fā)量的常量。思考一下,利用已有的知識(shí),我們?nèi)绾温暶饕粋€(gè)常量呢?其實(shí)不難,我們想想,可以利用一個(gè)只有訪問(wèn)器的私有特權(quán)方法就可以實(shí)現(xiàn)。
復(fù)制代碼 代碼如下:

var Publication = new Interface("Publication", ["getIsbn", "setIsbn", "getTitle", "setTitle", "getAuthor", "setAuthor", "display"]);
var Book = (function() {
// private static attribute
var numsOfBooks = 0;
// private static contant
var Constants = {
"MAX_JAVASCRIPT_NUMS": 500,
"MAX_NET_NUMS": 1000
};
// private static previleged method
this.getMaxNums(name) {
return Constants[name.ToUpperCase()];
}
// private static method
function checkIsbn(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) {
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.charAt(9) != checksum) return false;
} else {
if(!isbn.match(\^\d{12}\)) return false;
for(var i = 0;i < 12;i++) {
sum += isbn.charAt(i) * (i % 2 == 0 ? 1 : 3);
}
var checksum = sum % 10;
if(isbn.charAt(12) != checksum) return false;
}
return true;
}
// return constructor
return function(newIsbn, newTitle, newAuthor) {
// private attribute
var isbn, title, author;
// previleged method
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!Book.checkIsbn(newIsbn)) {
throw new Error("Book: Invalid ISBN.");
}
isbn = newIsbn;
}
this.getTitle = function() {
return title;
},
this.setTitle = function(newTitle) {
title = newTitle || "";
},
this.getAuthor = function() {
return author;
},
this.setAuthor = function(newAuthor) {
author = newAuthor || "";
}
Book.numsOfBooks++;
if(Book.numsOfBooks > 50) {
throw new Error("Book: at most 50 instances of Book can be created.");
}
// implements Publication interface
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
})();
// public static methods
Book.convertToTitle = function(title) {
return title.toUpperCase();
}
// public methods
Book.prototype = {
display: function() {
return "Book: ISBN: " + this.getIsbn() + ",Title: " + this.getTitle() +
",Author: " + this.getAuthor() + ", Maximum: ";
},
showMaxNums: function() {
return Book.getMaxNums("MAX_JAVASCRIPT_NUMS");
}
};

最完美的情況就是你所封裝的程序?qū)φ{(diào)用者而言,僅僅需要知道你的接口就可以,根本不關(guān)心你如何實(shí)現(xiàn)。但問(wèn)題在于,隨著工程量的擴(kuò)大,你的封裝內(nèi)容必然會(huì)增大,在項(xiàng)目發(fā)生交接時(shí),對(duì)于一個(gè)對(duì)作用域和閉包等概念不熟悉的成員來(lái)說(shuō),維護(hù)難度會(huì)變得如此之大。有些時(shí)候應(yīng)需求響應(yīng)必須改動(dòng)源碼(這里不一定指改接口),可能是新增一些細(xì)節(jié),即使拿到你的源碼卻無(wú)從下手,那就不好做了。因此,我的建議:封裝不要過(guò)度,接口一定要清晰,可擴(kuò)展。

相關(guān)文章

最新評(píng)論