--- title: 'Observer 觀察者模式' disqus: kyleAlien --- Observer 觀察者模式 === ## Overview of Content 如有引用參考請詳註出處,感謝 :smile: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Observer 觀察者模式 | JDK Observer | Android Framework Listview**](https://devtechascendancy.com/observer-jdk-android-framework-listview/) ::: [TOC] ## Observer 使用場景 * 當一個狀態或是變數改變時,需要**通知所有擁有它這個變數的物件** (就像是有多人訂閱 "Hello" 日報,如果日報訂閱價格改變,就必須通知所有已訂閱的用戶) * 定義一個可觀察物件,讓可觀察物件與其他物件產生 **一對多的依賴關係**,並在刷新時通知其他物件 > 關聯關係是 ==可拆分== 的,**並非組合關係** ### Observer UML * Observer 角色介紹 | 角色 | 功能說明 | | -------- | -------- | | Observer | 觀察者抽象,**定義一個更新方法** | | ConcreteObserver | 實作一個更新方法的細節 | | Subject | 聚合 `Observer`,並宣告抽象通知方法 | | ConcreteSubject | 實作抽象通知方法 | :::info * 建議在被觀察者(`Subject`)要通知前,就做出是否通知的判斷,不要延到觀察者(`Observer`)判斷是否消費這個事件 > 減少 Observer 的個別判斷邏輯 ::: > ![](https://i.imgur.com/h3SXBqr.png) ### Observer 設計 - 優缺點 * **Observer 設計優點** : 1. 觀察者與被觀察者,是抽象耦合,對應業務變化 2. 增強系統靈活性、可擴充、自由度 3. 可以建立一套觸發機制(鏈式調用) * **Observer 設計缺點** : 1. 考慮到執行效率問題,由於是 **輪巡更新**,所以當一個觀察者卡頓 (重量級) 會導致後方的資料更新也卡頓 > 可以採用異步通知的方式(這種通知方式也稱為 `emit`) 2. 在 **多線程的狀況下也要考慮同步問題** ## Observer 實現 ### Observer 標準 1. **`Observer` 類**:定義一個抽象的共同方法 ```java= public interface Observer { void changePrice(int newPrize); } ``` 2. **`ConcreteObserver` 類**:Person 實做 `Observer` 類,並決定在接收到通知後的行為細節 ```java= public class Person implements Observer { private final String name; public Person(String name) { this.name = name; } @Override public void changePrice(int newPrize) { System.out.println("Name : " + name + ", NewsPaper: " + newPrize); } } ``` 3. **`Subject` 類**:NewsSubject 的重點是 **聚合抽象 `Observer`**,在定義一個抽象通知方法 ```java= public abstract class NewsSubject { protected final List<Observer> list = new ArrayList<>(); public void addObserver(Observer observer) { list.add(observer); } public void removeObserver(Observer observer) { list.remove(observer); } public abstract void notifyUpdate(int newPrice); } ``` 4. **`ConcreteSubject` 類**:這裡以報社為例,每一間報社對於改動新價格後的優惠不同,這個不同點會定義在抽想方法中 ```java= // 蘋果日報 public class AppleNews extends NewsSubject { private int price; public AppleNews(int price) { this.price = price; } @Override public void notifyUpdate(int newPrice) { if(price == newPrice) { return; } // 折價 10 % price = newPrice - (newPrice / 10); for(Observer item : list) { item.changePrice(price); } } } // -------------------------------------------------------- // 自由時報 public class FreedomNews extends NewsSubject { private int price; public FreedomNews(int price) { this.price = price; } @Override public void notifyUpdate(int newPrice) { if(price == newPrice) { return; } // 折扣 10 元 price = newPrice - 10; for(Observer item : list) { item.changePrice(price); } } } ``` * **User 測試程式**:可以看到兩個重點,^1.^ 不需要手動新、呼叫每個訂閱者,^2.^ 透過抽象定義可以讓每個觀察管理者 (`ConcreteSubject`) 定義細節 ```java= public class MainObserver { public static void main(String[] args) { System.out.println("Apple news ----------------------"); AppleNews appleNews = new AppleNews(330); appleNews.addObserver(new Person("Alien")); appleNews.addObserver(new Person("Pan")); appleNews.addObserver(new Person("Domo")); appleNews.notifyUpdate(340); System.out.println("Freedom news ----------------------"); FreedomNews freedomNews = new FreedomNews(330); freedomNews.addObserver(new Person("Alien")); freedomNews.addObserver(new Person("Pan")); freedomNews.addObserver(new Person("Domo")); freedomNews.notifyUpdate(340); } } ``` > ![](https://i.imgur.com/ohSM6j8.png) :::danger * 目前這裡暫時不關心 Multi Thread 同步問題,**如果需要使用 MultiThread,則需要加上 `sychronized` 關鍵字同步** ::: ## JDK 內建觀察者 - 介紹 [**JDK Source Code**](https://github.com/openjdk-mirror/jdk7u-jdk/blob/master/src/share/classes/java/util/Observable.java) 以遺棄,原因是它並 **不支援序列化** (沒實作 `Serializable` 介面),**創建時線程(執行緒)不安全** * **`Observer` 類**:JDK 抽象觀察者取名 **Observer** > ![](https://i.imgur.com/gmykTYc.png) * **`Subject` 類**: 被觀察者 JDK 取名為 **Observable** (可被觀察) 1. 在多線程操作同一個對象時 `addObserver` & `deleteObserver` 是安全的 > ![](https://i.imgur.com/I2tNlHr.png) 2. **通知時必須改變 `changed` 狀態**,相關方法 `setChanged`、`clearChanged` 方法 ``` java= public class Observable { private boolean changed = false; private Vector obs; ... public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } ... protected synchronized void setChanged() { changed = true; } ... protected synchronized void clearChanged() { changed = false; } } ``` ### 實作 JDK 觀察者 :::info 做跟上面一樣的例子,不過我們用 JDK 提供的類別 ::: 1. **ConcreteObserver**:實作 `Observer` 接口,定義 Person 接收到更新後的行為 ```java= public class Person implements Observer { private final String name; public Person(String name) { this.name = name; } @Override public void update(Observable o, Object arg) { System.out.println("Name : " + name + ", NewsPaper: " + arg); } } ``` 2. **ConcreteSubject**:繼承於 `Observable` 抽象類,定義報社對於價格更新的打折細節 ```java= // 蘋果日報 public class AppleNews extends Observable { private int price; public AppleNews(int price) { this.price = price; } public void changePrice(int newPrice) { if(price == newPrice) { return; } price = newPrice - (newPrice / 10); setChanged(); notifyObservers(newPrice - (newPrice / 10)); } } // -------------------------------------------------------- // 自由時報 public class FreedomNews extends Observable { private int price; public FreedomNews(int price) { this.price = price; } public void changePrice(int newPrice) { if(price == newPrice) { return; } price = newPrice - 10; setChanged(); notifyObservers(newPrice - 10); } } ``` **--實作--** > ![](https://i.imgur.com/UkXLJZ8.png) ## Android Source 我們來看看 Android 最常使用的列表 ListView,它是如何更新元件。以下是 ListView 的基本使用方式 ```java= public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ListView listView = findViewById(R.id.list_view); listView.setAdapter(); } public final class MyAdapter extends BaseAdapter { private List<String> info = new ArrayList<>(); public void addData(String title) { info.add(title); // 主要分析 notifyDataSetChanged(); } @Override public int getCount() { return info.size(); } @Override public Object getItem(int position) { return info.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = getLayoutInflater(); MyViewHolder viewHolder; if(convertView == null) { viewHolder = new MyViewHolder(); convertView = inflater.inflate(R.layout.list_item, parent, false); convertView.setTag(viewHolder); } else { viewHolder = (MyViewHolder) convertView.getTag(); } viewHolder.textView.setText(info.get(position)); return convertView; } } private static final class MyViewHolder { private TextView textView; } } ``` ### ListView - [BaseAdapter](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/widget/BaseAdapter.java) 聚合觀察者 * 這裡我們主要分析 BaseAdapter#`notifyDataSetChanged` 方法 1. **Subject**:一看就知道 BaseAdapter 會聚合所有觀察者,其中就包含了註冊、取消註冊、通知... 等等方法 ```java= // BaseAdapter.java public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { // 實際聚合觀察者的類別 private final DataSetObservable mDataSetObservable = new DataSetObservable(); // 註冊觀察者 public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } // 取消觀察者 public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } // 通知所有觀察者 public void notifyDataSetChanged() { // @ 追蹤 notifyChanged 方法 mDataSetObservable.notifyChanged(); } } ``` * 其實 BaseAdapter 是包裝了一個可觀察者,也就是 **DataSetObservable 類**,它才是通知的實作類 ```java= // DataSetObservable.java // 觀察者為 DataSetObserver public class DataSetObservable extends Observable<DataSetObserver> { public void notifyChanged() { // 列表同步鎖 synchronized(mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { // 真正通知 mObservers.get(i).onChanged(); } } } public void notifyInvalidated() { synchronized (mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onInvalidated(); } } } } ``` 2. **Observer**:DataSetObserver 為抽象觀察者,宣告所有觀察者必須實作的函數 ```java= // DataSetObserver.java public abstract class DataSetObserver { public void onChanged() { // Do nothing } public void onInvalidated() { // Do nothing } } ``` * BaseAdpater 聚合觀察者 UML 關係圖 > ![](https://i.imgur.com/1RJ7XNp.png) ### ListView 觀察者 * 上面已經有說明了 BaseAdapter 的一部分功能就是聚合所有觀察者,並執行通知,而 **ListView 註冊觀察者則是透過 ListView#`setAdapter` 方法** ```java= // ListView.java ListAdapter mAdapter; @Override public void setAdapter(ListAdapter adapter) { // 如果已經有 Adapter 則移除觀察者 if (mAdapter != null && mDataSetObserver != null) { // 解註冊 mAdapter.unregisterDataSetObserver(mDataSetObserver); } ... 省略部分 // 賦予 mAdapter if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } if (mAdapter != null) { ... // 創建觀察者 mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); ... 省略部分 } else { ... } // 刷新 ListView layout requestLayout(); } ``` * **ListView 真正的觀察者**:從上面我們可以看到 ListView 會創建觀察者 `AdapterDataSetObserver`,它就是該 ListView 觀察數據的類 (實現在 `AdapterView`) ```java= // AbsListView.java // 查看 AdapterDataSetObserver 類 class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver { @Override public void onChanged() { super.onChanged(); if (mFastScroll != null) { mFastScroll.onSectionsChanged(); } } @Override public void onInvalidated() { super.onInvalidated(); if (mFastScroll != null) { mFastScroll.onSectionsChanged(); } } } // -------------------------------------------------------------- // AdapterView.java class AdapterDataSetObserver extends DataSetObserver { private Parcelable mInstanceState = null; @Override public void onChanged() { mDataChanged = true; mOldItemCount = mItemCount; mItemCount = getAdapter().getCount(); // 檢查是否有舊數據 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null && mOldItemCount == 0 && mItemCount > 0) { // 恢復舊數據 AdapterView.this.onRestoreInstanceState(mInstanceState); mInstanceState = null; } else { rememberSyncState(); } checkFocus(); // 要求重新布局 requestLayout(); } ... 省略其他方法 } ``` > ![](https://i.imgur.com/7ibGJCz.png) ## 更多的物件導向設計 物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)! :::info * [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/) * [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/) * [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/) ::: ### 創建模式 - Creation Patterns * [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/) * **創建模式 - `Creation Patterns`**: 創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結 :::success * [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/) * [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/) * [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/) * [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/) * [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/) * [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/) * [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/) ::: ### 行為模式 - Behavioral Patterns * [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/) * **行為模式 - `Behavioral Patterns`**: 行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結 :::warning * [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/) * [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/) * [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/) * [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/) * [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/) * [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/) * [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/) * [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/) * [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/) * [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/) * [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/) ::: ### 結構模式 - Structural Patterns * [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/) * **結構模式 - `Structural Patterns`**: 結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結 :::danger * [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/) * [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/) * [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/) * [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/) * [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/) * [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Java 設計模式` `基礎進階`