設計模式-裝飾者 學習筆記
模擬傳統面向物件預研的裝飾模式 首先要說明的是,作為一門解釋執行的語言,給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');