js設計模式之觀察者模式和發佈/訂閱模式

觀察者模式

The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state.

觀察者是一種 包含一系列依賴於主體(subject)的觀察者(observers),自動通知它們變化的內容的設計模式javascript


接下來,用oberver pattern來實現一個購物車列表,實現以前先說明幾個概念:html

主體 (Subject)java

維護一系列觀察者的數組,包含一些操做方法,好比增長、刪除,獲取等等。
class Subject {
    constructor() {
        this.observerList = [];
    }
    add(item) { //添加
        return this.observerList.push(item);
    }
    count() { //獲取長度
        return this.observerList.length;
    }
    get(index) { //獲取下標
        if (index > -1 && index < this.observerList.length) {
            return this.observerList[index];
        } else {
            return null;
        }
    }
    notify(context) { //通知
        let observerCount = this.count();

        for (let i = 0; i < observerCount; i++) {
            console.dir(this.get(i))
            this.get(i).update(context);
        }
    }
}

首先咱們聲明一個主體類,裏面包含一個觀察者數組,還有一些操做方法。設計模式

觀察者(Observer)數組

class Observer {
    constructor() {
        this.update = () => { //通用interface
            //todo
        }
    }
}

聲明一個更新接口,用來獲取主體分發的通知。app

二者關係大概以下:ide

圖片描述

主要流程:ui

  1. 定義好主體類和觀察者類
  2. 類實例化成具體對象,綁定方法,觀察者在主體對象裏註冊
  3. 操做。主體分發消息給觀察者。

具體實現代碼以下:this

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        table tr td {
            text-align: center;
        }
    </style>
</head>

<body>
    <p>
        name:
        <input type="text" style="width:100px" id="name"> price:
        <input type="text" style="width:50px" id="price">
        <input type="button" id="btn" value="Add">
        <input type="text" style="width:50px" id="priceAdd">
        <input type="button" id="priceAddBtn" value="priceAdd">
    </p>

    <table>
        <caption>list</caption>
        <thead>
            <tr>
                <th>
                    name
                </th>
                <th>
                    price
                </th>
                <th>
                    action
                </th>
            </tr>
        </thead>
        <tbody id="main">
        </tbody>
    </table>
    <script>
        //被觀察類
        class Subject {
            constructor() {
                this.observerList = [];
            }
            add(item) { //添加
                return this.observerList.push(item);
            }
            count() { //獲取長度
                return this.observerList.length;
            }
            get(index) { //獲取下標
                if (index > -1 && index < this.observerList.length) {
                    return this.observerList[index];
                } else {
                    return null;
                }
            }
            notify(context) { //通知
                let observerCount = this.count();

                for (let i = 0; i < observerCount; i++) {
                    console.dir(this.get(i))
                    this.get(i).update(context);
                }
            }
        }

        //觀察類
        class Observer {
            constructor() {
                this.update = () => { //通用interface
                    //todo
                }
            }
        }

        //合併對象方法
        let extend = (source, extendsion) => {

            let ext = new extendsion(),
                properties = Object.getOwnPropertyNames(ext),
                protoProperties = Object.getOwnPropertyNames(extendsion.prototype);
            // to get all properties
            properties.forEach(function(val, key) {

                source[val] = ext[val];

                if (key == properties.length - 1) {
                    protoProperties.forEach(function(innerVal, key) {
                        if (val != 'constructor') {
                            source[innerVal] = extendsion.prototype[innerVal];
                        }
                    })
                }
            })
        }

        let btn = document.getElementById('btn');
        let priceAdd = document.getElementById('priceAddBtn');


        //掛載主體
        extend(btn, Subject);
        //extend(priceAdd, Subject);

        //按鈕
        btn.addEventListener('click', function() {

            let tBody = document.getElementById('main'),
                name = document.getElementById('name'),
                price = document.getElementById('price'),

                dataSet = {
                    name: name.value,
                    price: price.value
                };

            let html = document.createElement('tr');

            //模版
            let item =
                `
                <td class="name">${dataSet.name}</td><td class="price">${dataSet.price}</td><td><button type="button" onclick='del(this)'>delete</button></td>
            `;
            html.innerHTML = item;


            btn.add(html); //observer放入subject

            extend(html, Observer); //繼承觀察類

            html.update = function(context) { //更新context
                let num = Number(this.querySelector('.price').innerHTML);
                this.querySelector('.price').innerHTML = num + context;
            };

            tBody.appendChild(html);

        });

        priceAdd.addEventListener('click', function() {
            let num = document.getElementById('priceAdd').value;
            btn.notify(Number(num));
        })

        //刪除當前行
        function del(obj) {

            let _this = obj,
                box = _this.parentNode.parentNode.parentNode;

            box.removeChild(_this.parentNode.parentNode);
        }
    </script>
</body>

</html>

發佈/訂閱者模式

The Publish/Subscribe pattern however uses a topic/event channel which sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application specific events which can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher.

Publish/Subscribe pattern和Observer pattern和相似,都是Observer註冊,subject分佈通知,可是Publish/Subscribe pattern多了個事件管道(event channel)用來集中處理監聽的事件google

典型的Publish/Subscribe模式的實現代碼:

var pubsub = {};
        (function(myObject) {
            // Storage for topics that can be broadcast
            // or listened to
            var topics = {};
            // An topic identifier
            var subUid = -1;
            // Publish or broadcast events of interest
            // with a specific topic name and arguments
            // such as the data to pass along  
            myObject.publish = function(topic, args) {
                if (!topics[topic]) {
                    return false;
                }
                var subscribers = topics[topic],
                    len = subscribers ? subscribers.length : 0;
                while (len--) {
                    subscribers[len].func(topic, args);
                }
                return this;
            };
            // Subscribe to events of interest
            // with a specific topic name and a
            // callback function, to be executed
            // when the topic/event is observed
            myObject.subscribe = function(topic, func) {
                if (!topics[topic]) {
                    topics[topic] = [];
                }
                var token = (++subUid).toString();
                topics[topic].push({
                    token: token,
                    func: func
                });
                return token;
            };
            // Unsubscribe from a specific
            // topic, based on a tokenized reference
            // to the subscription
            myObject.unsubscribe = function(token) {
                for (var m in topics) {
                    if (topics[m]) {
                        for (var i = 0, j = topics[m].length; i < j; i++) {
                            if (topics[m][i].token === token) {
                                topics[m].splice(i, 1);
                                return token;
                            }
                        }
                    }
                }
                return this;
            };
        }(pubsub));

test demo:

//註冊事件
        var test = pubsub.subscribe('message', function(topic, data) {
            console.log("topic:" + topic + ",data:" + data);
        });
        //分佈消息
        pubsub.publish('message', "siip"); //topic:message,data:test,a,b,c

        pubsub.publish("message", ["test", "a", "b", "c"]); //topic:message,data:test,a,b,c
        
        //刪除註冊事件
        pubsub.unsubscribe(test);

        pubsub.publish("message", {
            sender: "hello@google.com",
            body: "Hey again!"
        });

二者關係大概以下:

圖片描述

參考:

一、https://addyosmani.com/resour...

二、http://blog.csdn.net/cooldrag...

相關文章
相關標籤/搜索