新聞中心
大部分講設計模式的文章都是使用的 Java、C++ 這樣的以類為基礎的靜態(tài)類型語言,作為前端開發(fā)者,js 這門基于原型的動態(tài)語言,函數(shù)成為了一等公民,在實現(xiàn)一些設計模式上稍顯不同,甚至簡單到不像使用了設計模式,有時候也會產(chǎn)生些困惑。

成都創(chuàng)新互聯(lián)一直在為企業(yè)提供服務,多年的磨煉,使我們在創(chuàng)意設計,網(wǎng)絡營銷推廣到技術研發(fā)擁有了開發(fā)經(jīng)驗。我們擅長傾聽企業(yè)需求,挖掘用戶對產(chǎn)品需求服務價值,為企業(yè)制作有用的創(chuàng)意設計體驗。核心團隊擁有超過十多年以上行業(yè)經(jīng)驗,涵蓋創(chuàng)意,策化,開發(fā)等專業(yè)領域,公司涉及領域有基礎互聯(lián)網(wǎng)服務成都棕樹電信機房、App定制開發(fā)、手機移動建站、網(wǎng)頁設計、網(wǎng)絡整合營銷。
下面按照「場景」-「設計模式定義」- 「代碼實現(xiàn)」-「總」的順序來總結一下,如有不當之處,歡迎交流討論。
場景
假設我們在開發(fā)一款外賣網(wǎng)站,進入網(wǎng)站的時候,第一步需要去請求后端接口得到用戶的常用外賣地址。然后再去請求其他接口、渲染頁面。如果什么都不考慮可能會直接這樣寫:
// 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 thesubject, maintains a list of its dependents, calledobservers, and notifies them automatically of any state changes, usually by calling one of their methods.”
很好理解的一個設計模式,有一個 subject 對象,然后有很多 observers 觀察者對象,當 subject 對象有變化的時候去通知 observer 對象即可。
再看一下 UML 圖和時序圖:
每一個觀察者都實現(xiàn)了 update 方法,并且調用 Subject 對象的 attach方法訂閱變化。當 Subject 變化時,調用 Observer 的 update 方法去通知觀察者。
先用 java 寫一個簡單的例子:
公眾號文章可以看作是 Subject ,會不定期更新。然后每一個用戶都是一個 Observer ,訂閱公眾號,當更新的時候就可以第一時間收到消息。
import java.util.ArrayList;
interface Observer {
public void update();
}
// 提取 Subject 的公共部分
abstract class Subject {
private ArrayListlist = new ArrayList ();
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 + " 并且轉發(fā)了");
}
}
// 小剛
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(); // 通知觀察者
}
}
輸出結果如下:
上邊的實現(xiàn)主要是為了符合最原始的定義,調用 update 的時候沒有傳參。如果觀察者需要的參數(shù)是一致的,其實這里也可以直接把更新后的數(shù)據(jù)傳過去,這樣觀察者就不需要向上邊一樣再去調用 subject.getPost() 手動拿更新后的數(shù)據(jù)了。
這兩種不同的方式前者叫做拉 (pull)模式,就是收到 Subject 的通知后,通過內(nèi)部的 Subject 對象調用相應的方法去拿到需要的數(shù)據(jù)。
后者叫做推 (push) 模式,Subject更新的時候就將數(shù)據(jù)推給觀察者,觀察者直接使用即可。
下邊用 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 + " 并且轉發(fā)了");
}
}
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 的時候直接將數(shù)據(jù)傳給觀察者,看起來會簡潔很多。
代碼實現(xiàn)
回到開頭的場景,我們可以利用觀察者模式將獲取地址后的一系列后續(xù)操作解耦出來。
// 頁面里有三個模塊 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 和多個觀察者,減輕了它們之間的過度耦合。再說簡單點就是利用回調函數(shù),異步完成后調用傳入的回調即可。但上邊寫的觀察者模式還是有一些缺點:
Subject 仍需要自己維護一個觀察者列表,進行push 和 update。
如果有其他的模塊也需要使用觀察者模式,還需要模塊本身再維護一個新的觀察者列表,而不能復用之前的代碼。
Subject 需要知道觀察者提供了什么方法以便未來的時候進行回調。
下一篇文章會繼續(xù)改進上邊的寫法,觀察者模式的本質思想不變(某個對象變化,然后通知其他觀察者對象進行更新)。
但寫法上會引入一個中間平臺,便于代碼更好的復用,使得 Subject 和觀察者進行更加徹底的解耦,同時給了它一個新的名字「發(fā)布訂閱模式」。
新聞標題:前端的設計模式系列-觀察者模式
文章路徑:http://m.5511xx.com/article/ccccosh.html


咨詢
建站咨詢
