一、觀察者模式的定義
定義對象間一對多的依賴關(guān)系葫盼,使得當(dāng)前對象改變了狀態(tài)贫导,則所有依賴于它的對象都會得到通知并自動更新蟆盹。
二逾滥、觀察者模式的使用場景
- 一個抽象模型有兩個方面,其中一個方面依賴于另一個方面讥巡;(比如:View依賴于Model的數(shù)據(jù)欢顷,當(dāng)數(shù)據(jù)發(fā)生改變時抬驴,更新View)
- 一個對象的改變將導(dǎo)致一個或多個其他對象也發(fā)生改變豌拙;(比如:同一個數(shù)據(jù)源對多個觀察對象)
- 需要在系統(tǒng)創(chuàng)建一個觸發(fā)鏈姆蘸。(比如A影響B(tài)芙委,B影響C)
三灌侣、UML結(jié)構(gòu)圖
- Subject:抽象主題侧啼,也就是被觀察者(Observable)痊乾,把所有的觀察者對象的引用保存在一個集合里。每個主題都可以有任意數(shù)量的觀察者蛾魄,抽象主題提供一個接口滴须,可以增加和刪除觀察者對象叽奥。
- ConcreteSubject:具體主題朝氓,該角色將有關(guān)狀態(tài)存入具體觀察者對象主届,在具體主題內(nèi)部狀態(tài)發(fā)生改變時岂膳,會給所有注冊過的觀察者發(fā)出通知notify。
- Observer:抽象觀察者涧偷,它定義了更新接口燎潮,使得在得到被觀察者都更改通知時更新自己确封。
- ConcreteObserver:具體觀察者爪喘,實現(xiàn)了抽象觀察者所定義的更新接口秉剑。
四侦鹏、實現(xiàn)1(自己寫Observer和Observable)
/*
* 抽象被觀察者略水,添加刪除通知觀察者渊涝。
*/
public abstract class Subject {
//保存注冊觀察者對象
private Vector<Observer> mObservers = new Vector<>();
//注冊觀察者對象
public void addObserver(Observer o) {
this.mObservers.add(o);
}
//注銷觀察者對象
public void removeObserver(Observer o) {
this.mObservers.remove(o);
}
//通知觀察者對象
public void notifyObservers() {
for (Observer o : this.mObservers) {
o.update();
}
}
}
/*
* 具體被觀察者
*/
public class ConcreteSubject extends Subject {
public void doSomething() {
System.out.println("doSomething");
this.notifyObservers();
}
}
/*
* 抽象觀察者
*/
public interface Observer {
void update();
}
/*
* 具體觀察者
*/
public class ConcreteObserver implements Observer {
@Override
public void update() {
System.out.println("收到消息");
}
}
/*
* 測試類
*/
public class Client {
public static void main(String[] args) {
ConcreteSubject cs = new ConcreteSubject();
Observer observer1 = new ConcreteObserver();
Observer observer2 = new ConcreteObserver();
cs.addObserver(observer1);
cs.addObserver(observer2);
cs.doSomething();
}
}
- 顯示結(jié)果:
doSomething
收到消息
收到消息
五、實現(xiàn)2(實際開發(fā)中一般是使用java.util包下面的Observer煤傍、Observable)
/*
* 觀察者
*/
class EmailUser implements Observer {
public String name;
public EmailUser(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("親" + name + "蚯姆," +
"你關(guān)注的欄目《" + arg + "》已經(jīng)更新,請及時查看疙驾!");
}
}
/*
*被觀察者
*/
class Emailcolumn extends Observable {
public void postNewColumn(String content) {
setChanged();
notifyObservers(content);
}
}
/*
* 測試代碼
*/
public class ObserverDemo {
public static void main(String[] args) {
Emailcolumn emailcolumn = new Emailcolumn();
EmailUser user1 = new EmailUser("user1");
EmailUser user2 = new EmailUser("user2");
emailcolumn.addObserver(user1);
emailcolumn.addObserver(user2);
emailcolumn.postNewColumn("今天最開心:雷佳音");
}
}
- 顯示結(jié)果:
親user2它碎,你關(guān)注的欄目《今天最開心:雷佳音》已經(jīng)更新扳肛,請及時查看挖息!
親user1兽肤,你關(guān)注的欄目《今天最開心:雷佳音》已經(jīng)更新资铡,請及時查看害驹!
六、觀察者模式在Android中的實際運用
- 回調(diào)函數(shù):一對一的關(guān)系
- ListView數(shù)據(jù)更新
- RxJava
- 廣播注冊
- EventBus
1.回調(diào)函數(shù)
定義:實現(xiàn)了抽象類/接口的實例宛官,實現(xiàn)了父類提供的抽象方法后葫松,將該方法交還給父類來處理。
public class Employee {
//定義回調(diào)接口的成員變量
private Callback mcallback;
//聲明回調(diào)接口
public interface Callback {
public abstract void work();
}
//設(shè)置回調(diào)接口對象成員變量
public void setCallback(Callback callback) {
this.mcallback = callback;
}
//調(diào)用回調(diào)接口對象中的方法
public void doWork() {
if(mcallback!=null){
mcallback.work();
}
}
}
public class Boss {
private Employee employee;
//為Employee設(shè)置回調(diào)函數(shù)底洗,在這里定義具體的回調(diào)方法
public void setCallback() {
employee.setCallback(new Employee.Callback() {
@Override
public void work() {
System.out.println("work");
}
});
}
}
再來看另一個回調(diào)的例子:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// do something
}
});
OnClickListener作為觀察者腋么,button作為被觀察者,通過setOnClickListener來形成訂閱關(guān)系亥揖。當(dāng)button被點擊時相當(dāng)于被觀察者數(shù)據(jù)發(fā)生改變珊擂,就會去通知觀察者OnClickListener,這時觸發(fā)onClick(回調(diào)onClick)费变。
- 面試題:回調(diào)函數(shù)和觀察者模式的區(qū)別董虱?
- 觀察者模式:定義了一種一對多的依賴關(guān)系键兜,讓多個觀察者對象同時監(jiān)聽某一個主題對象隔躲。觀察者模式完美的將觀察者和被觀察的對象分離開,一個對象的狀態(tài)發(fā)生變化時用含,所有依賴于它的對象都得到通知并自動刷新。
- 回調(diào)函數(shù):其實也算是一種觀察者模式的實現(xiàn)方式缸夹,回調(diào)函數(shù)實現(xiàn)的觀察者和被觀察者往往是一對一的依賴關(guān)系。
- 所以最明顯的區(qū)別是觀察者模式是一種設(shè)計思路猫态,而回調(diào)函數(shù)式一種具體的實現(xiàn)方式勇凭;另一明顯區(qū)別是一對多還是多對多的依賴關(guān)系方面灌砖。
相關(guān)閱讀:Android面試一天一題(Day 43:設(shè)計模式)
2.ListView的notifyDataSetChanged
當(dāng) ListView 的數(shù)據(jù)發(fā)生變化時,調(diào)用 Adapter 的 notifyDataSetChanged 函數(shù)库继,這個函數(shù)又會調(diào)用 DataSetObservable 的 notifyChanged 函數(shù),這個函數(shù)會調(diào)用所有觀察者 (AdapterDataSetObserver) 的 onChanged 方法,在 onChanged 函數(shù)中又會調(diào)用 ListView 重新布局的函數(shù) requestLayout()使得 ListView 刷新界面撒妈。
七、觀察者模式的缺點
- 開發(fā)效率問題棋蚌。一個被觀察者,多個觀察者,開發(fā)和調(diào)試就會比較復(fù)雜。
- 運行效率問題班利。多個觀察者、多級觸發(fā)等情況下闯割,因為在Java默認(rèn)是順序執(zhí)行,所以一個觀察者卡殼,會影響整體的執(zhí)行效率,這時候推薦考慮采用異步的方式缎谷。
八、觀察者模式的優(yōu)點
- 觀察者和被觀察者之間是抽象耦合者甲,觀察者和被觀察者的擴展比較方便刽辙。
- 建立一套觸發(fā)機制。例如建立一條觸發(fā)鏈。
九、總結(jié)
觀察者模式主要的作用就是對象解耦,將觀察者與被觀察者完全隔離际歼,只依賴與 Observer 和 Obserable 抽象旭愧。例如LisetView就是運用了Adapter和觀察者模式使得它的可擴展性桃熄、靈活性非常強碉京,而耦合的很低,這些設(shè)計模式在Android源碼中優(yōu)秀運用的典范咽瓷。
- 那么如何達(dá)到低耦合月匣、高靈活性的代碼?
- 廣播接收器和時間總線在觀察者模式上有什么相似和不相同的地方头遭?
-
RxJava
是怎么運用觀察者模式的鲫惶?
十艺蝴、參考書籍
- 《Android 源碼設(shè)計模式解析與實戰(zhàn)》
- 小楠總的設(shè)計模式之旅14