设计模式之观察者模式

论坛每日精选项目

小明的公司又接到了新的项目,这次的项目是一个论坛管理员给他们的论坛做的,论坛每天会选出优秀的文章发给读者,读者需要订阅这个推送,小明不太明白从何入手,于是请教了他的主管……

然后小明的主管很快设计出了两套程序让小明选择,

程序A:
程序A

程序B:
程序A

程序A和B都是通过注册的方式将观察者关联到可观察者中,当有新的文章时,在notifyObservers()方法中调用所有Observer的update()方法。主管问小明我们应该选择哪一个呢?小明仔细分析了A和B,然后说选择B。主管说,A中我用的JDK中的可观察者类(java.util.Observable),为什么不选择A呢,还要自己定一个接口去做和Observable做差不多的事呢?小明说,上个项目用到策略模式时,有一个原则:针对接口编程,如果我们用继承的方式,会有很多问题:

1)如果ArticleData还需要干其他事情,因为Java不能多继承,所以会陷入两难

2)建立自己的实现时,破坏对扩展开发,对修改关闭的原则。因为Observable中已经对注册、删除、更新做了自己的实现

3)直接使用Observable时,很大的情况下Observable无法满足我们的需求

主管补充了一条:

4)JDK中的Observable在调用notifyObservers()时,依赖与setChanged()中的changed(boolean),可能不是我们需要的

还有一个需要注意的地方:注意Observable中的存放Observer的Vector是倒序遍历的,所以要注意调用Observer update()方法的顺序和我们注册时的顺序是相反的。另外一个值得我们关注的地方,对于更新,我们可以有两种方式:被观察者主动和观察者自己取。JDK中Observable的notifyObservers(Object)和notifyObservers()体现了这两种不同的方式。

定义

在对象之间建立一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会受到通知,并自动更新。

优缺点

优点:

  1. 观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次
  2. 观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知

缺点:

  1. 如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
  2. 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点
  3. 如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的
  4. 虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的

观察者模式中体现的原则

找出变化的部分,和不变的地方分离

改变的是关注者的数量和类型,所以观察者被分离出来了

针对接口编程,不针对实现编程

被观察者和观察者都使用接口,观察者利用被观察者接口注册、取消注册,被观察者利用观察者的更新接口通知观察者。这样让两者之间独立运作,具有松耦合的优点

多用组合,少用继承

被观察者和观察者是通过关联在一起,而不是通过继承的方式

观察者模式的运用

如在Java中JButton的监听事件注册;另外在Android中的控件适配器Adapter这一功能,我们的控件(ListView、RecycleView、GridView等)里面都有一个观察者实现类通过继承AdapterDataSetObserver实现了对变化的观察,在setAdapter()时,将该实现了对象注册到Adapter中。看一看源码一下就清楚了:

BaseAdapter.java

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    /……/

    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();
    }

    /**
     * Notifies the attached observers that the underlying data is no longer valid
     * or available. Once invoked this adapter is no longer valid and should
     * not report further data set changes.
     */
    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }

    /……/

}

AbsListView.java

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
    ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
    ViewTreeObserver.OnTouchModeChangeListener,
    RemoteViewsAdapter.RemoteAdapterConnectionCallback {

    AdapterDataSetObserver mDataSetObserver;

    /……/

     class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }

        @Override
        public void onInvalidated() {
            super.onInvalidated();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }
    }

    /……/
}

ListView.java

public class ListView extends AbsListView {

     /……/

    @Override
    public void setAdapter(ListAdapter adapter) {
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        resetList();
        mRecycler.clear();

        if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }

        mOldSelectedPosition = INVALID_POSITION;
        mOldSelectedRowId = INVALID_ROW_ID;

        // AbsListView#setAdapter will update choice mode states.
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();
            mAdapter.registerDataSetObserver(mDataSetObserver);

            mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());

            int position;
            if (mStackFromBottom) {
                position = lookForSelectablePosition(mItemCount - 1, false);
            } else {
                position = lookForSelectablePosition(0, true);
            }
            setSelectedPositionInt(position);
            setNextSelectedPositionInt(position);

            if (mItemCount == 0) {
                // Nothing selected
                checkSelectionChanged();
            }
        } else {
            mAreAllItemsSelectable = true;
            checkFocus();
            // Nothing selected
            checkSelectionChanged();
        }

        requestLayout();
    }

    /……/

}

代码很清晰,在AbsListView中实现了一个观察者内部类,并在setAdapter()时,实例化了这个实现类,将它注册到了Adapter中,当Adapter中数据有更新时,就会调用观察者的更新方法,也就是AbsListView中的那个观察者中的onChanged方法。一般情况下,Adapter就只有一个观察者,我们可以通过调用Adapter中的registerDataSetObserver(DataSetObserver)方法增加观察者,可以观察Adapter的数据变化。大家可以想一想他和我们上面的实现有什么区别,然后为什么是这样的呢?思考一下…………

被观察者通过实现接口,而观察者是通过继承然后和AbsListView组合的方式构成的。为什么AbsListView不直接是观察者呢?就是我们上面说的,如果直接通过AbsListView继承Observable,那么AbsListView将不能再继承自View,这也是JDK种我们Observable不是一个接口会出现的问题,我们需要换一种方式去表示我们的被观察者,自己定义一个接口或者和JDK的Observable实现类组合使用。

坚持原创分享,您的支持将鼓励我不断前行!