【面向对象】设计模式之观察者模式的学习

介绍

观察者模式是一种使用频率非常高的设计模式,最常用的地方就是订阅-发布系统。 这个模式的重要作用就是将观察者和被观察者解耦,使他们之间的依赖更小甚至没有。

观察者模式又被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

使用场景

  • 关联行为场景,这个关联是可拆分的。将观察者和被观察者封装在不同的对象中,可以各自独立的变化。
  • 当一个对象改变时,有其他对象要进行相应的变化,但是他并不知道有多少个对象需要变化。

角色介绍

img

  • 抽象主题(Subject):它把所有观察者对象的引用保存到一个集合,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,一般用抽象类与接口来实现。
  • 具体主题(ConcreteSubject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。一般实现了抽象接口。
  • 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己,通常以接口方式实现。
  • 具体观察者(ConcreteObserver):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调,一般实现抽象观察者实现。

一般Subject与Oberver是一对多的,也就是一个Subject可以注册很多个Observer。只需要Observer将自己注册到Subject中即可。

Java API中已经内置了观察者模式类,Observable类与Obeserver接口。分别对应了Subject以及Observer。在使用时需要注意的是,在调用notifyObeservers()方法之前一定要先调用setChanged方法,否则观察者无法接收到通知。

Java API中的观察者模式有一个很明显的缺点。由于Obervable是一个类,而Java又只允许单继承。导致你使用继承者模式又想要获取另一个父类的特性时,只能选择适配器模式或者内部类。这违背了设计模式的原则:多用组合,少用继承。

示例

这里,MyPerson类是一个被观察者,继承自Observable类:

public class MyPerson extends Observable {

    private String name;
    private int age;
    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        setChanged();
        notifyObservers();
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        setChanged();
        notifyObservers();
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
        setChanged();
        notifyObservers();
    }

    @Override
    public String toString() {
        return "MyPerson [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    }
}

而MyObserver是一个观察者,实现了Obeserver接口及其update方法:

public class MyObserver implements Observer {

    private int id;
    private MyPerson myPerson;

    public MyObserver(int id) {
        System.out.println("我是观察者---->" + id);
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public MyPerson getMyPerson() {
        return myPerson;
    }

    public void setMyPerson(MyPerson myPerson) {
        this.myPerson = myPerson;
    }

    @Override
    public void update(Observable observable, Object data) {
        System.out.println("观察者---->" + id + "得到更新");
        this.myPerson = (MyPerson) observable;
        System.out.println(((MyPerson) observable).toString());
    }

}

这样,在观察者接收到通知后,便会执行update方法进行更新操作

public class MainActivity extends Activity {

    private Button mBtnAddObserver;
    private Button mBtnChangeData;
    private MyPerson mObservable;
    private List<MyObserver> mObservers;

    private int i;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnAddObserver = (Button) findViewById(R.id.btn_add_observer);
        mBtnChangeData = (Button) findViewById(R.id.btn_change_data);

        mObservable = new MyPerson();
        mObservers = new ArrayList<MyObserver>();

        //按下添加Obeserver
        mBtnAddObserver.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Observer observer = new MyObserver(i);
                i++;
                mObservable.addObserver(observer);
                mObservers.add(observer);
            }
        });

        //按下改变Observable(同时会发送通知)
        mBtnChangeData.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                mObservable.setName("a" + i);
                mObservable.setAge(10 + i);
                mObservable.setSex("男" + i);
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        for(Obeserver observer : mObservers){
            mObservable.deleteObserver(observer);
        }
    }
}

Android中的观察者模式

我们要更新ListView的数据时,会使用到notifyDataSetChanged()方法,之后ListView的数据便会更新。这就比较类似这里的观察者模式了,ListView观察着Adapter中的内容,内容变化则ListView就会刷新

我们可以查看BaserAdapter的方法,会发现下面的几个方法。显然这里就是使用了观察者模式。当调用了notifyDataSetChanged后,被观察者就会发出通知。

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);
    }
    ......
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
    ......
}

Android用了一种很巧妙的方法解决了前面提到的单继承的问题,通过了对象适配器模式的方式。让BaseAdapter间接成为了被观察者。

其中这些注册和注销观察者等等方法是通过DataSetObservable这个类调用的。这个类继承自Observable。

public class DataSetObservable extends Observable<DataSetObserver> {   
    public void notifyChanged() {
        synchronized(mObservers) {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
    ......
}

Observable中有一个protected final ArrayList<T> mObservers = new ArrayList<T>();,  用来保存注册的观察者。mDataSetObservable.registerObserver(observer)mDataSetObservable.unregisterObserver(observer)分别就是增加和删除。

notifyChanged方法中,遍历这个集合,调用每一个观察者的onChanged()方法。

而这些观察者则是在ListView的setAdapter()调用的过程中注册的。

public class ListView extends AbsListView {
    public void setAdapter(ListAdapter adapter) {
        //如果已经有了一个adapter,注销这个adapter之前的观察者,
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

       ......
       if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            //将新的adapter赋给mAdapter
            mAdapter = adapter;
        }
        ......
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            //保存之前的数据个数
            mOldItemCount = mItemCount;
            //获取新的个数
            mItemCount = mAdapter.getCount();
            checkFocus();
            //创建数据集观察者
            mDataSetObserver = new AdapterDataSetObserver();
            //注册观察者
            mAdapter.registerDataSetObserver(mDataSetObserver);
            ...
            }
        } else {
            ...
        }
        requestLayout();
    }
}

这里AdapterDataSetObserver是ListView的父类AbsListView的内部类 。

public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
       ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
       ViewTreeObserver.OnTouchModeChangeListener,
       RemoteViewsAdapter.RemoteAdapterConnectionCallback {
       class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();
            if (mFastScroll != null) {
                mFastScroll.onSectionsChanged();
            }
        }
        ······
    }
}

AdapterDataSetObserver是AdapterView.AdapterDataSetObserver的子类,因此查看super.onChanged()

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
    class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;

        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局
            requestLayout();
        }
        ......
    }
}

当ListView数据变化时,调用Adapter的notifyDataSetChange方法,这个方法调用DataSetObservable的notifyChanged方法,这个方法又会调用所有观察者的onChanged方法,onChanged再调用重新布局View的方法,完成刷新数据的功能。

优缺点分析

  • 优点
    • 解除了观察者和被观察者的耦合,而且依赖的都是抽象,容易应对业务变化,各自的变化不会影响另一个。
    • 增强系统灵活性、可拓展性。
  • 缺点
    • Java中的消息默认是顺序执行,如果其中一个观察者卡顿,会造成整个系统效率变低,可以考虑异步。
    • 可能会引起无用的操作甚至错误的操作。
N0tExpectErr0r

N0tExpectErr0r

一名热爱代码的 Android 开发者

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>