百度工程師教你玩轉設計模式(觀察者模式)
要寫好代碼,設計模式(Design Pattern)是必不可少的基本功,設計模式是對面向對象設計(Object Oriented Design)中反覆出現的問題的一種有效解決方案,本次從比較常見的觀察者模式入手(Observer Pattern)。在觀察者模式中,存在多個觀察者對象依賴 (Observer) 都依賴同一個目標對象(Subject),當被依賴的目標對象發生變化的時候,會通知所有依賴它的觀察者對象,然後各個觀察者對象根據自己的需要做出對應的響應。
其主要優點如下:
-
降低了目標與觀察者之間的耦合關係
-
建立了目標與觀察者之間的變化觸發機制
其主要缺點如下:
-
目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現循環引用
-
當觀察者對象很多時,通知的發佈會花費很多時間,影響程序的效率
比較抽象不好理解?我們來參考日常功能設計中幾個常見的場景。
01
觀察者模式在天氣預報場景的應用
關注天氣預報是我們日常生活中一個比較重要的習慣,不同的角色對於天氣的變化由有着不同的反應。例如明天特大暴雨,氣象部門考慮的是評估併發布合理的政策指導,教育部門需要評估是否需要停課,應急部門考慮的是如何提前準備應急救援,環衛部門需要準備暴雨後的大量環衛工作,打工人需要考慮如何上下班通勤,也存在一些不受此次暴雨影響地區的人什麼也不需要考慮。結合上述觀察者模式的介紹,在此場景中,天氣屬於被各個角色依賴的目標對象 (Subject),氣象 / 教育 / 應急 / 環衛等部門,打工人,其他地區的人都屬於觀察者對象 (Observer)。目標對象發生變化後,各個觀察者對象收到消息後都會作出對應的響應措施。基於以上場景實現的簡化版觀察者模式類圖如下:
-
目標對象 (WeatherSubject):即被觀察的目標對象,即本例中的天氣,持有一個天氣狀態的屬性 state,以及綁定觀察者的方法 attach(),觀察者的方法 notifyAllObservers()。當狀態發生變更後,調用 notifyAllObservers() 方法通知所有觀察者。
-
抽象觀察者對象 (Observer):用於定義各個觀察者的行爲規範,也可以採用接口的方式實現
-
實際觀察者對象:MeteorologicalDepartment、RescueDepartment、OfficeWorker、Other 等,實際需要關注目標對象並作出響應的角色。繼承自抽象觀察者對象,並基於各自的關注點實現了對於的響應方法 update()。
基於以上示例的 Java 版代碼 demo 試下:
package com;
import java.util.ArrayList;
import java.util.List;
// 基於天氣預報場景的觀察者模式實現Demo
public class ObserverPatternDemo {
// 天氣對象
static class WeatherSubject {
private int state;
List<Observer> observers = new ArrayList<>();
void attach(Observer observer) {
observers.add(observer);
}
// 通知觀察者
void notifyAllObservers() {
for (Observer o : observers
) {
o.update(getState());
}
}
public int getState() {
return state;
}
// 狀態變更
public void setState(int state) {
this.state = state;
if (state == 1) {
System.out.println("=====明天要下大暴雨=====");
} else {
System.out.println("=====明天天氣很好呀=====");
}
// 變更後通知觀察者
notifyAllObservers();
}
}
// 抽象觀察者對象
static abstract class Observer {
abstract void update(int state);
}
// 政府氣象部門
static class MeteorologicalDepartment extends Observer {
@Override
void update(int state) {
if (state == 1) {
System.out.println("【氣象部門】發出預警");
}
}
}
// 政府應急救援部門
static class ResumeDepartment extends Observer {
@Override
void update(int state) {
if (state == 1) {
System.out.println("【救援部門】準備應急預案");
}
}
}
// 打工人
static class OfficeWorker extends Observer {
@Override
void update(int state) {
if (state == 1) {
System.out.println("【打工人】思考明天怎麼上下班通勤");
} else {
System.out.println("【打工人】努力搬磚");
}
}
}
// 其他無影響的人
static class Other extends Observer {
@Override
void update(int state) {
if (state == 1) {
System.out.println("【其他人】下雨啊,對我影響不大");
} else {
System.out.println("【其他人】明天天氣不錯,出去玩玩");
}
}
}
public static void main(String[] args) {
// 初始化目標對象
WeatherSubject subject = new WeatherSubject();
// 初始化觀察者對象,並關注目標對象
Observer ob1 = new MeteorologicalDepartment();
Observer ob2 = new ResumeDepartment();
Observer ob3 = new OfficeWorker();
Observer ob4 = new Other();
subject.attach(ob1);
subject.attach(ob2);
subject.attach(ob3);
subject.attach(ob4);
// 狀態變化: 大暴雨
subject.setState(1);
// 狀態變化: 好天氣
subject.setState(2);
//執行結果如下
=====明天要下大暴雨=====
【氣象部門】發出預警
【救援部門】準備應急預案
【打工人】思考明天怎麼上下班通勤
【其他人】下雨啊,對我影響不大
=====明天天氣很好呀=====
【打工人】努力搬磚
【其他人】明天天氣不錯,出去玩玩
}
}
02
觀察者模式在支付場景中的應用
在支付業務場景下,用戶購買一件商品,當支付成功之後三方會回調自身,在這個時候系統可能會有很多需要執行的邏輯(如:更新訂單狀態,發送短信通知,通知物流系統開始備貨,贈送禮品…)。
通常最直觀的處理方式,會創建對應支付系統需要依賴的類(Order、SMS、Express...)以及支付類 Pay,在支付主邏輯中實例化各依賴類,當用戶支付成功後,逐個調用各依賴類處理邏輯。但這樣支付類要了解需要通知哪些類,且支付主邏輯臃腫耦合也較重,不便於擴展和維護。
觀察者模式則可以更好的處理這種支付場景,這些支付系統所依賴的類邏輯之間並沒有強耦合,因此適合使用觀察者模式去實現這些功能,對與支付類來說不關心需要通知哪些類,只需要提供通知列表,當有更多的操作時,只需要向通知列表中添加新的觀察者即可,用戶支付成功通知所有註冊的觀察者。實現了對修改關閉,對擴展開放的開閉原則。
具體實現上通常包括以下幾部分:
-
抽象出被觀察者 Observable 類,抽象出共有的屬性和方法;
-
創建具體被觀察者 Pay 類,只要用戶支付成功,它就要去通知所有註冊的觀察者;
-
抽象出觀察者接口 Observer,它包含了一個更新自己的抽象方法 update,當接到具體主題 Pay 的更改通知時被調用;
-
創建訂單、短信、物流等具體觀察者類,它們要觀察支付狀態,得到 Pay 的更改通知時更新自身的狀態;
// 抽象主題(Subject)角色
public abstract class Observable {
// 觀察者列表
private List<Observer> observers = new ArrayList<>();
// 添加觀察者
public void add(Observer observer){
observers.add(observer);
}
// 移除觀察者
public void remove(Observer observer){
observers.remove(observer);
}
// 通知觀察者
protected void notifyObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
// 具體主題(Concrete Subject)角色
public class Pay extends Observable{
public void pay(){
System.out.println("支付完成.");
// 通知觀察者
super.notifyObservers();
}
}
// 抽象觀察者(Observer)角色
public interface Observer {
// 通知
void update();
}
// 具體觀察者(Concrete Observer)角色
// 訂單:修改訂單狀態
public class Order implements Observer{
@Override
public void update() {
System.out.println("修改訂單狀態...");
}
}
// 短信:發送扣款短信到用戶
public class SMS implements Observer{
@Override
public void update() {
System.out.println("賬戶扣款短信通知...");
}
}
//物流:通知物流系統開始備貨
public class Express implements Observer{
@Override
public void update() {
System.out.println("物流開始備貨...");
}
}
// 客戶端調用
public class Client {
public static void main(String[] args) {
Pay pay = new Pay();
pay.add(new Order());
pay.add(new SMS());
pay.add(new Express());
pay.pay();
}
}
03
觀察者模式在數據訂閱場景的應用
在實際應用中,數據推送的場景下,經常會用到觀察者模式。以小說資源爲例,新的章節生產完成後,需要該數據的業務很多,如搜索、網盤、貼吧、小度等,各方接收數據的方式各異。
對於每個訂閱方,會實現截然不同的訂閱方法,可能是推送 http 接口、kafka 隊列、afs 文件系統等。可以分別實現這些訂閱方法,並進行註冊,當出現消息發佈時,依次觸發訂閱者方法。
以下是 PHP 語言的實現 demo:
interface Observer {
public function push($data);
}
class Publish {
private $observers = array();
public function register(Observer $observer) {
$this->observers[] = $observer;
}
public function delete(Observer $observer) {
$index = array_search($observer, $this->observers);
if ($index !== FALSE && array_key_exists($index, $this->observers)) {
unset($this->_observers[$index]);
}
}
public function push($data) {
foreach ($this->observers as $observer) {
$observer->push($data);
}
}
}
class Search implements Observer {
public function push($data) {
//推送afs
}
}
class Cloud implements Observer {
public function push($data) {
//推送kafaka
}
}
$publish = new Publish();
$publish->register(new Search());
$publish->register(new Cloud());
$publish->push($data);
04
總 結
通過對以上三個實際案例的講解和具體的代碼實現閱讀,大家對觀察者模式的應用場景和具體實現方案應該有了更加深入的瞭解了。結合以上三個案例的的分析,適合觀察者模式的場景都有以下典型特徵:
-
存在多對一的依賴關係:即多個觀察者依賴同一個目標對象
-
存在目標變更觸發機制:目標變更後,需要觸發一系列的其他任務
通過觀察者機制來實現以上場景,可以實現目標類和觀察者類的解耦,即目標對象無需知道需要通知哪些觀察者,方便後續的擴展與維護。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Wx1HTnccJJ0H8CyqBEFS_A