天天看點

js設計模式(下)

引用:​​(23條消息) 《Javascript設計模式與開發實踐》關于設計模式典型代碼的整理(下):模闆方法模式、享元模式、職責鍊模式、中介者模式、裝飾模式、狀态模式、擴充卡模式_

8、模闆方法模式:一種基于繼承的設計模式。

// 模闆方法模式
var Beverage = function (param) {

    var boilWater = function () {
        console.log('把水煮沸');
    };

    var brew = param.brew || function () {
        throw new Error('必須傳遞brew方法');
    };

    var pourInCup = param.pourInCup || function () {
        throw new Error( '必須傳遞 pourInCup 方法' );
    };
    var addCondiments = param.addCondiments || function () {
        throw new Error( '必須傳遞 addCondiments 方法' );
    };

    var F = function(){};
    F.prototype.init = function(){ boilWater();
        brew();
        pourInCup();
        addCondiments();
    };
    return F;
};

var Coffee = Beverage({ brew: function(){
        console.log( '用沸水沖泡咖啡' ); },
    pourInCup: function(){
        console.log( '把咖啡倒進杯子' );
    },
    addCondiments: function(){
        console.log( '加糖和牛奶' ); }
});

var Tea = Beverage({
    brew: function(){
        console.log( '用沸水浸泡茶葉' ); },
    pourInCup: function(){
        console.log( '把茶倒進杯子' );
    },
    addCondiments: function(){
        console.log( '加檸檬' );
    }
});
// 測試
var coffee = new Coffee();
coffee.init();

var tea = new Tea(); 
tea.init();      

9、享元模式:運用共享技術來有效支援大量細粒度的對象。

内部狀态與外部狀态:

 内部狀态存儲于對象内部。

 内部狀态可以被一些對象共享。

 内部狀态獨立于具體的場景,通常不會改變。

 外部狀态取決于具體的場景,并根據場景而變化,外部狀态不能被共享。

// 享元模式
var Model = function (sex) {
    this.sex = sex; // 性别為内部狀态
};

Model.prototype.takePhoto = function () {
    console.log('sex=' + this.sex + ' underwear=' + this.underwear);    // 内衣為外部狀态
};
// 測試
var maleModel = new Model('male');
    femaleModel = new Model('female');

for (var i = 1; i <= 50; i++) {
    maleModel.underwear = 'underwear' + i;
    maleModel.takePhoto();
};

for ( var j = 1; j <= 50; j++ ){
    femaleModel.underwear = 'underwear' + j;
    femaleModel.takePhoto();
};      

10、職責鍊模式:使多個對象都有機會處理請求,進而避免請求的發送者和接收者之間的耦合關系,将這些對象連成一條鍊,并沿着這條鍊傳遞該請求,直到有一個對象處理它為止。

// 職責鍊模式
var order500 = function( orderType, pay, stock ){
    if ( orderType === 1 && pay === true ){
        console.log( '500 元定金預購,得到 100 優惠券' );
    }else{
        return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
    }
};

var order200 = function( orderType, pay, stock ){
    if ( orderType === 2 && pay === true ){
        console.log( '200 元定金預購,得到 50 優惠券' );
    }else{
        return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往後面傳遞
    }
};

var orderNormal = function( orderType, pay, stock ){
    if ( stock > 0 ){
        console.log( '普通購買,無優惠券' );
    }else{
        console.log( '手機庫存不足' );
    }
};
// Chain.prototype.setNextSuccessor 指定在鍊中的下一個節點
// Chain.prototype.passRequest 傳遞請求給某個節點
var Chain = function (fn) {
    this.fn = fn;
    this.successor = null;
};

Chain.prototype.setNextSuccessor = function (successor) {
    return this.successor = successor;
};

Chain.prototype.passRequest = function () {
  var ret = this.fn.apply(this, arguments);

  if(ret === 'nextSuccessor') {
      return this.successor && this.successor.passRequest.apply(this.successor, arguments);
  }

  return ret;
};
// 測試
var chainOrder500 = new Chain( order500 );
var chainOrder200 = new Chain( order200 );
var chainOrderNormal = new Chain( orderNormal );

chainOrder500.setNextSuccessor( chainOrder200 ); 
chainOrder200.setNextSuccessor( chainOrderNormal);

chainOrder500.passRequest( 1, true, 500 ); 
chainOrder500.passRequest( 2, true, 500 ); 
chainOrder500.passRequest( 3, true, 500 ); 
chainOrder500.passRequest( 1, false, 0 );      

11、中介者模式:增加一個中介者對象後,所有的相關對象都通過中介者對象來通信,而不是互相引用,是以當一個對象發生改變時,隻需要通知中介者對象即可。

// 中介者模式
var goods = { // 手機庫存
    "red|32G": 3,
    "red|16G": 0,
    "blue|32G": 1,
    "blue|16G": 6
};

var mediator = (function(){
    var colorSelect = document.getElementById( 'colorSelect' ),
        memorySelect = document.getElementById( 'memorySelect' ),
        numberInput = document.getElementById( 'numberInput' ),
        colorInfo = document.getElementById( 'colorInfo' ),
        memoryInfo = document.getElementById( 'memoryInfo' ),
        numberInfo = document.getElementById( 'numberInfo' ),
        nextBtn = document.getElementById( 'nextBtn' );
    return {
        changed: function( obj ){
            var color = colorSelect.value, // 顔色
                memory = memorySelect.value,// 記憶體
                number = numberInput.value, // 數量
                stock = goods[ color + '|' + memory ]; // 顔色和記憶體對應的手機庫存數量
                if ( obj === colorSelect ){ // 如果改變的是選擇顔色下拉框 
                    colorInfo.innerHTML = color;
                }else if ( obj === memorySelect ){ 
                    memoryInfo.innerHTML = memory;
                }else if ( obj === numberInput ){ 
                    numberInfo.innerHTML = number;
                }
            if ( !color ){
                nextBtn.disabled = true; nextBtn.innerHTML = '請選擇手機顔色'; return;
            }
            if ( !memory ){
                nextBtn.disabled = true; nextBtn.innerHTML = '請選擇記憶體大小'; return;
            }
            if ( ( ( number - 0 ) | 0 ) !== number - 0 ){   // 輸入購買數量是否為正整數
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請輸入正确的購買數量'; return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入購物車'; 
        }
    } 
})();

// 事件函數:
colorSelect.onchange = function(){
    mediator.changed( this ); 
};
memorySelect.onchange = function(){ 
    mediator.changed( this );
};
numberInput.oninput = function(){
    mediator.changed( this ); 
};      

12、裝飾者模式:在不改變對象自身的基礎上,在程式運作期間給對象動态地添加職責。

裝飾者模式和代理模式:裝飾者模式是實實在在的為對象增加新的職責和行為,而代理做的事情還是跟本體 一樣。

// 用AOP裝飾模式
Function.prototype.before = function(beforefn) {
    var _self = this;   // 儲存原函數的引用
    return function () {
        beforefn.apply(this, arguments);
        return _self.apply(this, arguments);
    }
}

Function.prototype.after = function (afterfn) {
    var _self = this;
    return function () {
        var ret = _self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
}      

13、狀态模式:允許一個對象在其内部狀态改變時改變它的行為,對象看起來似乎修改了它的類。

// 狀态模式
var delegate = function (client, delegation) {
    return {
        buttonWasPressed: function () {
            return delegation.buttonWasPressed.apply(client, arguments);
        }
    }
};

var FSM = {
    off: {
        buttonWasPressed: function() {
            console.log('關燈');
            this.button.innerHTML = '下一次按我是開燈';
            this.currState = this.onState;
        }
    },
    on: {
        buttonWasPressed: function(){
            console.log( '開燈' );
            this.button.innerHTML = '下一次按我是關燈';
            this.currState = this.offState;
        } 
    }
};

var Light = function () {
    this.offState = delegate(this, FSM.off);
    this.onState = delegate(this, FSM.on);
    this.currState = this.offState;
    this.button = null;
};

Light.prototype.init = function () {
    var button = document.createElement('button');
        self = this;
    button.innerHTML = '已關燈';
    this.button = document.body.appendChild(button);
    this.button.onclick = function () {
        self.currState.buttonWasPressed();
    }
};
// 測試
var light = new Light();
light.init();      

14、擴充卡模式:解決兩個軟體實體間的接口不相容的問題。

// 擴充卡模式
var renderMap = function( map ){
    if ( map.show instanceof Function ){
        map.show(); 
    }
};

var googleMap = {
    show: function(){
        console.log( '開始渲染谷歌地圖' );
    }
};
var baiduMap = {
    display: function(){
        console.log( '開始渲染百度地圖' );
    }
};
var baiduMapAdapter = {
    show: function(){
        return baiduMap.display();
    }
};
renderMap( googleMap ); // 輸出:開始渲染谷歌地圖
renderMap( baiduMapAdapter );   // 輸出:開始渲染百度地圖