第一章.設計原則-----開放-封閉原則

在面向對象的程序設計中 ,開放-封閉原則 (OCP)是最重要的一條原則。很多時候,一個程序具有良好的設計,往往說明它是符合開放-封閉原則的。

它的定義如下:
軟件實體(類、模塊、函數)等應該是可以擴展的,但是不可修改。

開放-封閉原則的思想:
當需要改變一個程序的功能或者給這個程序增加新功 能的時候,可以使用增加代碼的方式,但是不允許改動程序的源代碼。

1.擴展window.onload函數

假設我們是一個大型 Web 項目的維護人員,在接手這個項目時,發現它已經擁有 10 萬行以上的 JavaScript 代碼和數百個 JS 文件。

接到了一個新的需求,即在 window.onload 函數中打印出頁面中的所有節點數量。這很簡單:

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

如果目前的 window.onload 函數是一個擁有 500 行代碼的巨型函數,裏面密佈着各種變量和交叉的業務邏輯,而我們的需求又不僅僅是打印一個 log 這麼簡單。

改動代碼是一種危險的行爲, 也許我們都遇到過 bug 越改越多的場景。剛剛改好了一個 bug,但是又在不知不覺中引發了其他的 bug。

我們可以通過增加代碼,而不是修改代碼的方式,來給 window.onload 函數添加新的功能:

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 函數的內部實現

2. 開放-封閉消除條件分支

過多的條件分支語句是造成程序違反開放封閉原則的一個常見原因。每當需要增加一個新 的 if 語句時,都要被迫改動原函數。

利用對象的多態性來讓程序遵守開放封閉原則,是一個常用的技巧。

我們舉一個讓動物發出叫聲的例子:
下面先提供一段不符合開放封閉原則的代碼。每當我們增加一種新的動物時,都需要改動 makeSound 函數的內部實現:

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 Chcken){
 		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() ); // 汪汪汪

3. 如何遵循開放-封閉原則

我們能找到一些讓程序儘量遵守開放封閉原則的規律,最明顯的就是找出程序中將要發生變化的地方,然後把變化封裝起來。

通過封裝變化的方式,可以把系統中穩定不變的部分和容易變化的部分隔離開來。在系統的 演變過程中,我們只需要替換那些容易變化的部分,如果這些部分是已經被封裝好的,那麼替換起來也相對容易。而變化部分之外的就是穩定的部分。在系統的演變過程中,穩定的部分是不需要改變的。

幫助我們編寫遵守開放封閉原則的代碼方式:

3.1.利用對象的多態性

參考動物叫的例子

3.2.放置掛鉤

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

可以在父類中的某個容易變化的地方放置掛鉤,掛鉤的返回結果由具體子類決定。這樣一來,程序就擁有了變化的可能。

3.3 使用回調函數

在 JavaScript 中,函數可以作爲參數傳遞給另外一個函數,這是高階函數的意義之一。在這 種情況下,我們通常會把這個函數稱爲回調函數。在 JavaScript 版本的設計模式中,策略模式和 命令模式等都可以用回調函數輕鬆實現。

回調函數是一種特殊的掛鉤。我們可以把一部分易於變化的邏輯封裝在回調函數裏,然後把 回調函數當作參數傳入一個穩定和封閉的函數中。當回調函數被執行的時候,程序就可以因爲回 調函數的內部邏輯不同,而產生不同的結果。

參考資料

JavaScript設計模式與開發實踐----曾探

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章