Observer Pattern
觀察者模式的 keypoint 在於通知與接受通知的過程
Story
有一份工作叫佈署,佈署需要文件才能執行
interface Worker
{
// 佈署總是需要文件
public function deploy($document);
}
今天公司只有一個工程師 Alex 會做佈署。
class Alex implements Worker
{
public function deploy($document)
{
echo "文件是這樣的,", $document, PHP_EOL;
echo "已執行完畢", PHP_EOL;
}
}
Boss 對佈署深感興趣--畢竟是直接的利益關係者--,他希望佈署完能通知他,於是 deploy 的流程被修改了
class Alex implements Worker
{
public function deploy($document)
{
echo "文件是這樣的,", $document, PHP_EOL;
echo "已執行完畢", PHP_EOL;
// 通知 Boss ,老闆想宣傳
$boss = new Boss();
$boss->propaganda();
}
}
然後行銷跟客戶也對佈署感興趣,也都希望能通知, Alex 的流程完全被打亂了
class Alex implements Worker
{
public function deploy($document)
{
echo "文件是這樣的,", $document, PHP_EOL;
echo "已執行完畢", PHP_EOL;
// 通知 Boss ,老闆想宣傳
$boss = new Boss();
$boss->propaganda();
// 通知 Marketing ,行銷想準備下次要做的東西
$marketing = new Marketing();
$marketing->prepare();
// 通知 Customer ,客戶想使用服務
$customer = new Customer();
$customer->useService();
}
}
Alex 最後爆走,提議:不如我做一個公告,你們有興趣自己過來看
// 看公告的方法
interface Watch {
public function watch();
}
// 每個人都會看公告
class Boss implements Watch {
public function watch() {
$this->propaganda();
}
public function propaganda() {
echo "我是 Boss ,我想宣傳", PHP_EOL;
}
}
class Marketing implements Watch {
public function watch() {
$this->prepare();
}
public function prepare() {
echo "我是 Marketing ,我想提早準備", PHP_EOL;
}
}
class Customer implements Watch {
public function watch() {
$this->useService();
}
public function useService() {
echo "我是 Customer ,我想使用服務", PHP_EOL;
}
}
最後大家想知道佈署最新狀況的話,再跟 Alex 說一聲即可
class Alex implements Worker
{
private $watcher = [];
public function deploy($document)
{
echo "文件是這樣的,", $document, PHP_EOL;
echo "已執行完畢", PHP_EOL;
// 公告
foreach ($this->watcher as $watcher) {
$watcher->watch();
}
}
public function addWatcher(Watch $watcher)
{
$this->watcher[] = $watcher;
}
}
$worker = new Alex();
$worker->addWatcher(new Boss());
$worker->addWatcher(new Marketing());
$worker->addWatcher(new Customer());
// 佈署了
$worker->deploy('git pull 就對了');
Definition
觀察者模式的定義
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
定義物件之間的一對多關係,使得一個物件改變狀態時,所有倚賴於它的物件都會得到通知並且自動被更新。
Context
- 物件狀態異動的頻率無法預料。
- 為了達到狀態一致性而將物件緊密耦合在一起,將會降低物件的重複使用性。
- 不能限制資料相依物件的個數,可能有任意數量的物件都有興趣想要知道物件的狀態。
- 當某物件狀態改變時,其相依物件必須適時自動被通知。
此參考 Teddy 大師的文章
UML
通用類別圖如下:
Plantuml 程式碼
@startuml
interface Subject {
+ {abstract} attach(o: Observer)
+ {abstract} detach(o: Observer)
+ {abstract} notify()
}
interface Observer {
+ {abstract} update()
}
class ConcreteSubject
class ConcreteObserver
Subject o-> Observer : - observers
Subject
各角色定義
- Subject 被觀察者,它必需要能夠動態的
增加
、取消
和通知
觀察者,通常是抽象類別或實作類別 - Observer 觀察者,在接收到被觀察者的通知時,對接受到的訊息做處理
- ConcreteSubject 具體的被觀察者,定義自己的業務邏輯,並定義對哪些事件做通知
- ConcreteObserver 具體的觀察者,每個觀察者在收到消息後的處理回應是不同的,各自有各自的邏輯
How to Extends
物件耦合是在抽象層,所以只要有實作抽象,都可以互相耦合。想要擴充被觀察者或觀察者都很容易
Pros and Cons
Pros
- 觀察者和被觀察者的耦合是建立在抽象層上,符合 Dependency Inversion Principle
- 在擴充被觀察者或觀察者都非常容易,只要基本通知的邏輯沒改變的話,抽象層就不需修改,符合 Open Close Principle
Cons
- 當被觀察者或觀察者之間有循環依賴的話,系統有機會整個崩潰。
- 根據定義,被觀察者可以加入不限個數的觀察者,那通知的過程可能會非常的長,並影響正常程式的執行。
- 承上,當某個觀察者發生錯誤時,開發者在除錯上會非常困難。