Overview of Content
如有引用參考請詳註出處,感謝 😀
本文研究Observer設計模式,Observer模式在UML中的具體呈現,以及深入挖掘其設計所帶來的優勢和限制。我們將探討Observer模式的實際應用,並詳細介紹標準的Observer實現方式。
特別值得一提的是,我們將解析 JDK 內建的觀察者模式,深入探討如何實作並發揮其功能。
同時,我們將聚焦於Android源碼,分析 Observer 模式在 Android 開發中的實際應用情境,特別是在 ListView 的 BaseAdapter 中,以及 ListView 本身作為觀察者的角色。透過這些實例,讀者將更深入理解 Observer 設計模式的實際運用與優越性。
Observer 使用場景
● 當一個狀態或是變數改變時,需要通知所有擁有它這個變數的物件 (就像是有多人訂閱 "Hello" 日報,如果日報訂閱價格改變,就必須通知所有已訂閱的用戶)
● 定義一個可觀察物件,讓可觀察物件與其他物件產生 一對多的依賴關係,並在刷新時通知其他物件
關聯關係是 可拆分 的,並非組合關係
Observer UML
● Observer 角色介紹
角色 | 功能說明 |
---|---|
Observer | 觀察者抽象,定義一個更新方法 |
ConcreteObserver | 實作一個更新方法的細節 |
Subject | 聚合 Observer ,並宣告抽象通知方法 |
ConcreteSubject | 實作抽象通知方法 |
● 建議在被觀察者(
Subject
)要通知前,就做出是否通知的判斷,不要延到觀察者(Observer
)判斷是否消費這個事件減少 Observer 的個別判斷邏輯
Observer 設計:優缺點
● Observer 設計優點 :
A. 觀察者與被觀察者,是抽象耦合,對應業務變化
B. 增強系統靈活性、可擴充、自由度
C. 可以建立一套觸發機制(鏈式調用)
● Observer 設計缺點 :
A. 考慮到執行效率問題,由於是 輪巡更新,所以當一個觀察者卡頓 (重量級) 會導致後方的資料更新也卡頓
可以採用異步通知的方式(這種通知方式也稱為
emit
)
B. 在 多線程的狀況下也要考慮同步問題
Observer 實現
Observer 標準
A. Observer
類:定義一個抽象的共同方法
public interface Observer {
void changePrice(int newPrize);
}
B. ConcreteObserver
類:Person 實做 Observer
類,並決定在接收到通知後的行為細節
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);
}
}
C. Subject
類:NewsSubject 的重點是 聚合抽象 Observer
,在定義一個抽象通知方法
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);
}
D. ConcreteSubject
類:這裡以報社為例,每一間報社對於改動新價格後的優惠不同,這個不同點會定義在抽想方法中
// 蘋果日報
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
) 定義細節
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);
}
}
● 目前這裡暫時不關心 Multi Thread 同步問題,如果需要使用 MultiThread,則需要加上
sychronized
關鍵字同步
JDK 內建觀察者:介紹
JDK Source Code 以遺棄,原因是它並 不支援序列化 (沒實作 Serializable
介面),創建時線程(執行緒)不安全
● Observer
類:JDK 抽象觀察者取名 Observer
● Subject
類: 被觀察者 JDK 取名為 Observable (可被觀察)
A. 在多線程操作同一個對象時 addObserver
& deleteObserver
是安全的
B. 通知時必須改變 changed
狀態,相關方法 setChanged
、clearChanged
方法
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 觀察者
做跟上面一樣的例子,不過我們用 JDK 提供的類別
A. ConcreteObserver:實作 Observer
接口,定義 Person 接收到更新後的行為
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);
}
}
B. ConcreteSubject:繼承於 Observable
抽象類,定義報社對於價格更新的打折細節
// 蘋果日報
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 Listview
我們來看看 Android 最常使用的列表 ListView,它是如何更新元件。以下是 ListView 的基本使用方式
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 聚合觀察者
● 這裡我們主要分析 BaseAdapter#notifyDataSetChanged
方法
A. Subject:一看就知道 BaseAdapter 會聚合所有觀察者,其中就包含了註冊、取消註冊、通知... 等等方法
// 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 類,它才是通知的實作類
// 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();
}
}
}
}
B. Observer:DataSetObserver 為抽象觀察者,宣告所有觀察者必須實作的函數
// DataSetObserver.java
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
● BaseAdpater 聚合觀察者 UML 關係圖
ListView 觀察者
● 上面已經有說明了 BaseAdapter 的一部分功能就是聚合所有觀察者,並執行通知,而 ListView 註冊觀察者則是透過 ListView#setAdapter
方法
// 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
)
// 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();
}
... 省略其他方法
}
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式:Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式:Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式:Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計