娜样美的观察者模式

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值