前端的設計模式系列 - 觀察者模式
代碼也寫了幾年了,設計模式處於看了忘,忘了看的狀態,最近對設計模式有了點感覺,索性就再學習總結下吧。
大部分講設計模式的文章都是使用的 Java
、C++
這樣的以類爲基礎的靜態類型語言,作爲前端開發者,js
這門基於原型的動態語言,函數成爲了一等公民,在實現一些設計模式上稍顯不同,甚至簡單到不像使用了設計模式,有時候也會產生些困惑。
下面按照「場景」-「設計模式定義」- 「代碼實現」-「總」的順序來總結一下,如有不當之處,歡迎交流討論。
場景
假設我們在開發一款外賣網站,進入網站的時候,第一步需要去請求後端接口得到用戶的常用外賣地址。然後再去請求其他接口、渲染頁面。如果什麼都不考慮可能會直接這樣寫:
// getAddress 異步請求
// 頁面裏有三個模塊 A,B,C 需要拿到地址後再進行下一步
// A、B、C 三個模塊都是不同人寫的,提供了不同的方法供我們調用
getAddress().then(res => {
const address = res.address;
A.update(address)
B.next(address)
C.change(address)
})
此時頁面裏多了一個模塊 D
,同樣需要拿到地址後進行下一步操作,我們只好去翻請求地址的代碼把 D
模塊的調用補上。
// getAddress 異步請求
// 頁面裏有三個模塊 A,B,C 需要拿到地址後再進行下一步
// A、B、C 三個模塊都是不同人寫的,提供了不同的方法供我們調用
getAddress().then(res => {
const address = res.address;
A.update(address)
B.next(address)
C.change(address)
D.init(address)
})
可以看到各個模塊和獲取地址模塊耦合嚴重,A
、B
、C
模塊有變化或者有新增模塊,都需要深入到獲取地址的代碼去修改,一不小心可能就改出問題了。
此時就需要觀察者模式了。
設計模式定義
可以看下 維基百科的介紹:
★
The observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
”
很好理解的一個設計模式,有一個 subject
對象,然後有很多 observers
觀察者對象,當 subject
對象有變化的時候去通知 observer
對象即可。
再看一下 UML
圖和時序圖:
image-20220127110751274
每一個觀察者都實現了 update
方法,並且調用 Subject
對象的 attach
方法訂閱變化。當 Subject
變化時,調用 Observer
的 update
方法去通知觀察者。
先用 java
寫一個簡單的例子:
公衆號文章可以看作是 Subject
,會不定期更新。然後每一個用戶都是一個 Observer
,訂閱公衆號,當更新的時候就可以第一時間收到消息。
import java.util.ArrayList;
interface Observer {
public void update();
}
// 提取 Subject 的公共部分
abstract class Subject {
private ArrayList<Observer> list = new ArrayList<Observer>();
public void attach(Observer observer){
list.add(observer);
}
public void detach(Observer observer){
list.remove(observer);
}
public void notifyObserver(){
for(Observer observer : list){
observer.update();
}
}
}
// 具體的公衆號,提供寫文章和得到文章
class WindLiang extends Subject {
private String post;
public void writePost(String p) {
post = p;
}
public String getPost() {
return post;
}
}
// 小明
class XiaoMing implements Observer {
private WindLiang subject;
XiaoMing(WindLiang sub) {
subject = sub;
}
@Override
public void update(){
String post = subject.getPost();
System.out.println("我收到了" + post + " 並且點了個贊");
}
}
// 小楊
class XiaoYang implements Observer {
private WindLiang subject;
XiaoYang(WindLiang sub) {
subject = sub;
}
@Override
public void update(){
String post = subject.getPost();
System.out.println("我收到了" + post + " 並且轉發了");
}
}
// 小剛
class XiaoGang implements Observer {
private WindLiang subject;
XiaoGang(WindLiang sub) {
subject = sub;
}
@Override
public void update(){
String post = subject.getPost();
System.out.println("我收到了" + post + " 並且收藏");
}
}
public class Main {
public static void main(String[] args) {
WindLiang windliang = new WindLiang(); // Subject
XiaoMing xiaoMing = new XiaoMing(windliang);
XiaoYang xiaoYang = new XiaoYang(windliang);
XiaoGang xiaoGang = new XiaoGang(windliang);
// 添加觀察者
windliang.attach(xiaoMing);
windliang.attach(xiaoYang);
windliang.attach(xiaoGang);
windliang.writePost("新文章-觀察者模式,balabala"); // 更新文章
windliang.notifyObserver(); // 通知觀察者
}
}
輸出結果如下:
上邊的實現主要是爲了符合最原始的定義,調用 update
的時候沒有傳參。如果觀察者需要的參數是一致的,其實這裏也可以直接把更新後的數據傳過去,這樣觀察者就不需要向上邊一樣再去調用 subject.getPost()
手動拿更新後的數據了。
這兩種不同的方式前者叫做拉 (pull)
模式,就是收到 Subject
的通知後,通過內部的 Subject
對象調用相應的方法去拿到需要的數據。
後者叫做推 (push)
模式,Subject
更新的時候就將數據推給觀察者,觀察者直接使用即可。
下邊用 js
改寫爲推模式:
const WindLiang = () => {
const list = [];
let post = "還沒更新";
return {
attach(update) {
list.push(update);
},
detach(update) {
let findIndex = -1;
for (let i = 0; i < list.length; i++) {
if (list[i] === update) {
findIndex = i;
break;
}
}
if (findIndex !== -1) {
list.splice(findIndex, 1);
}
},
notifyObserver() {
for (let i = 0; i < list.length; i++) {
list[i](post);
}
},
writePost(p) {
post = p;
},
};
};
const XiaoMing = {
update(post){
console.log("我收到了" + post + " 並且點了個贊");
}
}
const XiaoYang = {
update(post){
console.log("我收到了" + post + " 並且轉發了");
}
}
const XiaoGang = {
update(post){
console.log("我收到了" + post + " 並且收藏");
}
}
windliang = WindLiang();
windliang.attach(XiaoMing.update)
windliang.attach(XiaoYang.update)
windliang.attach(XiaoGang.update)
windliang.writePost("新文章-觀察者模式,balabala")
windliang.notifyObserver()
在 js
中,我們可以直接將 update
方法傳給 Subject
,同時採取推模式,調用 update
的時候直接將數據傳給觀察者,看起來會簡潔很多。
代碼實現
回到開頭的場景,我們可以利用觀察者模式將獲取地址後的一系列後續操作解耦出來。
// 頁面裏有三個模塊 A,B,C 需要拿到地址後再進行下一步
// A、B、C 三個模塊都是不同人寫的,提供了不同的方法供我們調用
const observers = []
// 註冊觀察者
observers.push(A.update)
observers.push(B.next)
obervers.push(C.change)
// getAddress 異步請求
getAddress().then(res => {
const address = res.address;
observers.forEach(update => update(address))
})
通過觀察者模式我們將獲取地址後的操作解耦了出來,未來有新增模塊只需要註冊觀察者即可。
當 getAddress
很複雜的時候,通過觀察者模式會使得未來的改動變得清晰,不會影響到 getAddress
的邏輯。
必要的話也可以把 observers
抽離到一個新的文件作爲一個新模塊,防止讓一個文件變得過於臃腫。
總
觀察者模式比較好理解,通過抽象出一個 Subject
和多個觀察者,減輕了它們之間的過度耦合。再說簡單點就是利用回調函數,異步完成後調用傳入的回調即可。但上邊寫的觀察者模式還是有一些缺點:
-
Subject
仍需要自己維護一個觀察者列表,進行push
和update
。 -
如果有其他的模塊也需要使用觀察者模式,還需要模塊本身再維護一個新的觀察者列表,而不能複用之前的代碼。
-
Subject
需要知道觀察者提供了什麼方法以便未來的時候進行回調。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/a79WUhe-hcMAiEsiz6KkwQ