觀察者模式
1. 概述
有時被稱作發佈/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
2. 解決的問題
將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關對象間的一致性。我們不希望爲了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。觀察者就是解決這類的耦合關係的。
3. 模式中的角色
3.1 抽象主題(Subject):
它把所有觀察者對象的引用保存到一個聚集裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
3.2 具體主題(ConcreteSubject):
將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
3.3 抽象觀察者(Observer):
爲所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
3.4 具體觀察者(ConcreteObserver):
實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題狀態協調。
4. 模式解讀
4.1 觀察者模式的類圖
4.2觀察者模式的代碼
抽象主題角色類
public abstract class Subject {
/**
* 用來保存註冊的觀察者對象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 註冊觀察者對象
* @param observer 觀察者對象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 刪除觀察者對象
* @param observer 觀察者對象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有註冊的觀察者對象
*/
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}
具體主題角色類
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主題狀態爲:" + state);
//狀態發生改變,通知各個觀察者
this.nodifyObservers(state);
}
}
抽象觀察者角色類
public interface Observer {
/**
* 更新接口
* @param state 更新的狀態
*/
public void update(String state);
}
具體觀察者角色類
public class ConcreteObserver implements Observer {
//觀察者的狀態
private String observerState;
@Override
public void update(String state) {
/**
* 更新觀察者的狀態,使其與目標的狀態保持一致
*/
observerState = state;
System.out.println("狀態爲:"+observerState);
}
}
客戶端類
public class Client {
public static void main(String[] args) {
//創建主題對象
ConcreteSubject subject = new ConcreteSubject();
//創建觀察者對象
Observer observer = new ConcreteObserver();
//將觀察者對象登記到主題對象上
subject.attach(observer);
//改變主題對象的狀態
subject.change("new state");
}
}
應用
在Android中RecyclerView和ListView對頁面刷新notifyDataSetChanged
過程中使用的就是觀察者模式
這是RecyclerView的notifyDataSetChanged源代碼,Observer就是抽象觀察者:
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
下一步再看看notifyChanged(),這是一個具體觀察者角色類: static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
return !mObservers.isEmpty();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
// since onItemRangeInserted() is implemented by the app, it could do anything,
// including removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
// since onItemRangeRemoved() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
public void notifyItemMoved(int fromPosition, int toPosition) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1);
}
}
}
在往上看,protected final ArrayList<T> mObservers = new ArrayList<T>(); ,notifyChanged遍歷了mObservers
5. 模式總結
5.1 優點
5.1.1
觀察者模式解除了主題和具體觀察者的耦合,讓耦合的雙方都依賴於抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。
5.2 缺點
5.2.1
依賴關係並未完全解除,抽象通知者依舊依賴抽象的觀察者。
5.3 適用場景
5.3.1
當一個對象的改變需要給變其它對象時,而且它不知道具體有多少個對象有待改變時。
5.3.2
一個抽象某型有兩個方面,當其中一個方面依賴於另一個方面,這時用觀察者模式可以將這兩者封裝在獨立的對象中使它們各自獨立地改變和複用。