W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
面向?qū)ο缶幊套钪匾脑瓌t之一 —— 將內(nèi)部接口與外部接口分隔開來。
在開發(fā)比 “hello world” 應(yīng)用程序更復(fù)雜的東西時(shí),這是“必須”遵守的做法。
為了理解這一點(diǎn),讓我們脫離開發(fā)過程,把目光轉(zhuǎn)向現(xiàn)實(shí)世界。
通常,我們使用的設(shè)備都非常復(fù)雜。但是,將內(nèi)部接口與外部接口分隔開來可以讓我們使用它們且沒有任何問題。
例如,一個(gè)咖啡機(jī)。從外面看很簡(jiǎn)單:一個(gè)按鈕,一個(gè)顯示器,幾個(gè)洞……當(dāng)然,結(jié)果就是 —— 很棒的咖啡!:)
但是在內(nèi)部……(一張摘自維修手冊(cè)的圖片)
有非常多的細(xì)節(jié)。但我們可以在完全不了解這些內(nèi)部細(xì)節(jié)的情況下使用它。
咖啡機(jī)非常可靠,不是嗎?一臺(tái)咖啡機(jī)我們可以使用好幾年,只有在出現(xiàn)問題時(shí) —— 把它送去維修。
咖啡機(jī)的可靠性和簡(jiǎn)潔性的秘訣 —— 所有細(xì)節(jié)都經(jīng)過精心校并 隱藏 在內(nèi)部。
如果我們從咖啡機(jī)上取下保護(hù)罩,那么使用它將變得復(fù)雜得多(要按哪里?),并且很危險(xiǎn)(會(huì)觸電)。
正如我們所看到的,在編程中,對(duì)象就像咖啡機(jī)。
但是為了隱藏內(nèi)部細(xì)節(jié),我們不會(huì)使用保護(hù)罩,而是使用語言和約定中的特殊語法。
在面向?qū)ο蟮木幊讨?,屬性和方法分為兩組:
如果我們繼續(xù)用咖啡機(jī)進(jìn)行類比 —— 內(nèi)部隱藏的內(nèi)容:鍋爐管,加熱元件等 —— 是咖啡機(jī)的內(nèi)部接口。
內(nèi)部接口用于對(duì)象工作,它的細(xì)節(jié)相互使用。例如,鍋爐管連接到加熱元件。
但是從外面看,一臺(tái)咖啡機(jī)被保護(hù)殼罩住了,所以沒有人可以接觸到其內(nèi)部接口。細(xì)節(jié)信息被隱藏起來并且無法訪問。我們可以通過外部接口使用它的功能。
所以,我們需要使用一個(gè)對(duì)象時(shí)只需知道它的外部接口。我們可能完全不知道它的內(nèi)部是如何工作的,這太好了。
這是個(gè)概括性的介紹。
在 JavaScript 中,有兩種類型的對(duì)象字段(屬性和方法):
在許多其他編程語言中,還存在“受保護(hù)”的字段:只能從類的內(nèi)部和基于其擴(kuò)展的類的內(nèi)部訪問(例如私有的,但可以從繼承的類進(jìn)行訪問)。它們對(duì)于內(nèi)部接口也很有用。從某種意義上講,它們比私有的屬性和方法更為廣泛,因?yàn)槲覀兺ǔOM^承類來訪問它們。
受保護(hù)的字段不是在語言級(jí)別的 Javascript 中實(shí)現(xiàn)的,但實(shí)際上它們非常方便,因?yàn)樗鼈兪窃?Javascript 中模擬的類定義語法。
現(xiàn)在,我們將使用所有這些類型的屬性在 Javascript 中制作咖啡機(jī)??Х葯C(jī)有很多細(xì)節(jié),我們不會(huì)對(duì)它們進(jìn)行全面模擬以保持簡(jiǎn)潔(盡管我們可以)。
首先,讓我們做一個(gè)簡(jiǎn)單的咖啡機(jī)類:
class CoffeeMachine {
waterAmount = 0; // 內(nèi)部的水量
constructor(power) {
this.power = power;
alert( `Created a coffee-machine, power: ${power}` );
}
}
// 創(chuàng)建咖啡機(jī)
let coffeeMachine = new CoffeeMachine(100);
// 加水
coffeeMachine.waterAmount = 200;
現(xiàn)在,屬性 waterAmount
和 power
是公共的。我們可以輕松地從外部將它們 get/set 成任何值。
讓我們將 waterAmount
屬性更改為受保護(hù)的屬性,以對(duì)其進(jìn)行更多控制。例如,我們不希望任何人將它的值設(shè)置為小于零的數(shù)。
受保護(hù)的屬性通常以下劃線 _
作為前綴。
這不是在語言級(jí)別強(qiáng)制實(shí)施的,但是程序員之間有一個(gè)眾所周知的約定,即不應(yīng)該從外部訪問此類型的屬性和方法。
所以我們的屬性將被命名為 _waterAmount
:
class CoffeeMachine {
_waterAmount = 0;
set waterAmount(value) {
if (value < 0) {
value = 0;
}
this._waterAmount = value;
}
get waterAmount() {
return this._waterAmount;
}
constructor(power) {
this._power = power;
}
}
// 創(chuàng)建咖啡機(jī)
let coffeeMachine = new CoffeeMachine(100);
// 加水
coffeeMachine.waterAmount = -10; // _waterAmount 將變?yōu)?0,而不是 -10
現(xiàn)在訪問已受到控制,因此將水量的值設(shè)置為小于零的數(shù)變得不可能。
對(duì)于 power
屬性,讓我們將它設(shè)為只讀。有時(shí)候一個(gè)屬性必須只能被在創(chuàng)建時(shí)進(jìn)行設(shè)置,之后不再被修改。
咖啡機(jī)就是這種情況:功率永遠(yuǎn)不會(huì)改變。
要做到這一點(diǎn),我們只需要設(shè)置 getter,而不設(shè)置 setter:
class CoffeeMachine {
// ...
constructor(power) {
this._power = power;
}
get power() {
return this._power;
}
}
// 創(chuàng)建咖啡機(jī)
let coffeeMachine = new CoffeeMachine(100);
alert(`Power is: ${coffeeMachine.power}W`); // 功率是:100W
coffeeMachine.power = 25; // Error(沒有 setter)
getter/setter 函數(shù)
這里我們使用了 getter/setter 語法。
但大多數(shù)時(shí)候首選 ?
get.../set...
? 函數(shù),像這樣:class CoffeeMachine { _waterAmount = 0; setWaterAmount(value) { if (value < 0) value = 0; this._waterAmount = value; } getWaterAmount() { return this._waterAmount; } } new CoffeeMachine().setWaterAmount(100);
這看起來有點(diǎn)長(zhǎng),但函數(shù)更靈活。它們可以接受多個(gè)參數(shù)(即使我們現(xiàn)在還不需要)。
另一方面,get/set 語法更短,所以最終沒有嚴(yán)格的規(guī)定,而是由你自己來決定。
受保護(hù)的字段是可以被繼承的
如果我們繼承
class MegaMachine extends CoffeeMachine
,那么什么都無法阻止我們從新的類中的方法訪問this._waterAmount
或this._power
。
所以受保護(hù)的字段是自然可被繼承的。與我們接下來將看到的私有字段不同。
最近新增的特性
這是一個(gè)最近添加到 JavaScript 的特性。 JavaScript 引擎不支持(或部分支持),需要 polyfills。
這兒有一個(gè)馬上就會(huì)被加到規(guī)范中的已完成的 Javascript 提案,它為私有屬性和方法提供語言級(jí)支持。
私有屬性和方法應(yīng)該以 ?#
? 開頭。它們只在類的內(nèi)部可被訪問。
例如,這兒有一個(gè)私有屬性 #waterLimit
和檢查水量的私有方法 #fixWaterAmount
:
class CoffeeMachine {
#waterLimit = 200;
#fixWaterAmount(value) {
if (value < 0) return 0;
if (value > this.#waterLimit) return this.#waterLimit;
}
setWaterAmount(value) {
this.#waterLimit = this.#fixWaterAmount(value);
}
}
let coffeeMachine = new CoffeeMachine();
// 不能從類的外部訪問類的私有屬性和方法
coffeeMachine.#fixWaterAmount(123); // Error
coffeeMachine.#waterLimit = 1000; // Error
在語言級(jí)別,#
是該字段為私有的特殊標(biāo)志。我們無法從外部或從繼承的類中訪問它。
私有字段與公共字段不會(huì)發(fā)生沖突。我們可以同時(shí)擁有私有的 #waterAmount
和公共的 waterAmount
字段。
例如,讓我們使 waterAmount
成為 #waterAmount
的一個(gè)訪問器:
class CoffeeMachine {
#waterAmount = 0;
get waterAmount() {
return this.#waterAmount;
}
set waterAmount(value) {
if (value < 0) value = 0;
this.#waterAmount = value;
}
}
let machine = new CoffeeMachine();
machine.waterAmount = 100;
alert(machine.#waterAmount); // Error
與受保護(hù)的字段不同,私有字段由語言本身強(qiáng)制執(zhí)行。這是好事兒。
但是如果我們繼承自 CoffeeMachine
,那么我們將無法直接訪問 #waterAmount
。我們需要依靠 waterAmount
getter/setter:
class MegaCoffeeMachine extends CoffeeMachine {
method() {
alert( this.#waterAmount ); // Error: can only access from CoffeeMachine
}
}
在許多情況下,這種限制太嚴(yán)重了。如果我們擴(kuò)展 CoffeeMachine
,則可能有正當(dāng)理由訪問其內(nèi)部。這就是為什么大多數(shù)時(shí)候都會(huì)使用受保護(hù)字段,即使它們不受語言語法的支持。
私有字段不能通過 this[name] 訪問
私有字段很特別。
正如我們所知道的,通常我們可以使用 ?
this[name]
? 訪問字段:class User { ... sayHi() { let fieldName = "name"; alert(`Hello, ${this[fieldName]}`); } }
對(duì)于私有字段來說,這是不可能的:
this['#name']
不起作用。這是確保私有性的語法限制。
就面向?qū)ο缶幊蹋∣OP)而言,內(nèi)部接口與外部接口的劃分被稱為 封裝。
它具有以下優(yōu)點(diǎn):
保護(hù)用戶,使他們不會(huì)誤傷自己
想象一下,有一群開發(fā)人員在使用一個(gè)咖啡機(jī)。這個(gè)咖啡機(jī)是由“最好的咖啡機(jī)”公司制造的,工作正常,但是保護(hù)罩被拿掉了。因此內(nèi)部接口暴露了出來。
所有的開發(fā)人員都是文明的 —— 他們按照預(yù)期使用咖啡機(jī)。但其中的一個(gè)人,約翰,他認(rèn)為自己是最聰明的人,并對(duì)咖啡機(jī)的內(nèi)部做了一些調(diào)整。然而,咖啡機(jī)兩天后就壞了。
這肯定不是約翰的錯(cuò),而是那個(gè)取下保護(hù)罩并讓約翰進(jìn)行操作的人的錯(cuò)。
編程也一樣。如果一個(gè) class 的使用者想要改變那些本不打算被從外部更改的東西 —— 后果是不可預(yù)測(cè)的。
可支持性
編程的情況比現(xiàn)實(shí)生活中的咖啡機(jī)要復(fù)雜得多,因?yàn)槲覀儾恢皇琴?gòu)買一次。我們還需要不斷開發(fā)和改進(jìn)代碼。
如果我們嚴(yán)格界定內(nèi)部接口,那么這個(gè) class 的開發(fā)人員可以自由地更改其內(nèi)部屬性和方法,甚至無需通知用戶。
如果你是這樣的 class 的開發(fā)者,那么你會(huì)很高興知道可以安全地重命名私有變量,可以更改甚至刪除其參數(shù),因?yàn)闆]有外部代碼依賴于它們。
對(duì)于用戶來說,當(dāng)新版本問世時(shí),應(yīng)用的內(nèi)部可能被進(jìn)行了全面檢修,但如果外部接口相同,則仍然很容易升級(jí)。
隱藏復(fù)雜性
人們喜歡使用簡(jiǎn)單的東西。至少?gòu)耐獠縼砜词沁@樣。內(nèi)部的東西則是另外一回事了。
程序員也不例外。
當(dāng)實(shí)施細(xì)節(jié)被隱藏,并提供了簡(jiǎn)單且有據(jù)可查的外部接口時(shí),總是很方便的。
為了隱藏內(nèi)部接口,我們使用受保護(hù)的或私有的屬性:
_
? 開頭。這是一個(gè)眾所周知的約定,不是在語言級(jí)別強(qiáng)制執(zhí)行的。程序員應(yīng)該只通過它的類和從它繼承的類中訪問以 ?_
? 開頭的字段。#
? 開頭。JavaScript 確保我們只能從類的內(nèi)部訪問它們。目前,各個(gè)瀏覽器對(duì)私有字段的支持不是很好,但可以用 polyfill 解決。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: