观察者模式,是一种异常美丽的设计模式。本文包括定义、类图以及应用实例,例子采用 Java 实现。
1. 定义
观察者模式又称订阅者模式(可以将其看做报纸订阅服务,有两个主要角色,出版商和订阅者。假设出版商初版的杂志是周刊,那么一旦新杂志上架,出版商就将其邮寄给一个或多个订阅者)。
观察者模式在生活中每天都在上演。
快递小哥蹲在一家单位门口,被满地的包裹围攻,气定神闲。小哥咬了一口鸡蛋灌饼,对着手机说:“你好,你有快递到了……”
包裹被签收后,小哥将收件人从便签中划掉。
我们来简单分析一下这个场景。首先是人物:快递小哥一枚,收件人多只。那么,是什么将小哥和收件人联系起来的呢?是手机号,即小哥拥有所有收件人的手机号,以便通知收件人来领包裹——收件人事先在快递小哥那里注册了自己的信息(手机号)。
你站在桥上看风景
换言之,快递小哥是“主题”(Subject),收件人是观察者(Observer)。收件人向快递小哥注册自己的信息(实际上是在下订单时写的,然后由电商转给快递公司,再由快递公司分发给快递小哥,我们简化这一流程为收件人直接给快递小哥),然后等待快递小哥的通知,以便做出相应的行动。
言归正传,观察者模式由2部分组成:主题/Subject,观察者/Observer,Observer 在 Subject 中注册,然后等待 Subject 的通知。
《Head First 设计模式》下的定义:
观察者模式定义了对象之间的一对多依赖,当一个对象的状态发生改变时,它的所有依赖者都会收到通知并自动更新。
类图
2. 场景实现
我们用 Java 来实现拿快递这一场景。
2.1 Subject
我们将 Subject 定义为接口,接口中有3个行为:注册观察者,移除观察者,通知观察者。分别对应方法:registerObservers(),removeObservers(),removeObservers()。
快递小哥实现该接口,则具有并对外公开这3个行为,并按照自己的行为具体实现之,而具体实现内容则对外隐藏,更改这些方法的具体实现不影响其他对象的调用形式。
Subject.java
public interface Subject {
public void registerObservers(Observer o);
public void removeObservers(Observer o);
public void removeObservers();
}
public class ExpressMan implements Subject {
List<Observer> observerList;
public ExpressMan() {
this.observerList = new ArrayList<>(12);
}
@Override
public void registerObservers(Observer o) {
observerList.add(o);
}
@Override
public void removeObservers(Observer o) {
int i = observerList.indexOf(o);
if (i > 0) {
observerList.remove(i);
}
}
@Override
public void notifyObservers() {
for (Observer o : observerList) {
o.update();
}
}
}
2.2 Observer
Observer 接口只有1个行为:update()。观察者实现该接口,在该方法中实现自己的 update() 行为,如对于收件人 update() 则是暂停工作去取快递。
public interface Observer {
void update();
}
public class Recipient implements Observer{
Subject subject;
public Recipient(Subject _subject) {
this.subject = _subject;
subject.registerObservers(this);
}
@Override
public void update() {
// 暂停工作,去取快递
}
}
2.3 主函数
public class Main {
public static void main(String[] args) {
Subject Zhansan = new ExpressMan();
Observer Lisi = new Recipient(Zhansan);
Observer Wangwu = new Recipient(Zhansan);
Observer Chenliu = new Recipient(Zhansan);
Zhansan.notifyObservers();
}
}
3. 观察者模式的优势
之所以说观察者模式那样美,是因为这种模式实现了两组对象的松耦合,它们只知道对方实现了什么接口,具有什么的样行为,而对其中的实现细节并不了解。
我们再举个具体的例子来说明观察者模式是如何解耦的。下图是美团安卓 APP(v6.8.1) 的个人中心页,图中演示的是匿名评价功能。我们假设“匿名评价”这个 TextView 和“更多按钮”的 ImageButton(假设是个 ImageButton)分别属于两个组件 FeedItemViewModel 和 FeedCommentViewModel。
正常情况下,为了在 FeedCommentViewModel 中对 FeedItemViewModel 中的“匿名评价”这个 TextView 进行更新,必然会在 FeedCommentViewModel 中持有 FeedItemViewModel 的引用,这就造成了耦合。
如果我们想实现两个组件解耦,即任意一个组件都可以单独拿出去使用,我们可以借助 JDK 提供的 基类 Observable 和 接口 Observer。
FeedItemViewModel 是订阅者,实现 Observer 接口,重写 update(Observable observable, Object data) 方法,在方法体中实现更新“匿名评价”的操作;
FeedCommentViewModel 是发布者,继承 Observable,通过 addObserver(Observer o)
方法注册订阅者,通过 notifyObservers(Object data)
通知订阅者更新。
为了简单起见,我们使用 DataBinding 技术实现该功能,关于 DataBinding 更多的内容请参考《安卓 DataBinding 使用经验总结(姐姐篇)》。
代码中接口 BaseObservable 和 notifyOnPropertyChanged(BR.id)
是 DataBinding 相关的内容。由此可以看出 DataBinding 也是使用了观察者模式的。
实现效果如下:
代码如下(代码中我们使用了安卓提供的 DataBinding 技术):
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
FeedViewModel model = new FeedViewModel();
binding.setUser(model);
}
}
FeedItemViewModel.java
public class FeedViewModel extends BaseObservable implements Observer {
public @Bindable String firstName;
public FeedCommentViewModel mFeedCommentViewModel;
public FeedViewModel() {
firstName = "mmlovesyy";
mFeedCommentViewModel = new FeedCommentViewModel(this);
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(com.mmlovesyy.java_observable.BR.firstName);
}
@Override
public void update(Observable observable, Object data) {
setFirstName((String) data);
}
}
FeedCommentViewModel.java
public class FeedCommentViewModel extends Observable {
public FeedCommentViewModel(Observer observer) {
addObserver(observer);
}
public void onClick(View view) {
setChanged();
notifyObservers("mm***yy");
}
}
main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="user"
type="com.mmlovesyy.java_observable.FeedViewModel" />
</data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.mmlovesyy.java_observable.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{user.firstName}'
android:textColor="#ff0000" />
<include
layout="@layout/activity_comment"
bind:comment='@{user.mFeedCommentViewModel}' />
</LinearLayout>
</layout>
activity_comment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="comment"
type="com.mmlovesyy.java_observable.FeedCommentViewModel" />
</data>
<LinearLayout xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.mmlovesyy.java_observable.MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick='@{comment.onClick}'
android:text="匿名评价" />
</LinearLayout>
</layout>
开发中我们会用到 ItemView 和其对应的 ItemModel,如果让 ItemView 观察 ItemModel,那么 ItemModel 中的数据更新时,ItemView 不就自动更新了吗?看,我们实现了一个简化版的 Data Binding!
3. 应用实例
3.0 JDK 自带 Observer & Observable
在上一节匿名评价的例子中,我们使用了 JDK 自带的 java.util.Observer 和 java.util.Observable。
应当注意的是,Observable 是一个类,而非接口。由于 Java 的单继承性,只能继承一个父类,却可以实现多个接口,所以当某个类已经继承了父类,就不能再继承 Observable 类了,这是不太方便的地方。
同时,这个不太方便的地方告诉我们:如果可以,优先使用接口,而非基类。
3.1 BaseAdapter
使用安卓的 ListView 或 RecyclerView 的时候,BaseAdapter 是无法回避的。
BaseAdapter 里面也有一对观察者模式组合:DataSetObserver & DataSetObservable。并且提供了一系列的注册/注销/通知的方法:
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
如果 ListView / RecyclerView 内部有地方(如 headerView 或 footerView),或者 ListView / RecyclerView 外部有地方(如 ListView / RecyclerView 所在的 Fragment / Activity 内的某个 View),要监听 BaseAdapter 数据的变化,则可以通过注册 DataSetObserver 的方法来做到。
需要注意的是,这种方法只能监听数据的变化,而无法区分数据的增加或减少等细节。
3.2 BroadcastReceiver
3.3 RxJava
3.4 Data Binding
请参考我的另外两篇博客:《安卓 DataBinding 使用经验总结(姐姐篇)》 和 《安卓 DataBinding 使用经验总结(妹妹篇)》。
3.5 EventBus
3.6 OnClickListener
以 View.OnClickListener 为首的各种 Listener,也属于观察者模式。只不过是简化版的观察这模式,因为只有 1 个观察者,即 1 个 Observer。此时,register 方法被 setter 方法所取代 。