0.文章
觀察者模式很好理解,我通過微信公眾號這個簡單的栗子來代入一下苇经。
微信公眾號大家都很熟悉赘理,當你關(guān)注了它,它會在服務(wù)器有更新的時候推送信息給你扇单,當然其他人沒訂閱的情況下是不會收到推送的商模。
這個栗子又像訂報紙一樣,在過去沒有什么媒體的時候蜘澜,大家都通過訂閱報紙或者買報紙來了解新鮮事施流,當然只有你訂閱了報社才會發(fā)報紙給你。這就是典型的 主題-訂閱模式鄙信,也就是我們今天要討論的觀察者模式瞪醋。
1.觀察者模式定義
定啥義啊,寫完了也晦澀難懂装诡,直接理解定報紙和微信公眾號银受,別跟我說你沒玩過微信践盼,也沒訂過報紙……那還是先定一波報紙研究研究吧
發(fā)布-訂閱模式,發(fā)布者發(fā)布信息蚓土,訂閱者獲取信息宏侍,訂閱了就能收到信息,沒訂閱就收不到信息蜀漆。簡單不?
2.結(jié)構(gòu)圖
對于我個人來說看類UML圖其實是看不懂的,我是指在作為小白的情況下是不應(yīng)該投入太多時間研究這個比較抽象的類圖的咱旱,而是應(yīng)直接結(jié)合栗子來研究代碼(當然這是我自己的看法确丢,大家完全可以按照自己的思維來)
當然在這里我也不多解釋這個圖,我們一會直接看代碼
3.角色
結(jié)合我們前面說的栗子吐限,主要角色就是主題(發(fā)布者)和訂閱者
主題:
抽象主題(接口)
具體主題實現(xiàn)(公開類)
訂閱者:
訂閱者抽象(接口)
訂閱者實現(xiàn)(公開類)
角色也分的很清晰鲜侥。
4.Demo
這次我們來寫微信公眾號的栗子,先給微信的栗子诸典,然后最后給一個標準的代碼描函。
按照我們的角色部分所描述的。
我們先實現(xiàn)抽象主題狐粱。
package ObserverPattern.Wechat;
/**
* 抽象觀察者
* */
public interface SubjectInterface {
//關(guān)注動作
void registerAccount();
//取消關(guān)注
void removeAccount();
//通知對象更新
void notifyObject();
}
這個抽象里面一共有三個方法舀寓,關(guān)注取關(guān)和通知更新
下面我們實現(xiàn)一個公眾號。
package ObserverPattern.Wechat;
import java.util.ArrayList;
/**
* 實現(xiàn)一個名字叫javaclass的公眾號
* */
public class JavaClass implements SubjectInterface{
private ArrayList list;
public JavaClass() {
//初始化關(guān)注者管理器
list = new ArrayList();
}
@Override
public void registerAccount() {
}
@Override
public void removeAccount() {
}
@Override
public void notifyObject() {
}
}
可以發(fā)現(xiàn)我們現(xiàn)在的JavaClass只是填上了接口內(nèi)的方法肌蜻,但是沒寫內(nèi)容互墓,這是因為我們要先實現(xiàn)關(guān)注者抽象。
下面看我們的關(guān)注者抽象蒋搜。
/**
* 微信用戶抽象
* */
public interface WeChatUser {
void update();
}
關(guān)注者當然是微信用戶篡撵,而他的抽象僅僅添加了一個update方法,用戶公眾號通知用戶豆挽。
現(xiàn)在我們開始修改我們的公眾號抽象育谬,建立與微信用戶的聯(lián)系。
public interface SubjectInterface {
//關(guān)注動作
void registerAccount(WeChatUser weChatUser);
//取消關(guān)注
void removeAccount(WeChatUser weChatUser);
//通知對象更新
void notifyObject();
}
然后修改相應(yīng)的公眾號實現(xiàn)
package ObserverPattern.Wechat;
import ObserverPattern.Normal.MyObserver;
import java.util.ArrayList;
/**
* 實現(xiàn)一個名字叫javaclass的公眾號
* */
public class JavaClass implements SubjectInterface{
private ArrayList list;
public JavaClass() {
//初始化關(guān)注者管理器
list = new ArrayList();
}
@Override
public void registerAccount(WeChatUser weChatUser) {
list.add(weChatUser);
}
@Override
public void removeAccount(WeChatUser weChatUser) {
//這里應(yīng)該添加更復(fù)雜null判斷帮哈,為了簡單清楚直接刪掉了膛檀。
list.remove(weChatUser);
}
@Override
public void notifyObject() {
for(int i = 0;i < list.size();i++){
WeChatUser weChatUser = (WeChatUser) list.get(i);
weChatUser.update();
}
}
}
通過list列表來管理訂閱者,然后有信息的時候需要調(diào)用notifyObject()通知用戶更新但汞,暫時先不需要知道怎么調(diào)用的宿刮,我們一會梳理,我們可以知道 weChatUser.update();是用于更新公眾號通知的私蕾,然后實際是公眾號把消息發(fā)送給用戶的僵缺,所以我們最好修改update()方法來傳送信息。
我們聲明一個信息實體踩叭。
package ObserverPattern.Wechat;
public class Message {
private int id;
private String message;
public Message(int id, String message) {
this.id = id;
this.message = message;
}
public Message() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
然后修改update抽象磕潮。
package ObserverPattern.Wechat;
/**
* 微信用戶抽象
* */
public interface WeChatUser {
void update(Message message);
}
當然在具體的公眾號實現(xiàn)中翠胰,我們也要改一下。
@Override
public void notifyObject() {
for(int i = 0;i < list.size();i++){
WeChatUser weChatUser = (WeChatUser) list.get(i);
weChatUser.update(message);
}
}
那么message在哪自脯?
private Message message;
public void setMessage(Message message) {
this.message = message;
notifyObject();
}
可以給公眾號增加一個message屬性之景,然后添加一個setter,
這樣信息就可以通過我們的update傳送了膏潮。
細心的你一定發(fā)現(xiàn)了一個問題锻狗,我們這里直接調(diào)用了notifyObject();方法,通知更新就可以生效焕参。
萬事俱備轻纪,只欠使用者。
package ObserverPattern.Wechat;
public class WechatUsers implements WeChatUser {
private String name;
@Override
public void update(Message message) {
System.out.println(name+"收到信息:"+message.getId()+":"+message.getMessage());
}
public WechatUsers() {
}
public WechatUsers(String name) {
this.name = name;
}
}
很簡單叠纷,在實現(xiàn)實體的基礎(chǔ)上重寫了update方法刻帚,在里面打印了信息內(nèi)容。
我們開始測試涩嚣,編寫main方法
package ObserverPattern.Wechat;
public class Main {
public static void main(String[] args) {
//生成用戶
WeChatUser weChatUser = new WechatUsers("123456");
//生成公眾號
JavaClass javaclass = new JavaClass();
//關(guān)注公眾號
javaclass.registerAccount(weChatUser);
//公眾號發(fā)消息
javaclass.setMessage(new Message(1,"新聞"));
System.out.println("--------");
WeChatUser weChatUser2 = new WechatUsers("小明");
javaclass.registerAccount(weChatUser2);
javaclass.setMessage(new Message(2,"娛樂"));
System.out.println("--------");
javaclass.removeAccount(weChatUser);
javaclass.setMessage(new Message(3,"經(jīng)濟"));
}
}
看看結(jié)果
大家會發(fā)現(xiàn)崇众,只有在訂閱了之后,并且公眾號有新消息的時候航厚,相應(yīng)公眾號才會收到消息顷歌,當取消關(guān)注的時候,將收不到消息阶淘。
具體流程:
//1.關(guān)注公眾號
javaclass.registerAccount(weChatUser);
//2.公眾號發(fā)消息
javaclass.setMessage(new Message(1,"新聞"));
//3.設(shè)置信息衙吩,調(diào)用通知更新方法
public void setMessage(Message message) {
this.message = message;
notifyObject();
}
//4.通知更新
@Override
public void notifyObject() {
for(int i = 0;i < list.size();i++){
WeChatUser weChatUser = (WeChatUser) list.get(i);
weChatUser.update(message);
}
}
//5.更新消息
@Override
public void update(Message message) {
System.out.println(name+"收到信息:"+message.getId()+":"+message.getMessage());
}
這樣我們就實現(xiàn)微信公眾號的關(guān)注功能,當然真正微信不是這么實現(xiàn)的溪窒,哈哈哈坤塞,就不要想了學(xué)會這個就可以搞一個微信公眾號了,這是我們用來舉例子的澈蚌。
5.再次理解觀察者模式
1. 發(fā)布者和訂閱者摹芙,修改其中任何一部分,另一部分不會受到影響宛瞄,這是我們平時所說的松耦合浮禾。
這個我們通過代碼也能看出來,兩個代碼是沒有什么關(guān)系的份汗。
2.JDK中也有自帶的觀察者模式盈电。但是被觀察者是一個類而不是接口,限制了它的復(fù)用能力杯活。
這里我們就不介紹它了匆帚,因為平時實現(xiàn)一個觀察者模式并不是很難。
3.對于觀察者模式旁钧,還是拿公眾號的栗子理解最合適不過吸重,結(jié)合實際場景互拾,公眾號挨個通知用戶的話會出現(xiàn)很多問題,所以直接一下子將消息推送出去(雖然也是通過for循環(huán)來單個通知的嚎幸,但是意義已經(jīng)不同了颜矿。)
最主要的是,公眾號和用戶應(yīng)該是獨立的兩部分(但是矛盾的是嫉晶,由于主題抽象依賴了訂閱者抽象骑疆,所以耦合還是存在的)
6.Android中的觀察者模式
記得在上一篇的策略模式中我們提到了listview的adapter,其中我們通過查看源代碼發(fā)現(xiàn)adapter有使用策略者模式车遂,今天我們依然研究adapter
打開adapter的源代碼(AS中封断,鼠標點擊方法名,按Ctrl鍵可以快速轉(zhuǎn)到定義)
很直白有木有舶担,已經(jīng)寫出observer來了,而且registerDataSetObserver和unregisterDataSetObserver我們都見過彬呻,可以看到adapter是個interface衣陶,同時在adapter的源碼中我們找不到notify方法,那么他在哪呢闸氮?
我們找到了BaseAdapter剪况,這個是abstract類,實現(xiàn)了ListAdapter接口蒲跨,那么ListAdapter接口是抽象自adapter接口的译断,我們不研究他,就可以間接認為BaseAdapter抽象自adapter接口就行或悲,在這里面孙咪,我們看到了notify方法。
DataSetObservable里面管理著所有的訂閱者巡语。
這個就是我們的主題翎蹈,那么訂閱者怎么接收消息呢?
聯(lián)系實際男公,我們平時是用setAdapter方法來設(shè)置adapter的
new ListView(this).setAdapter(baseAdapter);
那我們看一下setAdapter源碼
其中有一句荤堪,注冊代碼,那么我們就可以知道了枢赔,只要setAdapter被調(diào)用了澄阳,那么就代表訂閱者已經(jīng)訂閱了主題。
當主題調(diào)用notify的時候踏拜,(也就是我們平時adapter.notifyDataChanged()的時候),然后消息就會被通知到訂閱者
主題調(diào)用順序:
A.調(diào)用notifyDataSetChanged
baseAdapter.notifyDataSetChanged();
B.調(diào)用notifyChanged
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
C.調(diào)用onChanged碎赢,通知更新
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
D.看一下onChanged()
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
發(fā)現(xiàn)沒有實現(xiàn)。然后我們繼續(xù)看訂閱者执隧。
訂閱者調(diào)用順序(視圖更新部分)
首先我們要明白Listview是作為訂閱者的
A.Listview抽象自AbsListView
public class ListView extends AbsListView {}
B.AbsListView 繼承自AdapterView
public abstract class AbsListView extends AdapterView<ListAdapter>
C.AdapterView內(nèi)部類AdapterDataSetObserver 繼承自DataSetObserver 揩抡,并且覆蓋了onChanged()方法
class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
}
}
到此户侥,我們會發(fā)現(xiàn),adapter和listview的發(fā)布訂閱模式連接起來了峦嗤。
7.其他發(fā)布訂閱框架
Eventbus:
作為Anroid事件總線框架蕊唐,eventbus采用發(fā)布訂閱模式來處理信息交互,很符合OO特點烁设。
Mqtt:
mqtt是即時通訊協(xié)議替梨,他的工作模式就是發(fā)布訂閱模式。