設計模式大冒險第一關:觀察者模式

封面圖

最近把以前學習過的這些設計模式又再次溫習了一下,以爲仍是有不少收穫的。確實有了溫故知新的感受,因此準備在每一個設計模式複習完以後都可以寫一篇關於這個設計模式的文章,這樣會讓本身可以加深對這個設計模式的理解;也可以跟你們一塊兒來探討一下。javascript

今天咱們來一塊兒學習一下觀察者模式,剛開始咱們不須要知道觀察者模式的定義是什麼,這些咱們到後面再去了解。我想先帶着你們從生活中的一個小事例開始。從生活中熟悉的事情入手,會讓咱們更快速的理解這個模式的用途java

生活中的小例子

相信你們都關注過一些公衆號,那麼對於一個公衆號來講,若是有新的文章發佈的話;那麼全部關注這個公衆號的用戶都會收到更新的通知,若是一個用戶沒有關注或者關注後又取消了關注,那麼這個用戶就不會收到該公衆號更新的通知。相信這個場景你們都很熟悉吧。那麼若是咱們把這個過程抽象出來,用代碼來實現的話,你會怎麼處理呢?不妨如今停下來思考一下。ios

經過上面的描述,咱們知道這是一個一對多的關係。也就是一個公衆號對應着許多關注這個公衆號的用戶。git

關注公衆號

那麼對於這個公衆號來講,它的內部須要有一個列表記錄着關注這個公衆號的用戶,一旦公衆號有了新的內容。那麼對於公衆號來講,它會遍歷這個列表。而後給列表中的每個用戶發送一個內容跟新的通知。咱們能夠經過代碼來表示這個過程:github

// 用戶
const user = {
    update() {
        console.log('公衆號更新了新的內容');
    },
};

// 公衆號
const officialAccount = {
    // 關注當前公衆號的用戶列表
    followList: [user],
    // 公衆號更新時候調用的通知函數
    notify() {
        const len = this.followList.length;
        if (len > 0) {
            // 通知已關注該公衆號的每一個用戶,有內容更新
            for (let user of this.followList) {
                user.update();
            }
        }
    },
};

// 公衆號有新內容更新
officialAccount.notify();

運行的結果以下:web

公衆號更新了新的內容

上面的代碼可以簡單的表示,當公衆號的內容發生了更新的時候,去通知關注該公衆號的用戶的過程。可是這個實現是很簡陋的,還缺乏一些內容。咱們接下來把這些缺乏的過程補充完整。對於公衆號來講,還須要能夠添加新的關注的用戶,移除再也不關注的用戶,獲取關注公衆號的用戶總數等。咱們來實現一下上面的過程:segmentfault

// 公衆號
const officialAccount = {
    // 關注當前公衆號的用戶列表
    followList: [],
    // 公衆號更新時候調用的通知函數
    notify() {
        const len = this.followList.length;
        if (len > 0) {
            // 通知已關注該公衆號的每一個用戶,有內容更新
            for (let user of this.followList) {
                user.update();
            }
        }
    },
    // 添加新的關注的用戶
    add(user) {
        this.followList.push(user);
    },
    // 移除再也不關注的用戶
    remove(user) {
        const idx = this.followList.indexOf(user);
        if (idx !== -1) {
            this.followList.splice(idx, 1);
        }
    },
    // 計算關注公衆號的總的用戶數
    count() {
        return this.followList.length;
    },
};

// 新建用戶的類
class User {
    constructor(name) {
        this.name = name;
    }
    // 接收公衆號內容更新的通知
    update() {
        console.log(`${this.name}接收到了公衆號的內容更新`);
    }
}

// 建立兩個新的用戶
const zhangSan = new User('張三');
const liSi = new User('李四');

// 公衆號添加關注的用戶
officialAccount.add(zhangSan);
officialAccount.add(liSi);

// 公衆號有新內容更新
officialAccount.notify();
console.log(`當前關注公衆號的用戶數量是:${officialAccount.count()}`);

// 張三再也不關注公衆號
officialAccount.remove(zhangSan);

// 公衆號有新內容更新
officialAccount.notify();
console.log(`當前關注公衆號的用戶數量是:${officialAccount.count()}`);

輸出的結果以下:設計模式

張三接收到了公衆號的內容更新
李四接收到了公衆號的內容更新
當前關注公衆號的用戶數量是:2
李四接收到了公衆號的內容更新
當前關注公衆號的用戶數量是:1

上面的代碼完善了關注和取消關注的過程,而且能夠獲取當前公衆號的關注人數。咱們還實現了一個用戶類,可以讓咱們快速建立須要添加到公衆號關注列表的用戶。固然你也能夠把公衆號的實現經過一個類來完成,這裏就再也不展現實現的過程了。app

經過上面這個簡單的例子,你是否是有所感悟,有了一些新的收穫?咱們上面實現的其實就是一個簡單的觀察者模式。接下來咱們來聊一聊觀察者模式的定義,以及一些在實際開發中的用途。框架

觀察者模式的定義

所謂的觀察者模式指的是一種一對多的關係,咱們把其中的叫作Subject(類比上文中的公衆號),把其中的叫作Observer(類比上文中關注公衆號的用戶),也就是觀察者。由於多個Observer的變更依賴Subject的狀態更新,因此Subject在內部維護了一個Observer的列表,一旦Subject的狀態有更新,就會遍歷這個列表,通知列表中每個Observer進行相應的更新。由於有了這個列表,Subject就能夠對這個列表進行增刪改查的操做。也就實現了ObserverSubject依賴的更新和解綁

咱們來看一下觀察者模式的UML圖:

觀察者模式UML模式

從上圖咱們這能夠看到,對於Subject來講,它自身須要維護一個observerCollection,這個列表裏面就是Observer的實例。而後在Subject內部實現了增長觀察者,移除觀察者,和通知觀察者的方法。其中通知觀察者的方式就是遍歷observerCollection列表,依次調用列表中每個observerupdate方法。

到這裏爲止,你如今已經對這個設計模式有了一些瞭解。那咱們學習這個設計模式有什麼做用呢?首先若是咱們在開發中遇到這種相似上面的一對多的關係,而且的狀態更新依賴的狀態;那麼咱們就可使用這種設計模式去解決這種問題。並且咱們也可使用這種模式解耦咱們的代碼,讓咱們的代碼更好拓展與維護

固然一些同窗會以爲本身在平時的開發中好像沒怎麼使用過這種設計模式,那是由於咱們平時在開發中通常都會使用一些框架,好比Vue或者React等,這個設計模式已經被這些框架在內部實現好了。咱們能夠直接使用,因此咱們對這個設計模式的感知會少一些

實戰:實現一個簡單的TODO小應用

咱們可使用觀察者模式實現一個小應用,這個應用很簡單,就是可以讓用戶添加本身的待辦,而且須要顯示已添加的待辦事項的數量

瞭解了需求以後,咱們須要肯定那些是,哪些是。固然咱們知道整個TODO的狀態就是咱們所說的,那麼對於待辦列表的展現以及待辦列表的計數就是咱們所說的。理清了思路以後,實現這個小應用就變得很簡單了。

能夠點擊👉這裏提早體驗一下這個簡單的小應用

TODO小應用

首先咱們須要先實現觀察者模式中的SubjectObserver類,代碼以下所示。

Subject類:

// Subject
class Subject {
    constructor() {
        this.observerCollection = [];
    }
    // 添加觀察者
    registerObserver(observer) {
        this.observerCollection.push(observer);
    }
    // 移除觀察者
    unregisterObserver(observer) {
        const observerIndex = this.observerCollection.indexOf(observer);
        this.observerCollection.splice(observerIndex, 1);
    }
    // 通知觀察者
    notifyObservers(subject) {
        const collection = this.observerCollection;
        const len = collection.length;
        if (len > 0) {
            for (let observer of collection) {
                observer.update(subject);
            }
        }
    }
}

Observer類:

// 觀察者
class Observer {
    update() {}
}

那麼接下來的代碼就是關於上面待辦的具體實現了,代碼中也添加了相應的註釋,咱們來看一下。

待辦應用的邏輯部分:

// 表單的狀態
class Todo extends Subject {
    constructor() {
        super();
        this.items = [];
    }
    // 添加todo
    addItem(item) {
        this.items.push(item);
        super.notifyObservers(this);
    }
}

// 列表渲染
class ListRender extends Observer {
    constructor(el) {
        super();
        this.el = document.getElementById(el);
    }
    // 更新列表
    update(todo) {
        super.update();
        const items = todo.items;
        this.el.innerHTML = items.map(text => `<li>${text}</li>`).join('');
    }
}

// 列表計數觀察者
class CountObserver extends Observer {
    constructor(el) {
        super();
        this.el = document.getElementById(el);
    }
    // 更新計數
    update(todo) {
        this.el.innerText = `${todo.items.length}`;
    }
}

// 列表觀察者
const listObserver = new ListRender('item-list');
// 計數觀察者
const countObserver = new CountObserver('item-count');

const todo = new Todo();
// 添加列表觀察者
todo.registerObserver(listObserver);
// 添加計數觀察者
todo.registerObserver(countObserver);

// 獲取todo按鈕
const addBtn = document.getElementById('add-btn');
// 獲取輸入框的內容
const inputEle = document.getElementById('new-item');
addBtn.onclick = () => {
    const item = inputEle.value;
    // 判斷添加的內容是否爲空
    if (item) {
        todo.addItem(item);
        inputEle.value = '';
    }
};

從上面的代碼咱們能夠清楚地知道這個應用的每個部分,被觀察的Subject就是咱們的todo對象,它的狀態就是待辦列表。它維護的觀察者列表分別是展現待辦列表的listObserver和展現待辦數量的countObserver。一旦todo的列表新增長了一項待辦,那麼就會通知這兩個觀察者去作相應的內容更新。這樣代碼的邏輯就很直觀明瞭。若是之後在狀態變動的時候還要添加新的功能,咱們只須要再次添加一個相應的observer就能夠了,維護起來也很方便。

固然上面的代碼只實現了很基礎的功能,尚未包含待辦的完成和刪除,以及對於未完成和已完成的待辦的分類展現。並且列表的渲染每次都是從新渲染的,沒有複用的邏輯。由於咱們本章的內容是跟你們一塊兒來探討一下觀察者模式,因此上面的代碼比較簡陋,也只是爲了說明觀察者模式的用法。相信優秀的你可以在這個基礎上,把這些功能都完善好,快去試試吧。

其實咱們學習這些設計模式,都是爲了讓代碼的邏輯更加清晰明瞭,可以複用一些代碼的邏輯,減小重複的工做,提高開發的效率。讓整個應用更加容易維護和拓展。固然不能爲了使用而使用,在使用以前,須要對當前的問題作一個全面的瞭解。到底需不須要使用某個設計模式是一個須要考慮清楚的問題。

好啦,關於觀察者模式到這裏就結束啦,你們若是有什麼意見和建議能夠在文章下面下面留言,咱們一塊兒探討一下。也能夠在這裏提出來,咱們更好地進行討論。也歡迎你們關注個人公衆號關山不難越,隨時隨地獲取文章的更新。

參考連接:

文章封面圖來源:unDraw

相關文章
相關標籤/搜索