---
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 的個別判斷邏輯
:::
> 
### 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);
}
}
```
> 
:::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**
> 
* **`Subject` 類**: 被觀察者 JDK 取名為 **Observable** (可被觀察)
1. 在多線程操作同一個對象時 `addObserver` & `deleteObserver` 是安全的
> 
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);
}
}
```
**--實作--**
> 
## 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 關係圖
> 
### 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();
}
... 省略其他方法
}
```
> 
## 更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
:::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 設計模式` `基礎進階`