iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 26
0
自我挑戰組

邁向 JavaScript 核心之路 系列 第 26

[Day 26] JavaScript 設計模式- 代理模式、中介者模式、觀察者模式

代理模式 (Proxy)

代理模式的意義是用一個物件做為另一個物件的介面,主要的目的是為了保護物件的存取,避免不正確的操作行為。


    var car = {
        start: function() {console.log("發動");},
        stop: function() {console.log("關閉");},
        run: function() {console.log("加速");}
    };
    
    function ProxyCar(car) {
    
        var isStart = false;
        this.start = function() {
            if (isStart) {
                console.log("引擎已經發動");
            } else {
                car.start();
                isStart = true;
            }
        };
        
        this.stop = function() {
            if (!isStart) {
                console.log("引擎已經關閉");
            } else {
                car.stop();
                isStart = false;
            }
        };
        
        this.run = function() {
            if (!isStart) {
                console.log("尚未發動引擎");
            } else {
                car.run();
            }
        };
    }
    
    var proxyCar = new ProxyCar(car);
    
    proxyCar.run(); //尚未發動引擎
    proxyCar.stop(); //引擎已經關閉
    proxyCar.start(); //發動
    proxyCar.run(); //加速

生活中當電梯未停妥時無法開啟電梯門,或電梯門尚未關閉時無法移動到其他樓層。

優點

  • 代理模式能夠協調調用者與被調用者,一定程度上降低了耦合度。

缺點

  • 有些類型的代理模式可能會造成請求的處理速度變慢。
  • 實現代理模式會需要額外的工作,有些代理模式的實現上會非常複雜。

中介者模式 (Mediator)

當物件和物件之間或有錯綜複雜的交互作用,可將這些關係交由另一物件 (中介者) 來處理,來減少這些物件間的耦合。


    function Player(name) {
        this.points = 0;
        this.name = name;
    }
    
    Player.prototype.play = function() {
        this.points += 1;
        mediator.played();
    };
    
    var scoreboard = {
        el: document.body,
        update: function(score) {
            var msg = "";
            for (var i in score) {
                if (score.hasOwnProperty(i)) {
                    msg += "<p><b>" + i + "</b>:";
                    msg += score[i];
                    msg += "</p>";
                }
            }
            this.el.innerHTML = msg;
        }
    };

有時候朋友間打牌,要自己紀錄與每個人間的分數關係,當人數越多時就會越複雜,因此如果能有一個計分板,將每個人得分都更新到計分板上,就能更輕鬆一些。


    var mediator = {
        users: {},
        init: function() {
            this.users.home = new Player("Home");
            this.users.guest = new Player("Guest");
        },
        played: function() {
            var score = {
                Home: this.users.home.points,
                Guest: this.users.guest.points
            };
            scoreboard.update(score);
        },
        keypress: function(e) {
            if (e.keyCode === 48) {
                mediator.users.home.play();
                return;
            }
            if (e.keyCode === 49) {
                mediator.users.guest.play();
                return;
            }
        }
    };
    
    mediator.init();
    window.onkeypress = mediator.keypress;

下面的範例簡化了在新增 Player 時的操作,只要透過中介者加入新的 player 即可。


    function Player(name) {
        this.points = 0;
        this.name = name;
    }
    
    Player.prototype.play = function() {
        this.points += 1;
        mediator.played();
    };
    
    var scoreboard = {
        el: document.body,
        update: function(score) {
            var msg = "";
            for (var i in score) {
                if (score.hasOwnProperty(i)) {
                    msg += "<p><b>" + i + "</b>:";
                    msg += score[i];
                    msg += "</p>";
                }
            }
            this.el.innerHTML = msg;
        }
    };
    
    var mediator = {
        users: {},
        add: function(key, obj) {
            this.users[obj.name] = {
                key: key,
                obj: obj
            };
        },
        played: function() {
            var score = {};
            for (var user in this.users) {
                score[user] = this.users[user].obj.points;
            }
            scoreboard.update(score);
        },
        keypress: function(e) {
            for (var user in mediator.users) {
                user = mediator.users[user];
                if (e.keyCode === user.key) {
                    user.obj.play();
                    return;
                }
            }
        }
    };
    
    mediator.add(48, new Player("Home"));
    mediator.add(49, new Player("Guest"));
    mediator.add(50, new Player("Other"));
    mediator.add(51, new Player("Reynold"));
    mediator.add(52, new Player("Cher"));
    
    window.onkeypress = mediator.keypress;

優點

  • 能減少子類別的產生
  • 簡化了程式之間的交互

缺點

  • 因為在中介者類中包含了交互的細節,有可能導致其變得非常複雜,難以維護。

觀察者模式 (Observer)

觀察者模式又可以稱為訂閱模式,是一對多的關係,讓多個觀察者同時監聽一個主題的事件,當這個主題物件狀態發生改變就會通知觀察者。

而大家熟知的 JavaScript 事件就是屬於觀察者模式。


    var messageCenter = {
        events: {},
        emit: function(type, message) {
            if (this.events[type]) {
                for (var i = 0; i < this.events[type].length; i++) {
                    this.events[type][i].callback(message);
                }
            }
        },
        on: function(obj, type, callback) {
            this.events[type] = this.events[type] || [];
            this.events[type].push({
                obj: obj,
                callback: callback
            });
        },
        off: function(obj, type) {
            if (this.events[type]) {
                for (var i = 0; i < this.events[type].length; i++) {
                    if (this.events[type][i].obj === obj) {
                        this.events[type].splice(i,1);
                        i--;
                    }
                }
            }
        }
    };
    
    function User(messageCenter) {
        this.messageCenter = messageCenter;
    }
    
    User.prototype.addEvent = function(type, callback) {
        this.messageCenter.on(this, type, callback);
    };
    
    User.prototype.removeEvent = function(type) {
        this.messageCenter.off(this, type);
    };
    
    var UserA = new User(messageCenter);
    
    UserA.addEvent("todo", function(msg) {
        console.log("UserA todo:" + msg);
    });
    
    UserA.addEvent("test", function(msg) {
        console.log("UserA test:" + msg);
    });
    
    var UserB = new User(messageCenter);
    
    UserB.addEvent("todo", function(msg) {
        console.log("UserB todo:" + msg);
    });
    
    var UserC = new User(messageCenter);
    
    UserC.addEvent("test", function(msg) {
        console.log("UserC test:" + msg);
    });
    
    messageCenter.emit("todo", "News");
    messageCenter.emit("test", "Word");

生活中如果我們想看報紙,則可以打電話到報社去訂閱,這樣每天早上就會收到報紙,若不想看的話,也只要致電過去退訂即可。

若讀者有聽過 Rx.js ,其就是使用觀察者模式為基礎的程式庫。

優點

  • 觀察者和被觀察者是抽象耦合的。

缺點

  • 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會消耗較多的資源且我們只能知道發生了變化,但無法得知是怎麼發生變化的。

對於設計模式的簡介這邊暫時告一個段落了,希望看完了這三天的文章,能對這些設計模式有個基礎的概念。

參考資料:

Tommy - 深入 JavaScript 核心課程

程式前沿 - 設計模式 – 觀察者模式


上一篇
[Day 25] JavaScript 設計模式- 裝飾者模式、策略模式、外觀模式
下一篇
[Day 27] JavaScript ES6 語法- 變數範圍鏈、常數、函式範圍鏈
系列文
邁向 JavaScript 核心之路 30

尚未有邦友留言

立即登入留言