《JavaScript設計模式與開發實踐》原則篇(3)—— 開放-封閉原則

嗨呀豆豆呢發表於2019-01-03

在物件導向的程式設計中,開放封閉原則(OCP)是最重要的一條原則。很多時候,一個程式具有良好的設計,往往說明它是符合開放封閉原則的。 當需要改變一個程式的功能或者給這個程式增加新功能的時候,可以使用增加程式碼的方式,但是不允許改動程式的原始碼。

故事背景

假設我們是一個大型 Web 專案的維護人員,在接手這個專案時,發現它已經擁有10萬行以上的JavaScript程式碼和數百個 JS 檔案。 不久後接到了一個新的需求,即在 window.onload 函式中列印出頁面中的所有節點數量。這 當然難不倒我們了。於是我們開啟文字編輯器,搜尋window.onload函式在檔案中的位置,在函式內部新增以下程式碼

window.onload = function(){ 
    // 原有程式碼略
    console.log( document.getElementsByTagName( '*' ).length ); 
};
複製程式碼

應用OCP原則

Function.prototype.after = function( afterfn ){ 
    var __self = this;
    return function(){
        var ret = __self.apply( this, arguments ); 
        afterfn.apply( this, arguments );
        return ret;
    } 
};
window.onload = ( window.onload || function(){} ).after(function(){ 
    console.log( document.getElementsByTagName( '*' ).length );
});
複製程式碼

通過動態裝飾函式的方式,我們完全不用理會從前 window.onload 函式的內部實現,就算拿到的是一份混淆壓縮過的程式碼也沒有關係。只要它從前是個穩定執行的函式,那麼以後也不會因為我們的新增需求而產生錯誤。新增的程式碼和原有的程式碼可以互不影響。

編寫符合OCP程式碼的方法

過多的條件分支語句是造成程式違反開放封閉原則的一個常見原因。每當需要增加一個新 的 if 語句時,都要被迫改動原函式。實際上,每當我們看到一大片的 if 或者 swtich-case 語句時,第一時間就應該考慮,能否利用物件的多型性來重構它們。

利用多型的思想

利用物件的多型性來讓程式遵守開放封閉原則,是一個常用的技巧。

  • 不符合OCP
var makeSound = function( animal ){
    if ( animal instanceof Duck ){ 
        console.log( '嘎嘎嘎' ); 
    } else if ( animal instanceof Chicken ) {
        console.log( '咯咯咯' );
    }
};
var Duck = function(){}; 
var Chicken = function(){};
makeSound( new Duck() ); 
makeSound( new Chicken() );

//動物世界裡增加一隻狗之後,makeSound 函式必須改成:
var makeSound = function( animal ){ 
    if ( animal instanceof Duck ){
        console.log( '嘎嘎嘎' ); 
    } else if ( animal instanceof Chicken ) {
        console.log( '咯咯咯' ); 
    } else if ( animal instanceof Dog ) {
        console.log('汪汪汪' ); 
    }
};
var Dog = function(){};
// 增加跟狗叫聲相關的程式碼
 makeSound( new Dog() ); // 增加一隻狗
複製程式碼

利用多型的思想,我們把程式中不變的部分隔離出來(動物都會叫),然後把可變的部分封 裝起來(不同型別的動物發出不同的叫聲),這樣一來程式就具有了可擴充套件性。當我們想讓一隻狗發出叫聲時,只需增加一段程式碼即可,而不用去改動原有的 makeSound函式

var makeSound = function( animal ){ 
    animal.sound();
};
var Duck = function(){};
Duck.prototype.sound = function(){ 
    console.log( '嘎嘎嘎' );
};
var Chicken = function(){};
Chicken.prototype.sound = function(){ 
    console.log( '咯咯咯' );
};
makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯
/********* 增加動物狗,不用改動原有的 makeSound 函式 ****************/
var Dog = function(){}; Dog.prototype.sound = function(){
console.log( '汪汪汪' ); };
makeSound( new Dog() ); // 汪汪汪
複製程式碼
放置掛鉤

放置掛鉤(hook)也是分離變化的一種方式。我們在程式有可能發生變化的地方放置一個掛鉤,掛鉤的返回結果決定了程式的下一步走向。這樣一來,原本的程式碼執行路徑上就出現了一個 分叉路口,程式未來的執行方向被預埋下多種可能性。

使用回撥函式

在 JavaScript 中,函式可以作為引數傳遞給另外一個函式,這是高階函式的意義之一。在這 種情況下,我們通常會把這個函式稱為回撥函式。在 JavaScript版本的設計模式中,策略模式和命令模式等都可以用回撥函式輕鬆實現。 回撥函式是一種特殊的掛鉤。我們可以把一部分易於變化的邏輯封裝在回撥函式裡,然後把 回撥函式當作引數傳入一個穩定和封閉的函式中。當回撥函式被執行的時候,程式就可以因為回 調函式的內部邏輯不同,而產生不同的結果。

總結

開放封閉原則是一個看起來比較虛幻的原則,但我們還是能找到一些讓程式儘量遵守開放封閉原則的規律,最明顯的就是找出程式中將要發生變化的地方,然後把變化封裝起來。 通過封裝變化的方式,可以把系統中穩定不變的部分和容易變化的部分隔離開來。在系統的 演變過程中,我們只需要替換那些容易變化的部分,如果這些部分是已經被封裝好的,那麼替換起來也相對容易。而變化部分之外的就是穩定的部分。在系統的演變過程中,穩定的部分是不需要改變的。

系列文章:

《JavaScript設計模式與開發實踐》最全知識點彙總大全

相關文章