「這是我參與8月更文挑戰的第2天,活動詳情查看:8月更文挑戰」前端
在面試高級前端時,每每會遇到一些關於設計模式的問題,每次都回答不太理想。恰逢8月更文挑戰的活動,準備用一個月時間好好理一下關於設計模式方面的知識點,給本身增長點面試的底氣。程序員
在學習設計模式以前,首先要認識到設計模式是個編程思想,對任何編程語言都適用。其次要從設計模式的原則開始學習,故本文將詳細介紹設計模式的原則之一開放封閉原則。web
開放-封閉原則是說軟件實體(類、模塊、函數等等)應該能夠擴展,可是不可修改。面試
在開發任何系統時,不要期望系統一開始時需求肯定,就不再會發生變化,這是不切實際的想法,既然需求是必定會發生變化的,那麼如何設計才能面對需求的改變,不至於修改大部分的代碼致使系統不穩定。ajax
既然修改會致使系統不穩定,那麼就少修改,不改變代碼的主題,使用擴展的方式添加新需求的代碼。這就是開放-封閉,是對擴展是開放的,對修改是封閉的。編程
舉一個職場中很常見的現象來解釋開放-封閉原則。彈性打卡制度,你們都很熟悉吧,這個制度就是一個很好的開放-封閉原則的應用。設計模式
在一家小公司,8點上班,可是有幾個骨幹員工常常遲到,老闆看眼裏,內心想這種現象很是很差,因而叫來人事主管提起這個現象,說日後遲到要扣錢,人事主管聽了跟老闆建議到:「我從考勤記錄得知那幾個骨幹晚上都加班比較晚,加上咱們又沒有給加班費,這麼作,不免會讓人心生不滿致使人員流失,建議改爲彈性上班,好比早上8點到10點彈性,晚上6點到8點彈性下班」。老闆聽了,想到仍是一天工做8個小時沒變,因而說到:「先按這樣執行一段時間,看看效果」。markdown
在以上的案例中,老闆說日後遲到要扣錢,就是修改本來的考勤邏輯代碼,可能會致使員工(系統)離職(不穩定)。而人事主管改爲彈性上班的建議,只是本來的考勤邏輯代碼中擴展出一種計算考勤的方法,其考勤時間仍是8個小時不變的,不會致使員工(系統)離職(不穩定)。app
給Function
的原型鏈上添加一個extend
方法來對函數進行擴展,在extend
中利用_this.apply(this , arguments)
執行要擴展的函數,並將直接結果result
返回。而後執行fun.apply(this , arguments)
,其中fun
就是調用函數extend
時傳入的要擴展的函數。這樣不去修改函數的原有代碼,也能往函數中添加新的邏輯,實現了開放-封閉。編程語言
Function.prototype.extend = function(fun){
var _this = this;
return function(){
const result = _this.apply(this , arguments);
fun.apply(this , arguments);
return result
}
}
const a = () =>{
//舊的邏輯代碼
}
const b = a.extend(() =>{
//新增邏輯代碼
})
複製代碼
多態的含義:同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。或者換句話說,給不一樣的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不一樣的 反饋。
多態的思想是將「作什麼」和「誰去作以及怎樣去作」分離開來,也就是將「不變的事物」與「可能改變的事物」分離開來。正好符合開放-封閉原則。
下面先提供一段不符合開放-封閉原則的代碼,再利用多態的思想來將其改形成符合開放-封閉原則。
class A {
constructor() {}
init() {
console.log('初始化A');
}
}
class B {
constructor() {}
init() {
console.log('初始化B');
}
}
const init = (type) => {
if (type === 'A') {
new A().init();
} else if (type === 'B') {
new B().init();
}
};
init('A');
init('B');
複製代碼
在init
函數中經過判斷傳入type
來分別初始化A類和B類,倘若又來一個C類,要用init
函數來初始化,要怎麼辦呢?直接修改init
函數:
class C {
constructor() {}
init() {
console.log('初始化C');
}
}
const init = (type) => {
if (type === 'A') {
new A().init();
} else if (type === 'B') {
new B().init();
}else if(type === 'C'){
new C().init();
}
};
複製代碼
如果這樣處理,日後每新增一個類要初始化,都要去改動init
函數的內部實現,是違背了開放-封閉原則。
利用多態的思想,把程序中不變的部分隔離出來(都會調用類的init
方法進行初始化), 而後把可變的部分(類的實例化)封裝起來,這樣一來程序就具備了可擴展性。故能夠這樣改造init
函數。
const init = (obj) =>{
if(obj.init instanceof Function){
obj.init();
}
}
init(new A());
init(new B());
init(new C());
複製代碼
函數能夠做爲參數傳遞給另一個函數,把這個函數稱爲回調函數。
能夠把一部分易於變化的邏輯封裝在回調函數中,而後把回調函數看成參數傳入一個穩定和封閉的函數中,該函數是封閉的,不能輕易修改的。
當一個函數要擴展時,能夠把擴展的邏輯寫入回調函數,當回調函數被執行時,就至關函數被擴展了,從而實現了開放-封閉原則。
Jq的ajax
就是利用了回調函數進行擴展,每次請求回來的數據都用回調函數進行處理。
var getUserInfo = function( callback ){
$.ajax( 'http:// xxx.com/getUserInfo', callback );
};
getUserInfo( function( data ){
console.log( data.userName );
});
getUserInfo( function( data ){
console.log( data.userId );
});
複製代碼
在函數中容易發生變化的地方放置鉤子函數,當函數執行到該地方時就會觸發鉤子函數,鉤子函數中寫入擴展的邏輯,就至關函數被擴展了,從而實現了開放-封閉原則。
遵循開放-封閉原則的開發過程當中,最難的是要找到將要發生變化的地方。將變化的封裝起來,能夠把系統中穩定不變的部分和容易變化的部分隔離開來。在系統的演變過程當中,咱們只須要替換那些容易變化的部分,若是這些部分是已經被封裝好的,那麼替換起來也相對容易。而變化部分以外的就是穩定的部分。在系統的演變過程當中,穩定的部分是不須要改變的。
開發時一開始就儘可能遵照開放-封閉原則,並非一件很容易的事情。
一方面,咱們須要儘快知道程序在哪些地方會發生變化,這要求咱們有一些未卜先知的能力。
另外一方面,留給程序員的需求排期並非無限的,因此咱們能夠說服本身去接受不合理的代碼帶來的坑。
在最初開發的時候,先假設變化永遠不會發生,這有利於咱們迅速完成需求。當變化發生並 且對咱們接下來的工做形成影響的時候,能夠再回過頭來封裝這些變化的地方。而後確保咱們不 會掉進同一個坑裏。