設計模式應用舉例

wupengyu發表於2019-02-16

紙上得來終覺淺,學習設計模式,看了很多書,但是始終還是覺得不如直接看例子來的更加客觀具體,下面主要記錄了js中的幾個常見的設計模式舉例,供自己以後複習的時候可以直接通過例子更快更好的理解設計模式。

單例模式

保證一個類僅有一個例項,並提供一個全域性訪問入口

var getSingleton = function(fn){
    var result;
    
    return function(){
        return result || (result = fn.apply(this, arguments));
    }
}

var createLoginLayer = function(){
    var div;
    
    return function(){
        if(!div){
            div = document.createElement(`div`);
            div.innerText = `這是彈窗`;
            div.style.display = `none`;
            document.body.appendChild(div);
        }
        
        return div;
    }
});

var singletonCreateLoginLayer = getSingleton(createLoginLayer);

document.getElementById(`loginBtn`).onclick = function(){
    var layer = singletonCreateLoginLayer();
    layer.style.display = `block`;
}

策略模式

定義一系列演算法,並使之可以相互替換。目的就是使演算法的使用和定義分離出來。

var Strategy = {
    S: function(salary){
        return salary * 4;
    },
    A: function(salary){
        return salary * 3;
    },
    B: function(salary){
        return salary * 2;
    }
};

var getBouns = function(strategy, salary){
    return Strategy[strategy](salary);
}

getBouns(`B`, 1000); // 2000

表單校驗

var strategies = {
    isNonEmpty: function(value, errorMsg){
        ....
    },
    minLength: function(value, length, errorMsg){
        ....
    },
    isMobile: function(value, errorMsg){
        ...
    }
};

var Validator = function(){
    this.cache = [];
};

Validator.prototype.add = function(dom, rule, errorMsg){
    var ary = [rule];
    
    this.cache.push(function(){
        var strategy = ary.shift();
        ary.unshift(dom.value);
        ary.push(errorMsg);
        return strategies[strategy].apply(dom, ary);
    });
}

Validator.prototype.start = function(){
    for(var i=0, validatorFunc; validatorFunc = this.cache[i++]){
        var msg = validatorFunc();
        if(msg){
            return msg;
        }
    }
}

var validatorFunc = function(){
    var validator = new Validator(); // 建立一個物件
    
    validator.add(...);
    
    var errorMsg = validator.start(); // 獲取校驗結果
}

var registerForm = document.getElementById(`form`);
registerForm.onsubmit = function(){
    var errorMsg = validatorFunc();
    
    if(errorMsg){
        return false;
    }
}

代理模式

為一個物件提供一個代用品或佔位符,以便控制對它的訪問。當客戶不方便直接訪問一個物件或者不滿足需要的時候提供一個替身物件來控制對這個物件的訪問,客戶實際上訪問的是替身物件。

單一職責原則指的是,就一個類(通常也包括物件和函式等)而言,應該僅有一個引起它變 化的原因。如果一個物件 了多 職責,就意味著這個物件將變得 大,引起它變化的原因可 能會有多個。物件導向設計 將行為分 到細 度的物件之中,如果一個物件 的職責過多, 等於把這些職責耦合到了一起,這種耦合會 致 和低內聚的設計。當變化發生時,設計可能 會 到意外的 。

虛擬代理

圖片懶載入

const myImg = (
  const node = documnet.createElement(`img`)
  document.body.appendChild(node)
  return {
    setSrc(src) {
       node.src= src
    }
  }
)()

const proxy = (
  const img = new Image()
  img.onload = () => {
    myImg.setSrc(this.src)
  }
  return {
    setImg(src) {
      img.src = src
      myImg.setSrc(`loading.gif`)
    }
  }
)()

觀察者模式

釋出訂閱模式又叫觀察者模式,它定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都將得到通知。在JavaScript開發中,我們一般用事件模型來替代傳統的釋出訂閱模式。

const Event = (
  function() {
    var eventList = {}
    var addEventListen
    var trigger
    var remove 
    addEventListen = function(eventName, fn) {
      eventList[eventName] = eventList[eventName] || []
      eventList[eventName].push(fn)
    }
    trigger = function() {
      var key = Array.prototype.shift.call(arguments)
      var fns = eventList[key]
      if (!fns || !fns.length) {
         return
      }
      fns.forEach((fn, index) => {
        fn.apply(this, arguments)
      })
    }
    remove = function(eventName, fn) {
      var fns = eventList[eventName]
      if (!fns || !fns.length) {
       return false
      }
      if (!fn) {
        fns.length = 0
      } else {
        fns.forEach((_fn, index) => {
          if(fn === _fn) {
            fns.splice(index, 1)
          } 
        })
      }
    }
    return {
      addEventListen,
      trigger,
      remove
    }
  }
)()

var testFn = () => {
    console.log(`you have click a cancel btn`)
}

Event.addEventListen(`click`, () => {
  console.log(`you have click a button`)
})

Event.addEventListen(`click`, () => {
  console.log(`you have click button2`)
})

Event.addEventListen(`click`, () => {
  console.log(`you have click button3`)
})

Event.addEventListen(`click`, testFn)

Event.remove(`click`, testFn)
Event.trigger(`click`)

享元模式

享元模式是為效能優化而生的,在一個存在大量相似物件的系統中,享元模式可以很好地解決大量物件帶來的效能問題

檔案上傳

// uploadType作為內部狀態,再抽離外部狀態
var Upload = function(uploadType){
    this.uploadType = uploadType;
};

// 定義刪除檔案的方法
Upload.prototype.delFile = function(id){
    uploadManager.setExternalState(id, this); // 設定外部狀態
    
    if(this.fileSize < 3000){
        return this.dom.parentNode.removeChild(this.dom);
    }
    
    if(window.confirm(`確定要刪除檔案嗎?`+ file.fileName)){
        return this.dom.parentNode.removeChild(this.dom);
    }
};

// 工廠進行物件例項化
var UploadFactory = (function(){
    var createFlyWeightObjs = {};
    return {
        create: function(uploadType){
            if(createFlyWeightObjs[uploadType]){
                return createFlyWeightObjs[uploadType];
            }
            
            return createFlyWeightObjs[uploadType] = new Upload(uploadType);
        }
    }
})();

// 管理器封裝外部狀態
var uploadManager = (function(){
    var uploadDataBase = {}; // 儲存外部狀態
    
    return {
        add: function(id, uploadType, fileName, fileSize){
            var flyWeightObj = UploadFactory.create(uploadType);
            
            var dom = document.createElement(`div`);
            dom.innerHTML = `...`;
            document.body.appendChild(dom);
            
            uploadDataBase[id] = { // 新增外部狀態
                fileName: fileName,
                fileSize: fileSize,
                dom: dom
            };
            
            return flyWeightObj;
        },
        setExternalState: function(id, flyWeightObj){ // 設定外部狀態
            var uploadData = uploadDataBase[id];
            for(var i in uploadData){
                flyWeightObj[i] = uploadData[i];
            }
        }
    }
})();

var id = 0;

window.startUpload = function(uploadType, files){
    for(var i = 0, file; file = files[i++];){
        var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
    }
};

startUpload(`plugin`, [
    {
        fileName: `1.txt`,
        fileSize: 1000
    },
    ...
]);

物件池

物件池維護一個裝載空閒物件的池子,如果需要物件的時候,不是直接new,而是轉從物件池裡獲取。如果物件池沒有空閒物件,則建立一個新的物件,當獲取出的物件完成它的職責之後,再進入池子等待被下次獲取。

var objectPoolFactory = function(createObjFn){
    var objectPool = [];
    
    return {
        create: function(){
            var obj = objectPool.length === 0 ?
                createObjFn.apply(this, arguments) : objectPool.shift();
            
            return obj;
        },
        recover: function(obj){
            objectPool.push(obj);
        }
    };
};

// 現在利用`ObjectPoolFactory`來建立一個裝載一些`iframe`的物件池
var iframeFactory = objectPoolFactory(function(){
    var iframe = document.createElement(`iframe`);
    document.body.appendChild(iframe);
    
    iframe.onload = function(){
        iframe.onload = null;
        iframeFactory.recover(iframe);
    }
    
    return iframe;
});

var iframe1 = iframeFactory.create();
iframe1.src = `http://baidu.com`;

var iframe2 = iframeFactory.create();
iframe2.src = `http://qq.com`;

setTimeout(function(){
    var iframe3 = iframeFactory.create();
    iframe3.src = `http://163.com`;
}, 3000);

中介者模式

用一箇中介者物件來封裝一系列的物件互動。中介者使各個物件之間不會相互引用。從而使其達到鬆散耦合的目的。
與觀察者模式對比來看,中介者模式是觀察者模式中的共享被觀察者物件。在這個系統中的物件之間直接的釋出/訂閱關係被犧牲掉了,取而代之的是維護一個通訊的中心節點。

寫程式是為了快速完成專案交付生產,而不是堆砌模式和過渡設計。關鍵就在於如何去衡量物件之間的耦合程度。如果物件之間的複雜耦合確實導致呼叫和維護出現了困難,而且這些耦合度隨專案的變化呈指數增長曲線,那就可以考慮用中介者模式來重構程式碼。

function Player(name, teamColor){
    this.name = name; // 角色名字
    this.teamColor = teamColor; // 隊伍顏色
    this.state = `alive`; // 玩家生存狀態
}

Player.prototype.win = function(){ 
    console.log(`winner:` + this.name);
};

Player.prototype.lose = function(){ 
    console.log(`loser:` + this.name);
};

Player.prototype.die = function(){
    this.state = `dead`;
    playerDirector.ReceiveMessage(`playerDead`, this); // 給中介者傳送訊息,玩家死亡
};

Player.prototype.remove = function(){
    playerDirector.ReceiveMessage(`removePlayer`, this);  // 給中介者傳送訊息,移除一個玩家
};

Player.prototype.changeTeam = function(){
    playerDirector.ReceiveMessage(`changeTeam`, this); // 給中介者傳送訊息,玩家換隊
};

var playerFactory = function(name, teamColor){
    var newPlayer = new Player(name, teamColor);
    playerDirector.ReceiveMessage(`addPlayer`, newPlayer); // 給中介者傳送訊息,新增玩家
    
    return newPlayer;
};

// 實現playerDirector物件
var playDirector = (function(){
    var players = {}; // 儲存所有玩家
    var operations = {}; // 中介者可以執行的操作
    
    // 新增一個玩家
    operations.add = function(player){
        var teamColor = player.teamColor;
        players[teamColor] = players[teamColor] || [];
        players[teamColor].push(player);
    };
    
    // 移除一個玩家
    operations.removePlayer = function(player){
        var teamColor = player.teamColor;
        var teamPlayers = players[teamColor] || [];
        
        for(var i=teamPlayers.length - 1; i >= 0 ;i --){
            if(teamPlayers[i] === player){
                teamPlayers.splice(i, 1);
            }
        }
    };
    
    // 玩家換隊
    operations.changeTeam = function(player, newTeamColor){
        operations.removePlayer(player); // 從原隊伍中刪除
        player.teamColor = newTeamColor; // 換顏色
        operations.addPlayer(player); // 新增玩家到新的隊伍
    }
    
    operations.playerDead = function(player){
        var teamColor = player.teamColor;
        var teamPlayer = players[teamColor];
        
        var all_dead = true;
        
        // 遍歷隊友列表
        for(var i=0, player; player = teamPlayer[i++];){
            if(player.state !== `dead`){
                all_dead = false;
                break;
            }
        }
        
        // 如果隊友全部死亡
        if(all_dead === true){
            this.lose();
            
            // 通知所有隊友玩家遊戲失敗
            for(var i=0, player; player = teamPlayer[i++];){
                player.lose();
            }
            
            // 通知所有敵人遊戲勝利
            for(var color in players){
                if(color !== teamColor){
                    var teamPlayers = players[color];
                    for(var i=0, player; player = teamPlayers[i++];){
                        player.win();
                    }
                }
            }
        }
    }
    
    var ReceiveMessage = function(){
        var message = Array.prototype.shift.call(arguments);
        operations[message].apply(this, arguments);
    };
    
    return {
        ReciveMessage: ReceiveMessage
    }
})();

// 建立8個玩家物件
var player1 = playerFactory(`a`, `red`);
var player2 = playerFactory(`b`, `red`);
var player3 = playerFactory(`c`, `red`);
var player4 = playerFactory(`d`, `red`);

var player5 = playerFactory(`e`, `blue`);
var player6 = playerFactory(`f`, `blue`);
var player7 = playerFactory(`g`, `blue`);
var player8 = playerFactory(`h`, `blue`);

裝飾者模式

給物件動態地增加職責的方式稱為裝飾者模式。
裝飾者模式能夠在不改變物件自身的基礎上,在程式執行期間給物件動態地新增職責。跟繼承相比,裝飾者是一種更輕便靈活的做法,這是一種“即用即付”的方式。
函式通過Function.prototype.before或者Function.prototype.after被裝飾之後,返回的實際上是一個新的函式,如果在原函式上儲存了一些屬性,那麼這些屬性會丟失。
這種裝飾方式也疊加了函式的作用域,如果裝飾的鏈條過長,效能上也會受到一些影響。

Function.prototype.before = function(beforeFn){
    var _self = this; 
    return function(){ 
        if(beforefn.apply(this, arguments) === false){
            return;
        }; 
        return _self.apply(this, arguments); 
    }
};

var validata = function(){
    if(username.value === ``){
        alert(`不能為空`);
        return false;
    }
    
    if(password.value === ``){
        alert(`不能為空`);
        return false;
    }
};


var formSubmit = function(){
    var param = {
        username: username.value,
        password: password.value
    };
    
    ajax(`....`);
};

formSubmit = formSubimt.before(validata);

submitBtn.onclick = function(){
    formSubmit();
};

狀態模式

狀態模式的關鍵是把事物的每種狀態都封裝成單獨的類,跟此種狀態有關的行為都被封裝在這個類的內部,只需要在上下文中,把某個請求委託給當前的狀態物件即可,該狀態物件會福州渲染它自身的行為。

// Light 類
var Light = function(){
    this.offLightState = new OffLightState(this);
    this.weekLightState = new WeekLightState(this);
    this.strongLightState = new StrongLightState(this);
    this.button = null;
};

Light.prototype.init = function(){
    var button = document.createElement(`button`);
    var self = this;
    
    this.button = document.body.appendChild(button);
    this.button.innerHTML = `開關`;
    
    this.currState = this.offLightState;
    
    this.button.onclick = function(){
        self.currState.buttonWasPressed();
    };
};
// offLightState
var OffLightState = function(light){
    this.light = light;
};

OffLightState.prototype.buttonWasPressed = function(){
    console.log(`弱光`) // offLightState 對應的行為
    this.light.setState(this.light.weekLightState); // 切換狀態到 weekLightState
};

文中程式碼主要來自曾探老師的《JavaScript設計模式與開發實踐》,書中的內容關於設計模式寫的更加詳實細緻,如果想學習設計模式推薦這本書入門哈!

相關文章