設計模式學習筆記 - 3
寫在前面
今日特餐:觀察者模式 (Observer)
先稍微複習上一篇筆記《設計模式學習筆記 - 2》的內容-策略模式:
可以定義和封裝一系列的演算法,並且讓它們是可替換/對調的。這個模式可以讓你在不影響用戶端的情況下獨立改變演算法。
我們透過冒險遊戲的角色及武器,對此模式有一定的認識與瞭解。
今天要學習的設計模式是觀察者模式,以下正文:
Observer 觀察者模式
發佈者 + 訂閱者 = 觀察者模式
觀察者模式定義務鍵之間的一對多依賴關係,當一個物件改變狀態時,依賴它的物件都會自動收到通知與更新。
可以想成youtuber 在影片的開頭或結尾,都會告訴觀眾,記得按讚、訂閱、開啟小鈴鐺,訂閱+開啟小鈴鐺,就可以在該youtuber 更新影片的時候,收到第一手的通知,而有訂閱的觀眾就成了觀察者。
為了確保每個youtuber 的行為,與每個觀眾的行為互相不干擾,所以在設計上會將youtuber 及 觀眾個別實做相同的介面:
youtuberInterface 會有訂閱、取消訂閱等方法,而AudienceInterface 會有接收通知的方法,確保不會因為每個youtuber 或觀眾不同,導致通知的時候出現問題。

根據上圖,我們透過簡單的程式碼來實做看看,其中我將上傳影片、取得訂閱者清單、增加訂閱者、取消訂閱等方法都直接放入介面中,是為了確定每個youtuber都是在相同的基礎行為上。
- YoutuberInterface - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 Interface Youtuber {
 public function register(Audience $audience);
 public function remove(Audience $audience);
 public function notifyAudience();
 public function uploadVideo();
 public function getAudienceList();
 public function setAudience(Audience $audience);
 public function removeAudience(Audience $audience);
 }
- AudienceInterface - 1 
 2
 3
 4
 5
 Interface Audience {
 public function update(Youtuber $youtuber);
 }
- YoutuberImplement - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 class YoutuberImpl implements Youtuber {
 public $name;
 private $audienceList = [];
 public function __construct($name) {
 $this->name = $name;
 }
 public function register(Audience $audience) {
 echo 'You are an observer now!' . PHP_EOL;
 $this->setAudience($audience);
 echo 'there are ' . count($this->getAudienceList()) . ' audience !' . PHP_EOL;
 }
 public function remove(Audience $audience) {
 echo 'You are no longer an observer!' . PHP_EOL;
 $this->removeAudience($audience);
 }
 public function notifyAudience() {
 $audienceList = $this->getAudienceList();
 foreach ($audienceList as $audience) {
 $audience->update($this);
 }
 }
 public function uploadVideo() {
 echo 'video upload successful!' . PHP_EOL;
 $this->notifyAudience();
 }
 public function getAudienceList() {
 return $this->audienceList;
 }
 public function setAudience(Audience $audience) {
 $audienceList = $this->audienceList;
 $audienceList[] = $audience;
 $this->audienceList = $audienceList;
 }
 public function removeAudience(Audience $audience) {
 $audienceList = $this->audienceList;
 $newAudienceList = [];
 foreach ($audienceList as $item) {
 if ($audience != $item) {
 $newAudienceList[] = $item;
 }
 }
 $this->audienceList = $newAudienceList;
 echo 'there are ' . count($this->getAudienceList()) . ' audience !' . PHP_EOL;
 }
 }
- AudienceImplement - 1 
 2
 3
 4
 5
 6
 7
 class AudienceImpl implements Audience {
 public function update(Youtuber $youtuber) {
 echo $youtuber->name . ' has uploaded a new video!' . PHP_EOL;
 }
 }
- 測試 
測試的部分我們只操作一個頻道及一個觀眾,作為簡單的示例:
我們建立一個新的頻道叫做蔡阿嘎,以及一位觀眾Andy,
Andy 可以針對蔡阿嘎這個頻道進行訂閱、取消訂閱,而蔡阿嘎頻道可以上傳影片,並且通知訂閱者Andy。
| 1 | 
 | 
思考
這邊例子僅簡單以echo 訊息的方式表示通知。未來可能要思考,通知的時候是傳遞哪些資料過去,傳遞所有資訊的話會不會造成觀察者的負擔。
再以書上的例子作為思考的出發點,要設計一個氣象站(Subject),每當天氣資訊更新的時候,就通知觀察者(Observer)們溫度、濕度、氣壓、降雨機率…等所有的天氣資訊。
但可能不是每個觀察者都需要所有的天氣資訊,比方說我每天出門前,我會在意的是今天的溫度及降雨機率,那麼其他資訊對我來說就是多餘的了。
所以可以調整為,天氣站要開放讓觀察者們自行取得需要的資料,而通知僅僅是告知他們,天氣資訊已經更新了。
個別的觀察者因為收到這則通知,自行向氣象站索取所需的資料:
getTemperature();
getHumidity();
…
以Youtube 的例子稍微說明,但實際的Youtube 應該做了更多更複雜的事情,
今天的筆記-觀察者模式到這邊告一段落,如果有謬誤的地方,歡迎email 給我指正或來信討論,謝謝!