js - 常用設計模式

零、原鏈和資料

  1.js設計模式

 

  注: 代碼均爲簡潔版,如需瞭解其他版本寫法的優缺點,請移步原文章。

 

一、單體模式

其思想是在一定的作用域範圍內保證一個特定類僅有一個實例,意味着當你第二次使用同一個類創建信對象時,應得到和第一次創建對象完全相同。

var Universe;
(function(){
    var instance;
    Universe=function Universe(){
        if(instance){
            return instance;
        }
        instance=this;
        this.xx="xx";
    }
})();

var uni = new Universe();
Universe.prototype.a = 1
var uni2 = new Universe();
console.log(uni === uni2) //true
console.log(uni.a)   //1
console.log(uni2.a)  //1
console.log(uni.constructor === Universe);  //true

 

二、工廠模式

批量創建對象,可根據不同要求小範圍內調整,就如同工廠一樣,零件我可以採購,然後根據要求生產福特/mini/沃爾沃...

demo:

  • 公共構造函數 CarMaker
  • 名爲 factory 的 CarMaker 靜態方法來創建 car 對象
function CarMaker() {}  

// 原型鏈上
CarMaker.prototype.drive = () => {
    return `I have ${this.doors} doors`;
}

// 靜態方法
// 枚舉子對象
CarMaker.compact = () => {
    this.doors = 4;
}
CarMaker.convertible = () => {
    this.doors = 2;
}
CarMaker.suv = () => {
    this.doors = 6;
}

CarMaker.factory = (type) => {
    if (typeof CarMaker[type] !== 'function') {
        throw new ReferenceError('Error')
    }

    // 關鍵句,因爲 CarMaker.compact/convertible/suv 是獨立的函數,並不在 CarMaker 的繼承鏈上
    // 所以這裏關聯到 CarMaker 的繼承鏈上
    if (typeof CarMaker[type].prototype.drive !== 'function') {
        CarMaker[type].prototype = new CarMaker();
    }

    return new CarMaker[type]();
}

var corolla = CarMaker.factory('compact');
console.log(corolla.drive()); //I have 4 doors

是不是和 Object.create({...})很像

 

三、迭代器模式

emmm... 用來迭代複雜數據結構的。

基礎的 api 設計:

  • next() 下一個
  • hasNext() 是否有下一個
  • reWind() 重置指針
  • current() 返回當前
var agg = (function() {
    var index = 0;
    var data = [1, 2, 3, 4, 5, 6];
    var length = data.length;
    return {
        next: function() { //這裏是從第一個數據開始輸出 本例中爲 1
            if (!this.hasNext()) {
                return null;
            }
            var element = data[index];
            index++;
            return element;
        },
        hasNext: function() {
            return index < length;
        },
        reWind: function() {
            index = 0;
        },
        current: function() {
            return data[index];
        }
    }
})();

while (agg.hasNext()) {
    console.log(agg.next()); //1,2,3,4,5,6
}
agg.reWind();  //此時重置指針到0

 

四、裝飾者模式

可以在運行時候添加附加功能到對象中,其一個方便特徵在於其預期行爲的可定製和可配置特性。

demo:
假設在開發一個銷售商品的Web應用,每一筆信銷售都是一個人新的 sale 對象。該對象“知道”有關項目的價格,並可以通過 getPrice() 方法返回加個。
根據不同情況,可以用額外的功能裝飾此對象。
假設客戶在魁北克省,買房需要支付聯邦稅和魁北克省稅,則此時需要調用聯邦稅裝飾者和魁北克省稅裝飾者。

// 實例及要求
var sale=new Sale(100);
sale=sale.decorate("fedtax"); //聯邦稅
sale=sale.decorate("quebec"); //魁北克省稅
sale=sale.decorate("miney"); //轉爲美元格式
sale.getPrice(); //返回價格
function Sale(price) {
    this.price = price;
    this.decorateList = [];
}

// 注意這裏是靜態方法
Sale.decorators = {};

Sale.decorators.fedtax = {
    getPrice: function(price) {
        var price = this.uber.getPrice();
        return price * 0.8; //對price進行處理
    },
}

Sale.decorators.quebec = {
    getPrice: function(price) {
        var price = this.uber.getPrice();
        return price * 0.7; //對price進行處理
    },
}

Sale.decorators.money = {
    getPrice: function(price) {
        var price = this.uber.getPrice();
        return "$" + price * 0.9; //對price進行處理
    },
}

Sale.prototype.decorate = function(decorator) {
    this.decorateList.push(decorator);
    return this;
};

Sale.prototype.getPrice = function() {
    var price = this.price;
    this.decorateList.forEach(function(name) {
        price = Sale.decorators[name].getPrice(price);
    });
    return price;
};

var sale = new Sale(100);
sale = sale.decorate("fedtax"); //聯邦稅
sale = sale.decorate("quebec"); //魁北克省稅
sale = sale.decorate("money"); //轉爲美元格式
console.log(sale.getPrice()); //$50.4

便於鏈式調用的改版:

function Sale(price) {
    this.price = price;
    this.decorateList = [];
}

Sale.decorators = {};

// 自定義的裝飾者
Sale.decorators.fedTax = {
    getPrice(price) {
        return price * 0.8;
    }
}
Sale.decorators.queBec = {
    getPrice(price) {
        return price * 0.7;
    }
}
Sale.decorators.money = {
    getPrice: function(price) {
        return "$" + price * 0.9; //對price進行處理
    },
}

Sale.prototype.decorate = function(decorator) {
    this.decorateList.push(decorator);
    return this; // 鏈式調用
}

Sale.prototype.getPrice = function() {
    let price = this.price;
    this.decorateList.forEach(name => {
        price = Sale.decorators[name].getPrice(price);
    })
    return price;
}

let sale = new Sale(100);
sale.decorate('fedTax').decorate('queBec').decorate('money');
console.log(sale.getPrice()); //$50.4

 

五、策略模式

策略模式支持在運行時候選擇算法。
例如用在表單驗證問題上,可以創建一個具有 validate() 方法的驗證器對象,無論表單具體類型是什麼,該方法都會被調用,並且返回結果或者錯誤信息。 其核心思想是:預先定義各種規則字典(types), 然後定義數據(data)與規則字典的對應關係(config), 接着循環數據的每個字段,執行匹配的規則檢驗,蒐集對應的 success/error 信息。

// 定義驗證器對象
let validator = {
    // 所有可以的驗證規則處理類存放的地方,後面會單獨定義
    types: {},

    // 驗證類型所對應的錯誤信息
    messages: [],

    // 當然需要使用的驗證類型
    config: {},

    // 暴露的公開驗證方法
    // 傳入的參數是 key: value 對
    validate: function(data) {
        let i, msg, type, checker, result_ok;
        // 清空所有的錯誤信息
        this.messages = [];

        for (i in data) {
            if (data.hasOwnProperty(i)) {
                type = this.config[i]; // 根據key查詢是否有存在的驗證規則
                checker = this.types[type]; // 獲取驗證規則的驗證類

                if (!type) continue; // 如果驗證規則不存在,則不處理

                if (!checker) { // 如果驗證規則類不存在,拋出異常
                    throw {
                        name: 'ValidationError',
                        message: 'No handler to validate type ' + type
                    };
                }

                result_ok = checker.validate(data[i]);  // 使用查到的單個驗證類進行驗證
                if (!result_ok) {
                    msg = "Invalid value for *" + i + "*, " + checker.instructions;
                    this.messages.push(msg);
                }
            }
        }

        return this.hasErrors();
    },
    hasErrors: function() {
        return this.messages.length !== 0;
    }
}


// 然後剩下的工作,就是定義types裏存放的各種驗證類了

// 驗證給定的值是否不爲空
validator.types.isNonEmpty = {
    validate: function(value) {
        return value !== '';
    },
    instructions: '傳入的值不能爲空',
};

 // 驗證給定的值是否是數字
validator.types.isNumber = {
    validate: function(value) {
        return !isNaN(value);
    },
    instructions: '傳入的值只能是合法的數字,例如:1, 3.14 or 2010',
};

// 驗證給定的值是否只是字母或數字
validator.types.isAlphaNum = {
    validate: function(value) {
        return !/[^a-z0-9]/i.test(value);
    },
    instructions: '傳入的值只能保護字母和數字,不能包含特殊字符',
}

// 使用的時候,我們首先要定義需要驗證的數據集合,然後還需要定義每種數據需要驗證的規則類型,代碼如下:
let data = {
    first_name: 'Tom',
    last_name: 'Xu',
    age: 'unknown',
    username: 'TomXu@',
}

// 配置下驗證規則
validator.config = {
    first_name: 'isNonEmpty',
    age: 'isNumber',
    username: 'isAlphaNum'
}

// 最後獲取驗證結果
validator.validate(data);
if (validator.hasErrors()) {
    console.log(validator.messages.join('\n'));
}

策略模式可以作爲 switch-case 的拓展版本。

 

六、代理模式

在代理模式中,一個對象充當另外一個對象的接口。
代理模式是介於對象的客戶端和對象本身之間,並且對該對象的訪問進行保護。
原理是:在 代理對象中實例化原來需要的對象,然後調用相應的方法。

注: 在 es6 中有一個原生的代理對象:Proxy,vue 3.0 也將基於這個代理對象。

demo: 賣家 - 郵局(proxy) - 買家

let Package = function(receiver) {
    this.receiver = receiver;
}

let Seller = function(good) {
    this.package = good;
    this.send = function(gift) {
        return good.receiver + '你的包裹' + gift;
    }
}

let Express = function(good) {
    this.package = good;
    this.send = function(packageName) {
        return new Seller(good).send(packageName);
    }
}

let ems = new Express(new Package('gary'));
console.log(ems.send('鍵盤'));

 

七、中介者模式

中介者模式可以讓多個對象之間鬆耦合,並降低維護成本。
在中介者模式下,對象與對象之間的聯繫全靠中介者,者意味着除中介者外,其他對象不知道還有沒有另外的的對象,這是中介者模式與代理模式不一樣的地方。

function Player(name) {
    this.point = 0;
    this.name = name;
}
Player.prototype.play = function() {
    this.point += 1;
    mediator.played(); // mediator 中介
}

let scoreBoard = {
    element: document.querySelector('#score'), // '這裏放 dom 元素,用來顯示分數'
    update: function(score) { // 更新分數
        let msg = '';

        for (let i in score) {
            if (score.hasOwnProperty(i)) {
            msg += score[i] + '-';
            }
        }
        this.element.innerText = msg;
    },
}

let mediator = { // 中介
    players: {}, // 存放玩家對象
    setUp: function() {
        let players = this.players;
        players.home = new Player('home');
        players.guest = new Player('guest');
    },
    played() {
    let players = this.players;
    let score = {
        home: players.home.point,
        guest: players.guest.point
    };
    scoreBoard.update(score);
    },
    keypress: function(e) {
        e = e || window.event;
        if (e.which === 49) { // 按鍵 1
            return mediator.players.home.play();
        }
        if (e.which === 48) { // 按鍵 0
            return mediator.players.guest.play();
        }
    },
}

mediator.setUp(); // 啓動(初始化)
window.onkeypress = mediator.keypress;

setTimeout(function() { //設置5秒遊戲時間
    window.onkeypress = null;
    alert("game end");
}, 5000);

 

八、觀察者/訂閱-發佈模式

詳情請移步 js - 觀察者模式與訂閱發佈模式

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