<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          設(shè)計模式大冒險第一關(guān):觀察者模式

          共 5975字,需瀏覽 12分鐘

           ·

          2020-10-23 16:40

          最近把之前學(xué)習(xí)過的這些設(shè)計模式又再次溫習(xí)了一下,覺得還是有很多收獲的。確實有了溫故知新的感覺,所以準(zhǔn)備在每個設(shè)計模式復(fù)習(xí)完之后都能夠?qū)懸黄P(guān)于這個設(shè)計模式的文章,這樣會讓自己能夠加深對這個設(shè)計模式的理解;也能夠跟大家一起來探討一下。

          今天我們來一起學(xué)習(xí)一下觀察者模式,剛開始我們不需要知道觀察者模式的定義是什么,這些我們到后面再去了解。我想先帶著大家從生活中的一個小事例開始。從生活中熟悉的事情入手,會讓我們更快速的理解這個模式的用途

          生活中的小例子

          相信大家都關(guān)注過一些公眾號,那么對于一個公眾號來說,如果有新的文章發(fā)布的話;那么所有關(guān)注這個公眾號的用戶都會收到更新的通知,如果一個用戶沒有關(guān)注或者關(guān)注后又取消了關(guān)注,那么這個用戶就不會收到該公眾號更新的通知。相信這個場景大家都很熟悉吧。那么如果我們把這個過程抽象出來,用代碼來實現(xiàn)的話,你會怎么處理呢?不妨現(xiàn)在停下來思考一下。

          通過上面的描述,我們知道這是一個一對多的關(guān)系。也就是一個公眾號對應(yīng)著許多關(guān)注這個公眾號的用戶。

          關(guān)注公眾號

          那么對于這個公眾號來說,它的內(nèi)部需要有一個列表記錄著關(guān)注這個公眾號的用戶,一旦公眾號有了新的內(nèi)容。那么對于公眾號來說,它會遍歷這個列表。然后給列表中的每一個用戶發(fā)送一個內(nèi)容跟新的通知。我們可以通過代碼來表示這個過程:

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

          //?公眾號
          const?officialAccount?=?{
          ????//?關(guān)注當(dāng)前公眾號的用戶列表
          ?followList:?[user],
          ????//?公眾號更新時候調(diào)用的通知函數(shù)
          ?notify()?{
          ??const?len?=?this.followList.length;
          ??if?(len?>?0)?{
          ??????//?通知已關(guān)注該公眾號的每個用戶,有內(nèi)容更新
          ???for?(let?user?of?this.followList)?{
          ????user.update();
          ???}
          ??}
          ?},
          };

          //?公眾號有新內(nèi)容更新
          officialAccount.notify();

          運行的結(jié)果如下:

          公眾號更新了新的內(nèi)容

          上面的代碼能夠簡單的表示,當(dāng)公眾號的內(nèi)容發(fā)生了更新的時候,去通知關(guān)注該公眾號的用戶的過程。但是這個實現(xiàn)是很簡陋的,還缺少一些內(nèi)容。我們接下來把這些缺少的過程補充完整。對于公眾號來說,還需要可以添加新的關(guān)注的用戶,移除不再關(guān)注的用戶,獲取關(guān)注公眾號的用戶總數(shù)等。我們來實現(xiàn)一下上面的過程:

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

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

          //?創(chuàng)建兩個新的用戶
          const?zhangSan?=?new?User('張三');
          const?liSi?=?new?User('李四');

          //?公眾號添加關(guān)注的用戶
          officialAccount.add(zhangSan);
          officialAccount.add(liSi);

          //?公眾號有新內(nèi)容更新
          officialAccount.notify();
          console.log(`當(dāng)前關(guān)注公眾號的用戶數(shù)量是:${officialAccount.count()}`);

          //?張三不再關(guān)注公眾號
          officialAccount.remove(zhangSan);

          //?公眾號有新內(nèi)容更新
          officialAccount.notify();
          console.log(`當(dāng)前關(guān)注公眾號的用戶數(shù)量是:${officialAccount.count()}`);

          輸出的結(jié)果如下:

          張三接收到了公眾號的內(nèi)容更新
          李四接收到了公眾號的內(nèi)容更新
          當(dāng)前關(guān)注公眾號的用戶數(shù)量是:2
          李四接收到了公眾號的內(nèi)容更新
          當(dāng)前關(guān)注公眾號的用戶數(shù)量是:1

          上面的代碼完善了關(guān)注和取消關(guān)注的過程,并且可以獲取當(dāng)前公眾號的關(guān)注人數(shù)。我們還實現(xiàn)了一個用戶類,能夠讓我們快速創(chuàng)建需要添加到公眾號關(guān)注列表的用戶。當(dāng)然你也可以把公眾號的實現(xiàn)通過一個類來完成,這里就不再展示實現(xiàn)的過程了。

          通過上面這個簡單的例子,你是不是有所感悟,有了一些新的收獲?我們上面實現(xiàn)的其實就是一個簡單的觀察者模式。接下來我們來聊一聊觀察者模式的定義,以及一些在實際開發(fā)中的用途。

          觀察者模式的定義

          所謂的觀察者模式指的是一種一對多的關(guān)系,我們把其中的叫做Subject(類比上文中的公眾號),把其中的叫做Observer(類比上文中關(guān)注公眾號的用戶),也就是觀察者。因為多個Observer的變動依賴Subject的狀態(tài)更新,所以Subject在內(nèi)部維護了一個Observer的列表,一旦Subject的狀態(tài)有更新,就會遍歷這個列表,通知列表中每一個Observer進行相應(yīng)的更新。因為有了這個列表,Subject就可以對這個列表進行增刪改查的操作。也就實現(xiàn)了ObserverSubject依賴的更新和解綁

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

          觀察者模式UML模式

          從上圖我們這可以看到,對于Subject來說,它自身需要維護一個observerCollection,這個列表里面就是Observer的實例。然后在Subject內(nèi)部實現(xiàn)了增加觀察者,移除觀察者,和通知觀察者的方法。其中通知觀察者的方式就是遍歷observerCollection列表,依次調(diào)用列表中每一個observerupdate方法。

          到這里為止,你現(xiàn)在已經(jīng)對這個設(shè)計模式有了一些了解。那我們學(xué)習(xí)這個設(shè)計模式有什么作用呢?首先如果我們在開發(fā)中遇到這種類似上面的一對多的關(guān)系,并且的狀態(tài)更新依賴的狀態(tài);那么我們就可以使用這種設(shè)計模式去解決這種問題。而且我們也可以使用這種模式解耦我們的代碼,讓我們的代碼更好拓展與維護

          當(dāng)然一些同學(xué)會覺得自己在平時的開發(fā)中好像沒怎么使用過這種設(shè)計模式,那是因為我們平時在開發(fā)中一般都會使用一些框架,比如Vue或者React等,這個設(shè)計模式已經(jīng)被這些框架在內(nèi)部實現(xiàn)好了。我們可以直接使用,所以我們對這個設(shè)計模式的感知會少一些

          實戰(zhàn):實現(xiàn)一個簡單的TODO小應(yīng)用

          之前我開發(fā)的一個組隊打卡小程序主線程,其中首頁待辦的一些功能就使用了觀察者模式,今天我們利用觀察者模式實現(xiàn)一個粗糙的版本就是能夠讓用戶添加自己的待辦,并且需要顯示已添加的待辦事項的數(shù)量

          了解了需求之后,我們需要確定那些是,哪些是。當(dāng)然我們知道整個TODO的狀態(tài)就是我們所說的,那么對于待辦列表的展示以及待辦列表的計數(shù)就是我們所說的。理清了思路之后,實現(xiàn)這個小應(yīng)用就變得很簡單了。

          可以點擊?這里提前體驗一下這個簡單的小應(yīng)用

          TODO小應(yīng)用

          首先我們需要先實現(xiàn)觀察者模式中的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()?{}
          }

          那么接下來的代碼就是關(guān)于上面待辦的具體實現(xiàn)了,代碼中也添加了相應(yīng)的注釋,我們來看一下。

          待辦應(yīng)用的邏輯部分:

          //?表單的狀態(tài)
          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?=>?`
        2. ${text}
        3. `
          ).join('');
          ?}
          }

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

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

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

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

          從上面的代碼我們可以清楚地知道這個應(yīng)用的每一個部分,被觀察的Subject就是我們的todo對象,它的狀態(tài)就是待辦列表。它維護的觀察者列表分別是展示待辦列表的listObserver和展示待辦數(shù)量的countObserver。一旦todo的列表新增加了一項待辦,那么就會通知這兩個觀察者去做相應(yīng)的內(nèi)容更新。這樣代碼的邏輯就很直觀明了。如果以后在狀態(tài)變更的時候還要添加新的功能,我們只需要再次添加一個相應(yīng)的observer就可以了,維護起來也很方便。

          當(dāng)然上面的代碼只實現(xiàn)了很基礎(chǔ)的功能,還沒有包含待辦的完成和刪除,以及對于未完成和已完成的待辦的分類展示。而且列表的渲染每次都是重新渲染的,沒有復(fù)用的邏輯。因為我們本章的內(nèi)容是跟大家一起來探討一下觀察者模式,所以上面的代碼比較簡陋,也只是為了說明觀察者模式的用法。相信優(yōu)秀的你能夠在這個基礎(chǔ)上,把這些功能都完善好,快去試試吧。

          其實我們學(xué)習(xí)這些設(shè)計模式,都是為了讓代碼的邏輯更加清晰明了,能夠復(fù)用一些代碼的邏輯,減少重復(fù)的工作,提升開發(fā)的效率。讓整個應(yīng)用更加容易維護和拓展。當(dāng)然不能為了使用而使用,在使用之前,需要對當(dāng)前的問題做一個全面的了解。到底需不需要使用某個設(shè)計模式是一個需要考慮清楚的問題。

          好啦,關(guān)于觀察者模式到這里就結(jié)束啦,大家如果有什么意見和建議可以在文章下面下面留言,我們一起探討一下。也可以在這里提出來,我們更好地進行討論。也歡迎大家關(guān)注我的公眾號關(guān)山不難越,隨時隨地獲取文章的更新。

          大家如果有興趣的話,可以看一下我之前的作品主線程,如果這個小程序能夠?qū)δ阌幸恍椭脑捑透昧藒

          參考鏈接:

          • The Observer Pattern
          • How to Use the Observable Pattern in JavaScript

          文章封面圖來源:unDraw


          關(guān)注我

          大家也可以關(guān)注我的公眾號《腦洞前端》獲取更多更新鮮的前端硬核文章,帶你認識你不知道的前端。



          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.97色色 | 青青艹在线视频 | 淫乱系列国产 | 精品三级国产 | 色哟哟AV|