娜样美的观察者模式

来源:互联网 发布:网络机顶盒H3芯片 编辑:程序博客网 时间:2024/05/29 16:35

观察者模式,是一种异常美丽的设计模式。本文包括定义、类图以及应用实例,例子采用 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 方法所取代 。

4. 更多资料

  • 《安卓 DataBinding 使用经验总结(姐姐篇)》
  • 《安卓 DataBinding 使用经验总结(妹妹篇)》
2 0