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

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

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

1. 最簡單的是完全暴露對象。使用構(gòu)造函數(shù)創(chuàng)建一個類,其中所有的屬性和方法在外部都是可以訪問的。
復(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是否正確,如果不是你將無法獲取圖像以及鏈接??紤]到這點,每本圖書isbn必須存在的,而圖書的標題和作者是可選的。表面上看只要指定一個isbn參數(shù)似乎就能正常運行。但卻不能保證isbn的完整性,基于此我們加入isbn的驗證,使圖書的檢查更加健壯。
復(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()來驗證ISBN的有效性,確保display()可以正常運行。但是需求有變化了,每本書可能有多個版本,意味著同一本可能有多個ISBN號存在,需要維護單獨的選擇版本的算法來控制。同時盡管能檢查數(shù)據(jù)的完整性,但卻無法控制外部對內(nèi)部成員的訪問(如對isbn,title,author賦值),就談不上保護內(nèi)部數(shù)據(jù)了。我們繼續(xù)改進這個方案,采用接口實現(xiàn)(提供get訪問器/set存儲器)。
復(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)在就可以通過接口Publication來與外界進行通信。賦值方法也在構(gòu)造器內(nèi)部完成,不需要實現(xiàn)兩次同樣的驗證,看似非常完美的完全暴露對象方案了。雖然能通過set存儲器來設(shè)置屬性,但這些屬性仍然是公有的,可以直接賦值。但此方案到此已經(jīng)無能為力了,我會在第二種信息隱藏解決方案中來優(yōu)化。盡管如此,此方案對于那些沒有深刻理解作用域的新手非常容易上手。唯一的不足是不能保護內(nèi)部數(shù)據(jù)且存儲器增加了多余的不必要代碼。
2. 使用命名規(guī)則的私有方法。就是使用下劃線來標識私有成員,避免無意中對私有成員進行賦值,本質(zhì)上與完全暴露對象是一樣的。但這卻避免了第一種方案無意對私有成員進行賦值操作,卻依然不能避免有意對私有成員進行設(shè)置。只是說定義了一種命名規(guī)范,需要團隊成員來遵守,不算是一種真正的內(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屬性被加上"_"標識為私有成員外,checkIsbn()也被標識為私有方法。

3. 通過閉包來真正私有化成員。如果對閉包概念中的作用域和嵌套函數(shù)不熟悉的朋友,可以參考"面向?qū)ο蟮腏avascript之一(初識Javascript)"文章,這里不再詳細論述。
復(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來聲明三個私有成員,同樣也聲明了私有方法checkIsbn(),僅僅在構(gòu)造器中有效。使用this關(guān)鍵字聲明特權(quán)方法,即聲明在構(gòu)造器內(nèi)部但卻可以訪問私有成員。任何不需要訪問私有成員的方法都在Book.prototype中聲明(如:display),也即是將需要訪問私有成員的方法聲明為特權(quán)方法是解決這個問題的關(guān)鍵。但此訪問也有一定缺陷,如對每一個實例而言,都要創(chuàng)建一份特權(quán)方法的副本,勢必需要更多內(nèi)存。我們繼續(xù)優(yōu)化,采用靜態(tài)成員來解決所面臨的問題。順便提一句:靜態(tài)成員僅僅屬于類,所有的對象僅共用一份副本(在"面向?qū)ο蟮腏avascript之二(實現(xiàn)接口)中有說明,參見Interface.ensureImplements方法"),而實例方法是針對對象而言。
復(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來創(chuàng)建私有成員和特權(quán)方法。不同之處在于使用閉包來返回構(gòu)造器,并將checkIsbn聲明為私有靜態(tài)方法??赡苡腥藭?,我為什么要創(chuàng)建私有靜態(tài)方法,答案在于使所有對象公用一份函數(shù)副本而已。我們這里創(chuàng)建的50個實例都只有一個方法副本checkIsbn,且屬于類Book。根據(jù)需要,你也可以創(chuàng)建公有的靜態(tài)方法供外部調(diào)用(如:convertToTitle)。這里我們繼續(xù)考慮一個問題,假設(shè)以后我們需要對不同的書做限制,比如<<Javascript高級編程>>最大印發(fā)量為500,<<.NET>>最大印發(fā)量為1000,也即說需要一個最大印發(fā)量的常量。思考一下,利用已有的知識,我們?nèi)绾温暶饕粋€常量呢?其實不難,我們想想,可以利用一個只有訪問器的私有特權(quán)方法就可以實現(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)心你如何實現(xiàn)。但問題在于,隨著工程量的擴大,你的封裝內(nèi)容必然會增大,在項目發(fā)生交接時,對于一個對作用域和閉包等概念不熟悉的成員來說,維護難度會變得如此之大。有些時候應(yīng)需求響應(yīng)必須改動源碼(這里不一定指改接口),可能是新增一些細節(jié),即使拿到你的源碼卻無從下手,那就不好做了。因此,我的建議:封裝不要過度,接口一定要清晰,可擴展。

相關(guān)文章

最新評論