0.文章
微信
观察者模式很好理解,我通过微信公众号这个简单的栗子来代入一下。
微信公众号大家都很熟悉,当你关注了它,它会在服务器有更新的时候推送信息给你,当然其他人没订阅的情况下是不会收到推送的。
这个栗子又像订报纸一样,在过去没有什么媒体的时候,大家都通过订阅报纸或者买报纸来了解新鲜事,当然只有你订阅了报社才会发报纸给你。这就是典型的 主题-订阅模式,也就是我们今天要讨论的观察者模式。
1.观察者模式定义
定啥义啊,写完了也晦涩难懂,直接理解定报纸和微信公众号,别跟我说你没玩过微信,也没订过报纸……那还是先定一波报纸研究研究吧
发布-订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。简单不?
2.结构图
对于我个人来说看类UML图其实是看不懂的,我是指在作为小白的情况下是不应该投入太多时间研究这个比较抽象的类图的,而是应直接结合栗子来研究代码(当然这是我自己的看法,大家完全可以按照自己的思维来)
UML图片来自百度百科
当然在这里我也不多解释这个图,我们一会直接看代码
3.角色
结合我们前面说的栗子,主要角色就是主题(发布者)和订阅者
主题:
抽象主题(接口)
具体主题实现(公开类)
订阅者:
订阅者抽象(接口)
订阅者实现(公开类)
角色也分的很清晰。
4.Demo
这次我们来写微信公众号的栗子,先给微信的栗子,然后最后给一个标准的代码。
按照我们的角色部分所描述的。
我们先实现抽象主题。
package ObserverPattern.Wechat;
/**
* 抽象观察者
* */
public interface SubjectInterface {
//关注动作
void registerAccount();
//取消关注
void removeAccount();
//通知对象更新
void notifyObject();
}
这个抽象里面一共有三个方法,关注取关和通知更新
下面我们实现一个公众号。
package ObserverPattern.Wechat;
import java.util.ArrayList;
/**
* 实现一个名字叫javaclass的公众号
* */
public class JavaClass implements SubjectInterface{
private ArrayList list;
public JavaClass() {
//初始化关注者管理器
list = new ArrayList();
}
@Override
public void registerAccount() {
}
@Override
public void removeAccount() {
}
@Override
public void notifyObject() {
}
}
可以发现我们现在的JavaClass只是填上了接口内的方法,但是没写内容,这是因为我们要先实现关注者抽象。
下面看我们的关注者抽象。
/**
* 微信用户抽象
* */
public interface WeChatUser {
void update();
}
关注者当然是微信用户,而他的抽象仅仅添加了一个update方法,用户公众号通知用户。
现在我们开始修改我们的公众号抽象,建立与微信用户的联系。
public interface SubjectInterface {
//关注动作
void registerAccount(WeChatUser weChatUser);
//取消关注
void removeAccount(WeChatUser weChatUser);
//通知对象更新
void notifyObject();
}
然后修改相应的公众号实现
package ObserverPattern.Wechat;
import ObserverPattern.Normal.MyObserver;
import java.util.ArrayList;
/**
* 实现一个名字叫javaclass的公众号
* */
public class JavaClass implements SubjectInterface{
private ArrayList list;
public JavaClass() {
//初始化关注者管理器
list = new ArrayList();
}
@Override
public void registerAccount(WeChatUser weChatUser) {
list.add(weChatUser);
}
@Override
public void removeAccount(WeChatUser weChatUser) {
//这里应该添加更复杂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列表来管理订阅者,然后有信息的时候需要调用notifyObject()通知用户更新,暂时先不需要知道怎么调用的,我们一会梳理,我们可以知道 weChatUser.update();是用于更新公众号通知的,然后实际是公众号把消息发送给用户的,所以我们最好修改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);
}
当然在具体的公众号实现中,我们也要改一下。
@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传送了。
细心的你一定发现了一个问题,我们这里直接调用了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;
}
}
很简单,在实现实体的基础上重写了update方法,在里面打印了信息内容。
我们开始测试,编写main方法
package ObserverPattern.Wechat;
public class Main {
public static void main(String[] args) {
//生成用户
WeChatUser weChatUser = new WechatUsers("123456");
//生成公众号
JavaClass javaclass = new JavaClass();
//关注公众号
javaclass.registerAccount(weChatUser);
//公众号发消息
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,"经济"));
}
}
看看结果
大家会发现,只有在订阅了之后,并且公众号有新消息的时候,相应公众号才会收到消息,当取消关注的时候,将收不到消息。
具体流程:
//1.关注公众号
javaclass.registerAccount(weChatUser);
//2.公众号发消息
javaclass.setMessage(new Message(1,"新闻"));
//3.设置信息,调用通知更新方法
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());
}
这样我们就实现微信公众号的关注功能,当然真正微信不是这么实现的,哈哈哈,就不要想了学会这个就可以搞一个微信公众号了,这是我们用来举例子的。
5.再次理解观察者模式
1. 发布者和订阅者,修改其中任何一部分,另一部分不会受到影响,这是我们平时所说的松耦合。
这个我们通过代码也能看出来,两个代码是没有什么关系的。
2.JDK中也有自带的观察者模式。但是被观察者是一个类而不是接口,限制了它的复用能力。
这里我们就不介绍它了,因为平时实现一个观察者模式并不是很难。
3.对于观察者模式,还是拿公众号的栗子理解最合适不过,结合实际场景,公众号挨个通知用户的话会出现很多问题,所以直接一下子将消息推送出去(虽然也是通过for循环来单个通知的,但是意义已经不同了。)
最主要的是,公众号和用户应该是独立的两部分(但是矛盾的是,由于主题抽象依赖了订阅者抽象,所以耦合还是存在的)
6.Android中的观察者模式
记得在上一篇的策略模式中我们提到了listview的adapter,其中我们通过查看源代码发现adapter有使用策略者模式,今天我们依然研究adapter
打开adapter的源代码(AS中,鼠标点击方法名,按Ctrl键可以快速转到定义)
很直白有木有,已经写出observer来了,而且registerDataSetObserver和unregisterDataSetObserver我们都见过,可以看到adapter是个interface,同时在adapter的源码中我们找不到notify方法,那么他在哪呢?
我们找到了BaseAdapter,这个是abstract类,实现了ListAdapter接口,那么ListAdapter接口是抽象自adapter接口的,我们不研究他,就可以间接认为BaseAdapter抽象自adapter接口就行,在这里面,我们看到了notify方法。
DataSetObservable里面管理着所有的订阅者。
这个就是我们的主题,那么订阅者怎么接收消息呢?
联系实际,我们平时是用setAdapter方法来设置adapter的
new ListView(this).setAdapter(baseAdapter);
那我们看一下setAdapter源码
其中有一句,注册代码,那么我们就可以知道了,只要setAdapter被调用了,那么就代表订阅者已经订阅了主题。
当主题调用notify的时候,(也就是我们平时adapter.notifyDataChanged()的时候),然后消息就会被通知到订阅者
主题调用顺序:
A.调用notifyDataSetChanged
baseAdapter.notifyDataSetChanged();
B.调用notifyChanged
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
C.调用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
}
}
发现没有实现。然后我们继续看订阅者。
订阅者调用顺序(视图更新部分)
首先我们要明白Listview是作为订阅者的
A.Listview抽象自AbsListView
public class ListView extends AbsListView {}
B.AbsListView 继承自AdapterView
public abstract class AbsListView extends AdapterView<ListAdapter>
C.AdapterView内部类AdapterDataSetObserver 继承自DataSetObserver ,并且覆盖了onChanged()方法
class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
}
}
到此,我们会发现,adapter和listview的发布订阅模式连接起来了。
7.其他发布订阅框架
Eventbus:
作为Anroid事件总线框架,eventbus采用发布订阅模式来处理信息交互,很符合OO特点。
Mqtt:
mqtt是即时通讯协议,他的工作模式就是发布订阅模式。
网友评论