『面試的底氣』—— 設計模式之開放封閉原則|8月更文挑戰

這是我參與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 );
}); 
複製代碼

四、利用鉤子函數

在函數中容易發生變化的地方放置鉤子函數,當函數執行到該地方時就會觸發鉤子函數,鉤子函數中寫入擴展的邏輯,就至關函數被擴展了,從而實現了開放-封閉原則。

難點

遵循開放-封閉原則的開發過程當中,最難的是要找到將要發生變化的地方。將變化的封裝起來,能夠把系統中穩定不變的部分和容易變化的部分隔離開來。在系統的演變過程當中,咱們只須要替換那些容易變化的部分,若是這些部分是已經被封裝好的,那麼替換起來也相對容易。而變化部分以外的就是穩定的部分。在系統的演變過程當中,穩定的部分是不須要改變的。

開發時一開始就儘可能遵照開放-封閉原則,並非一件很容易的事情。

一方面,咱們須要儘快知道程序在哪些地方會發生變化,這要求咱們有一些未卜先知的能力。

另外一方面,留給程序員的需求排期並非無限的,因此咱們能夠說服本身去接受不合理的代碼帶來的坑。

在最初開發的時候,先假設變化永遠不會發生,這有利於咱們迅速完成需求。當變化發生並 且對咱們接下來的工做形成影響的時候,能夠再回過頭來封裝這些變化的地方。而後確保咱們不 會掉進同一個坑裏。

相關文章
相關標籤/搜索