1. 程式人生 > >設計模式-裝飾者 學習筆記

設計模式-裝飾者 學習筆記

模擬傳統面向物件預研的裝飾模式 首先要說明的是,作為一門解釋執行的語言,給js中的物件動態新增或者改變職責是一件再簡單不過的事情,

var obj = {
	name : 'testName'
}
obj.name = 'testName2'

雖然這種做法改動了物件本身,跟傳統定義中的裝飾者模式並不一樣,但是卻更加符合js語言特色 **實際上傳統面嚮物件語言中的裝飾者模式在js中的適用場景並不多。**下面的例子是模擬傳統面嚮物件語言,實現裝飾者模式

需求:假如我們在編寫一個飛機大戰的遊戲,隨著經驗值的增加,我們操作的飛機物件可用升級成更厲害的飛機,一開始這些飛機只能發射普通的子彈,升到第二級時,可用發射導彈,升到第三級時可用發射原子彈。

	var Plane = function(){};

	Plane.prototype.fire = function(){
		console.log( '發射普通子彈' );
	}

	var MissileDecorator = function( plane ){
		this.plane = plane;
	}
	MissileDecorator.prototype.fire = function(){
		this.plane.fire();
		console.log( '發射導彈' );
	}
	var AtomDecorator = function( plane ){
		this.plane = plane;
	}
	AtomDecorator.prototype.fire = function(){
		this.plane.fire();
		console.log( '發射原子彈' );
	}

	var plane = new Plane();
	plane = new MissileDecorator( plane );
	plane = new AtomDecorator( plane );
	plane.fire();
	//輸出:發射普通子彈 發射導彈 發射原子彈

這種給物件動態增加職責的方式,並沒有改變物件自身,而是將物件放入另一個物件中,這些物件以一條鏈的方式進行引用,形成一個聚合物件。這些物件都擁有相同的介面(file方法),當請求達到鏈中某個物件時,這個物件會執行自身的操作,隨後又把請求轉發給鏈中的下一個物件。

上面的例子是使用"類"來實現裝飾模式。下面是直接改變物件或者物件的某個方法。

	var plane = {
		fire: function(){
			console.log( '發射普通子彈' );
		}
	}
	var missileDecorator = function(){
		console.log( '發射導彈' );
	}
	var atomDecorator = function(){
		console.log( '發射原子彈' );
	}
	var fire1 = plane.fire;
	plane.fire = function(){
		fire1();
		missileDecorator();
	}
	var fire2 = plane.fire;
	plane.fire = function(){
		fire2();
		atomDecorator();
	}
	plane.fire();
	// 分別輸出: 發射普通子彈、發射導彈、發射原子彈

這個例子中,變數fire1 fire2分別儲存不同的fire方法,實現物件函式引用的呼叫。

上述兩個例子是模擬傳統程式語言 實現裝飾者模式。 下面我們來裝飾函式 在js中,我們可以很方便的給某個物件擴充套件屬性或者方法,但是卻很難再不改動某個函式原始碼的情況下,給該函式新增一些額外的功能。 想要給函式新增一些功能,最簡單粗暴的方法就是直接改寫該函式,但是這是最差的方法,違反了開放-封閉原則,如下:

 var a = function(){
	alert(1);
}
//=================>
var a = function(){
	alert(1);
	alert(2);
}

但是實際上,我們很多時候是不想去碰原函式的,也許是原函式是其他同事編寫,裡面實現十分雜亂。現在需要不改寫函式原始碼的情況下,能給函式增加功能, 常見改寫如下:

var a = function(){
	alert(1);
}
var _a = a;
a = function(){
	_a()
	alert(2)
}

這是工作中常見的做法,避免a的邏輯被覆蓋。還有onload函式,常用到這種做法。比如現在想給window繫結onload事件,但是不確定這個事件之前是否已繫結,為了避免覆蓋之前的window.onload函式中的行為,我們一般都會儲存好原先的window.onload,把它放入新的window.onload裡執行: window.onload = function(){ alert(1); } //儲存之前的onload邏輯 var _onload = window.onload || function(){};

window.onload = function(){ _onload(); alert(2); } 這種程式碼是符合開放-封閉原則,在新增功能的同時,不改變原來的onload程式碼,但是也還是存在下面兩個問題

  • 必須要維護_onload這個變數,假如函式的裝飾鏈過程,或者要裝飾的函式變多,中間這些變數的數量也會越來越多
  • this劫持的問題,在window.onload的例子中沒有這煩惱,是因為呼叫普通函式_onload時,this也指向window,假如吧window.onload換成document.getElementById,如下
var _getElementById = document.getElementById;
document.getElementById = function(id){
	alert(1);
	return _getElementById(id) //(1)
}
var button = document.getElementById('button');