浅谈观察者模式

Posted by Don on October 10, 2019

定义

观察者模式又称为发布————订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式或源-监听器(Source-Listener),是一种对象行为型模式。它定义了对象之间的一种一对多的依赖关系,使得每当一个对象状态发生改变时其相关依赖对象都得到通知并被自动更新。

观察者模式结构

观察者模式结构中通常包括观察目标和观察者2个继承层次结构,如下图所示:

观察者模式共包含4个角色:
1.Subject(目标):目标又称做主题,它是指被观察的对象。目标中定义了一个观察者集合,用于存储任意数量的观察者,而且提供了一系列方法用来添加(addach)和删除(detach)观察者对象,同时也定义了通知方法(notify),当自身状态发生变化后会使用此方法通知对应的观察者。目标类可以是接口、抽象类或具体类。 举个例子来说明设计抽象目标类的必要性:在多人联机对战游戏中,当战队的某一成员遭受攻击时给其他盟友发送通知,盟友收到通知后将做出响应。如果此系统设计时不引入抽象目标类的话那么联盟的每个成员都需要持有其他所有盟友的对象,这将导致系统开销较大。

2.ConcreteSubject(具体目标):具体目标是目标类的子类,通常包含被观察的属性数据,当它的状态发生改变时,将向他的各个观察者发出通知。如果无需扩展目标类 ,具体目标类可以省略。

3.Observer(观察者): 观察者将对观察目标状态的改变做出反应。观察者一般定义为接口,该接口中声明了更新数据的的方法notify(),因此又称为抽象观察者。

4.ConcreteObserver: 具体观察者实现抽象观察者所要求的更新接口,以便响应目标状态的变更。

观察者模式的代码实现

抽象目标类Subject源码如下:

public abstract class Subject {
    //观察者集合
    List<IObserver> observerList = new ArrayList<>();

    //向观察者集合中添加观察者
    public abstract void attach(IObserver observer);

    //删除观察者集合中的观察者
    public abstract void detach(IObserver observer);

    //通知观察者状态变更
    public abstract void notifyObserver();
}

具体目标类ConcreteSubject源码如下:

public class ConcreteSuject extends Subject {
    @Override
    public void attach(IObserver observer) {
        observerList.add(observer);
    }

    @Override
    public void detach(IObserver observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObserver() {
        //遍历观察者集合,调用每个观察者的响应方法
        for (IObserver obs : observerList) {
            obs.update();
        }
    }
}

抽象观察者Observer源码如下:

public interface IObserver {
    void update();
}

具体观察者源码如下:

public class ConcreteObserver implements IObserver {

    @Override
    public void update() {
        //todo 实现响应代码
        System.out.println("ConcreteObserver响应变更");
    }
}

客户端源码如下:

 @Test
    public void observerPatternTest() {
        //创建目标对象
        Subject subject = new ConcreteSuject();
        //创建观察者对象
        IObserver observer = new ConcreteObserver();
        //将观察者对象在目标对象上登记
        subject.attach(observer);
        //目标状态变更后通知观察者
        subject.notifyObserver();
    }

输出结果为:

ConcreteObserver2响应变更

总结

使用场景

  1. 多级触发场景,比如A对象的行为应该B,B对象的行为影响C…..

  2. 跨系统的消息交换场景,如消息队列、事件总线的处理机制

优点:

  1. 观察者模式可以实现表示层和数据逻辑层的分离,比如MVC架构中也使用了观察者模式,Model和View既是观察目标也是观察者,就像上面的多人联机对战游戏(个人观点,若有问题还望指正),C充当两者间的中介,当Model层数据变化后,View层将自动改变其显示内容,当View层的状态发生变化后,Model层会更新数据

  2. 在观察者和观察目标之间建立了一个抽象的耦合,观察目标只需要维持一个抽象观察者集合,无需了解其具体观察者。在一定程度上提升了系统性能。

  3. 观察者模式支持广播通信(1对多),观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

  4. 符合开闭原则,增加新的具体观察者无需修改原有系统代码,在具体观察者和观察目标不存在关联关系的情况下增加新的观察目标也很方便。

缺点

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

Android中的观察者模式

观察者模式是一种使用频率非常高的设计模式,它为对象之间的联动提供了一套完整的解决方案。Android中使用此模式的地方非常多,比如控件的OnClick事件、RecyclerView界面的刷新机制以及Rxjava和事件总线EventBus等。我们以RecyclerView界面的刷新机制举例说明一下:

RecyclerView界面的刷新其实是因为我们调用了Adapter的notifyDataSetChange()方法,因此我们先看一下Recyclerview 中Adapter的部分源码:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
//...省略部分代码
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
	//创建具体观察目标对象
        private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
        
	.......

	
        public void registerAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
	//注册观察者
            this.mObservable.registerObserver(observer);
        }

	
        public void unregisterAdapterDataObserver(@NonNull RecyclerView.AdapterDataObserver observer) {
	//删除观察者
            this.mObservable.unregisterObserver(observer);
        }

	
        public final void notifyDataSetChanged() {
	//数据变化后,调用观察目标的notifyChange方法,通知观察者
            this.mObservable.notifyChanged();
        }
    }
}

从源码中可以看出,Adapter类是观察者模式中的客户端类,它创建了一个具体观察目标(AdapterDataObservable),在这里我们可以调用观察目标的注册、删除观察者(AdapterDataObserver)的方法以及当数据变化后通知观察者的方法。这里我们可以看到,观察者(AdapterDataObserver)是以参数的形式传进来的,那么它是如何传进来的呢?首先我们先看一下观察者是何时注册的:
RecyclerView类部分源码:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
//具体观察者
private final RecyclerView.RecyclerViewDataObserver mObserver;
	//...省略部分代码
 public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
        this.setLayoutFrozen(false);
	//注意此方法的实现
        this.setAdapterInternal(adapter, false, true);
        this.processDataSetCompletelyChanged(false);
        this.requestLayout();
    }

    private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
        if (this.mAdapter != null) {
	    //清空观察者集合
            this.mAdapter.unregisterAdapterDataObserver(this.mObserver);
            this.mAdapter.onDetachedFromRecyclerView(this);
        }

        if (!compatibleWithPrevious || removeAndRecycleViews) {
            this.removeAndRecycleViews();
        }

        this.mAdapterHelper.reset();
        RecyclerView.Adapter oldAdapter = this.mAdapter;
        this.mAdapter = adapter;
        if (adapter != null) {
	    //观察者注册
            adapter.registerAdapterDataObserver(this.mObserver);
            adapter.onAttachedToRecyclerView(this);
        }

        if (this.mLayout != null) {
            this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter);
        }

        this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious);
        this.mState.mStructureChanged = true;
    }
}

以上代码可以看出,当我们调用setAdapter()方法时会将观察者注册到观察目标上,那接下来我们看一下观察目标通知观察者的具体实现:

//具体观察目标
static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
       
        public void notifyChanged() {
	    //遍历观察者集合,调用每个观察者的响应方法
            for(int i = this.mObservers.size() - 1; i >= 0; --i) {
                ((RecyclerView.AdapterDataObserver)this.mObservers.get(i)).onChanged();
            }

        }
}

我们可以看到此类遍历了观察者集合,并调用观察者的onChanged()方法实现响应,但是此类并没有定义观察者集合,因此我们看一下它的父类Observable(抽象观察目标)的源码:

//抽象观察者
public abstract class Observable<T> {
    /**
     * The list of observers.  An observer can be in the list at most
     * once and will never be null.
     */
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    /**
     * Adds an observer to the list. The observer cannot be null and it must not already
     * be registered.
     * @param observer the observer to register
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is already registered
     */
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    /**
     * Removes a previously registered observer. The observer must not be null and it
     * must already have been registered.
     * @param observer the observer to unregister
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is not yet registered
     */
    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }

    /**
     * Remove all registered observers.
     */
    public void unregisterAll() {
        synchronized(mObservers) {
            mObservers.clear();
        }
    }
}

在此类中可以明显看到此类维护了观察者集合,并提供了注册、删除观察者等方法。
接下来我们再看一下观察者的具体响应方法:

//抽象观察者
public abstract static class AdapterDataObserver {
        public AdapterDataObserver() {
        }

        public void onChanged() {
        }

        public void onItemRangeChanged(int positionStart, int itemCount) {
        }

        public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
            this.onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
        }
    }

我们发现此类是abstract修饰的,因此它属于抽象观察者,那么我们看一下具体观察者RecyclerViewDataObserver的具体实现:

private class RecyclerViewDataObserver extends RecyclerView.AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        public void onChanged() {
            RecyclerView.this.assertNotInLayoutOrScroll((String)null);
            RecyclerView.this.mState.mStructureChanged = true;
            RecyclerView.this.processDataSetCompletelyChanged(true);
            if (!RecyclerView.this.mAdapterHelper.hasPendingUpdates()) {
	    //重新布局
                RecyclerView.this.requestLayout();
            }

        }
    }

我们可以看到onChanged()方法中调用了requestLayout()方法来重新进行布局,至此,RecyclerView的刷新机制已完成。

总结

刷新机制中各个角色:
AdapterDataObservable是具体观察目标(继承自抽象观察目标Observable),RecyclerViewDataObserver是具体观察者(继承自抽象观察者AdapterDataObserver),我们通过RecyclerView的setAdapter()方法将观察者注册到观察目标上。

刷新流程:
当我们调用notifyDataSetChanged()等刷新方法时其实就是调用了观察目标的notifyChanged()方法,而此方法会遍历所有观察者,并调用观察者的响应方法onChanged(),而onChanged()方法调用requestLayout()方法来实现界面的刷新。