观察者模式解析以及在Android中的实际应用

概述

在生活中和实际项目中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,在这样的情况下就可以使用观察者模式,比如天气预报的,我相信很多用户都有经常接到气象局的天气预报的推送,或者是杂志的,如果你订阅了,那么便会定期收到邮件的推送等等。

观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题(被观察)”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。前面所述的“气象局的天气预报中心”相当于观察者模式的一个具体“主题”;每个收到短信的用户相当于观察者模式中的一个具体“观察者”。

观察者模式的结构

观察者模式的结构中包含了四种角色:

● 主题(Subject):一般情况下主题是一个接口,该接口规定了具体主题需要实现的方法,一般都有添加,删除和更新内容等基本方法,当然可以适当扩充,一般主题也叫做被观察者。

● 观察者(Observer):观察者一般也是一个接口,主要方法为更新内容的接口方法。

● 具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。

● 具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者,所以一般观察者的方法里面都有一个删除的接口。

下面是观察者模式的类图:

可以看到类的结构图跟上面的介绍的角色是完全一致的。

观察者模式的优点

● 具体主题和具体观察者是松耦合关系。由于主题接口仅仅依赖于观察者接口,因此具体主题只是知道它的观察者是实现观察者接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题接口,因此具体观察者只是知道它依赖的主题是实现主题接口的某个类的实例,但不需要知道具体是哪个类。

● 观察者模式满足设计模式中的“开-闭原则”。主题接口仅仅依赖于观察者接口,这样,就可以让创建具体主题的类也仅仅是依赖于观察者接口,因此,如果增加新的实现观察者接口的类,不必修改创建具体主题的类的代码。同样,创建具体观察者的类仅仅依赖于主题接口,如果增加新的实现主题接口的类,也不必修改创建具体观察者类的代码。

使用场景

● 当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。

● 当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据

说了一堆枯燥的理论,下面看一个简单的例子:

首先定义一个主题和抽象观察者,代码如下:

public interface Subject {
     void registerObserver(Observer o);
     void removeObserver(Observer o);
     void notifyObservers(float temprature);
}

//抽象的观察者
public interface Observer {
    void update(float temprature);

    void remove(Subject mSubject);//不想再观察的时候删除
}
复制代码

然后定义实现主题的类和实现抽象观察者的类:

public class ConcreteSubject implements Subject {
    private static final List<Observer> observers;
    static {
        observers = new ArrayList<>();
        observers.clear();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        if (observers.indexOf(o) >= 0) {
            observers.remove(o);
        }
    }

    @Override
    public void notifyObservers(float temprature) {
        if (observers.size() <= 0) {
            System.out.println("没有观察者");
            return;
        }
        for (final Observer o : observers) {
            o.update(temprature);
        }
    }
}

这个实现了所有的主体接口,方便添加,删除和通知所有的观察者
复制代码

具体的观察者如下:

public class ConcreteObserver implements Observer {

    @Override
    public void update(float temprature) {
        System.out.println("温度为:" + temprature);
    }

    @Override
    public void remove(Subject mSubject) {
        mSubject.removeObserver(this);//在不需要观察的时候删掉即可
    }
}
复制代码

下面测试一下代码;

public class ObserverClient {
    public static void main(String[] args){
        final Subject sb = new ConcreteSubject();
        final Observer observer = new ConcreteObserver();
        sb.registerObserver(observer);
        sb.notifyObservers(21.0f);
        
        observer.remove(sb);
        sb.notifyObservers(23.0f);
    }
}
复制代码

测试代码很简单,先注册一个具体的观察者,然后主题发布一个温度,接着具体的观察者就收到通知了,而当具体的观察者删掉了主题之后,就再也收不到通知了,就好比你取消了杂志的订阅,便再也收不到杂志了,一样的理解就好,测试结果如下:

这只是一个主题和一个抽象观察者,实际上不管多少个主题或者抽象观察者,都是一样的,基本原理都是"订阅-发布-更新"系统而已。

观察者模式在Android源码中的应用

实际在Android系统的源码中,观察者模式可以说广泛的应用的,拿我们熟悉的Adapter中数据的更新机制来简单分析一下,一般情况下,有数据改变的时候,大部分人喜欢一句话解决问题:

mAdapter.notifyDataSetChanged();
我们来看一下调用的过程:
public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }
复制代码

这个mObservable是一个AdapterDataObservable对象,代码如下;

private final AdapterDataObservable mObservable = new AdapterDataObservable();
复制代码

可见又调用了mObservable的更新方法,代码如下:

public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
复制代码

这里是重点了,取出所有的观察者,然后再调用onChanged()方法, 有个问题,是什么时候添加那些具体的观察者的呢?是在setAdapter()方法里面,代码如下:

public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }
复制代码

接着又调用了

 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
        //先移除掉旧的观察者
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
        //这里是重点,注册
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        markKnownViewsInvalid();
    }
复制代码

我们可以看到注册的时候调用的是mAdapter的registerAdapterDataObserver方法,代码如下:

public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }
继续跟踪
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);
        }
    }
 可以看到这里才是真正注册的地方
复制代码

下面分析真正刷新的地方;代码如下:

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
//省略一些代码
复制代码

是取出依次更新调用了onChanged()方法,AdapterDataObserver是一个抽象类,那么真正的实现类是RecyclerViewDataObserver,代码如下:

private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;

            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();//这里是重点了,真正触发刷新
            }
        }
复制代码

可以看到终于来到了真正更新的地方了,就是这里的requestLayout()方法,根据View的原理,requestLayout默认情况下会一层一层往父亲类的requestLayout()调用,最终会调用顶层类的刷新,来到最后就是大名鼎鼎的ViewRootImpl的performTraversals()的方法,从而刷新界面,至此刷新流程结束。

观察者模式在Android实际中的应用

我们前面说了,观察者模式在一对多,并且在一个对象的数据改变引起其他对象的数据同时改变的时候特别有用,在Android项目中,这种需求是很多的,比如广播,广播发送出去以后,谁注册了广播,那么便会接收到广播的消息,又比如EventBus事件总线,只要发送出去消息事件,不管在哪里,谁注册了消息事件,也会同时收到事件,从而进行逻辑的处理,在实际的项目中,有时候也会碰到在不同的界面,需要同时改变UI的情况,比如在B界面处理了一个逻辑,然后需要在前面的A界面中,一些UI要求改变等等,假如有下面2个界面,分别是A界面和B界面,

好了,现在需求来了,要求点击TestActivity2中的改变全部文本按钮,然后TestActivity2和TestActivity1中的TextView都要求改为预先设置好的文本,这个例子来源于实际例子中的,当然实际例子涉及到红点或者其他UI的改变,不好抽离出来分析,所以就以这个简单的例子为说一下,但原理不变,我们首先来分析一下怎么实现比较好,当然EventBus发送消息可以解决问题,但发送消息之后需要一个个去手动设置新的文本,在这里我们不用EventBus来发送事件,我们利用观察者模式来解决,我们知道了点击"更改全部文本"之后会触发了一大堆的UI改变,首先我们定义了一个更新文本的方法,代码如下:

public interface Observer {
    void updateText(String text);//更新TextView的文本
    void remove(Observable observable);//删除自己作为观察者
}
复制代码

然后这些TextView为自定义的,并且实现了观察者接口,代码如下

public class AutoUpdatetextView extends android.support.v7.widget.AppCompatTextView implements Observer {
    public AutoUpdatetextView(Context context) {
        super(context);
    }

    public AutoUpdatetextView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public AutoUpdatetextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //在更新文本的接口方法里面实现了自动更新文本
    @Override
    public void updateText(String text) {
        if (!TextUtils.isEmpty(text)) {
            setText(text);
        }
    }

    @Override
    public void remove(Observable observable) {
        observable.remove(this);
    }
}
复制代码

好了,抽象观察者和具体观察者就写好了,我们来写抽象主题和具体的主题,代码如下:

抽象主题实现增加,删除和更新功能
public interface Observable {
    void addObserver(Observer observer);
    void remove(Observer observer);
    void update(String text);
}

 //具体的主题
public class ObservableManager implements Observable {
    private static final List<Observer> observers;
    private static ObservableManager sInstance;

    private ObservableManager() {

    }

    public static ObservableManager getInstance() {
        if (sInstance == null) {
            synchronized (ObservableManager.class) {
                if (sInstance == null) {
                    sInstance = new ObservableManager();
                }
            }
        }
        return sInstance;
    }

    static {
        observers = new ArrayList<>();
        observers.clear();
    }

    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void remove(Observer observer) {
        if (observers.indexOf(observer) > 0) {
            observers.remove(observer);
        }
    }

    @Override
    public void update(String text) {
        if (observers.size() <= 0) {
            Log.d("[app]", "没有具体的观察者");
            return;
        }
        for (Observer observer : observers) {
            observer.updateText(text);
        }

    }

    public void removeAll(List<Observer> mList) {
        if (mList == null || mList.size() == 0) {
            return;
        }
        Log.d("[app]", "移除的集合大小为:" + mList.size());
        for (Observer observer : mList) {
            remove(observer);
        }
        Log.d("[app]", "剩下的集合大小为:" + observers.size());
    }
}
复制代码

好了,抽象主题和具体的实现也写好了,我们再来分析一下,要把那些TextView添加到具体的主题里面,那么应该怎么添加,在 view这棵树已经添加到window上之后进行添加,在添加的判断是否是AutoUpdatetextView,如果是的话,就添加,如果不是,就不要添加了,为此,我们需要写一个BaseActivity,在这里,布局就不贴了,代码如下:

public abstract class BaseActivity extends AppCompatActivity {

    protected List<Observer> mList = new ArrayList<>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mList.clear();
        setContentView(getResID());
        ViewGroup viewGroup = (ViewGroup) getWindow().getDecorView();
        addUpdateTextView(viewGroup);
    }

    protected abstract int getResID();
    //把Observer的子类添加进去
    private void addUpdateTextView(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View childView = viewGroup.getChildAt(i);
            if (childView instanceof ViewGroup) {
                ViewGroup childGroup = (ViewGroup) childView;
                addUpdateTextView(childGroup);
            } else {
                if (childView instanceof Observer) {
                    //添加到观察者集合里面进去
                    Observer observer = (Observer) childView;
                    ObservableManager.getInstance().addObserver(observer);
                    mList.add(observer);
                }
            }
        }
    }
    //在onDestroy之后需要把这棵树上的AutoUpdatetextView移除,避免持有Activity导致内存泄露
    @Override
    protected void onDestroy() {
        ObservableManager.getInstance().removeAll(mList);
        super.onDestroy();
    }
}
复制代码

下面我们看一下TestActivity1的代码,

public class TestActivity1 extends BaseActivity {
    private TextView text;

    @Override
    protected int getResID() {
        return R.layout.activity_test1;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        text = (TextView) findViewById(R.id.text);
        text.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(TestActivity1.this, TestActivity2.class);
                startActivity(intent);
            }
        });
    }
}
就跳转到TestActivity2而已
复制代码

TestActivity2的代码:

public class TestActivity2 extends BaseActivity {
    private TextView update;
    String[] texts = {"开开心心", "呵呵,新的文本", "你好啊", "Android开发呢", "ios开发呢", "WP开发", "PHP开发", "Python开发"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        update = (TextView) findViewById(R.id.update);
        update.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int len = texts.length;
                int result = new Random().nextInt(len);
                Log.d("[app]", "result=" + texts[result]);
                //这里是重点,通知其他注册了的地方改变文本,这里是文本,实际上可以做其他事情
                ObservableManager.getInstance().update(texts[result]);
            }
        });
    }

    @Override
    protected int getResID() {
        return R.layout.activity_test2;
    }

}
复制代码

我们可以看到:

 ObservableManager.getInstance().update(texts[result]);
复制代码

这句话是重点,这里更新了文本,那么意味着之前注册的全部文本控件都将收到消息,从而走AutoUpdatetextView的更新文本的接口方法

//在更新文本的接口方法里面实现了自动更新文本
    @Override
    public void updateText(String text) {
        if (!TextUtils.isEmpty(text)) {
            setText(text);
        }
    }
复制代码

运行如下:

结果就是这样,动态显示文本,当然实际情况可以做更多的逻辑操作,限于篇幅,不再一一展示,理解原理更为重要,今天的文章就写到这里,感谢大家阅读。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值