美文网首页
观察者模式

观察者模式

作者: CodingSnail | 来源:发表于2017-12-18 20:11 被阅读0次
    微信图片_20171218201112.jpg
    目前代码已经上传到GitHub,里面有设计模式系列的整个代码
    GitHub 设计模式地址

    1、观察者模式介绍

    观察者模式是一个使用率非常高的模式,订阅-发布系统,也叫订阅者模式,最常见的应用就是在报社交钱了,送报员每天会给你送报纸,订阅了微信公众号,发公众号发布了文章以后会推送到微信里,以及移动端常用的消息推送,都是订阅了推送,然后有通知的时候推送过来,这个模式的重要作用就是解耦,将被观察者和观察者解耦,使得它们之间依耐性很小。

    2、观察者模式的定义

    定义对象间一对多的依赖关系,当一个对象状态发生改变时,所有依赖于它的对象都会的达到通知并被自动更新。

    3、Java内置的观察者模式的实现

    object_observer.png

    以上图以一个关注的 微信订阅号-- "面向对象"来说明观察者模式
    首先有需要对象的单身男士 关注"面向对象"订阅号,那么单身男士就是观察者,“面向对象”订阅号是被观察者,当面向对象这个平台上有小姐姐发布自己的信息后,"面向对象"订阅号会像所有的观察者发布这个小姐姐的个人信息。
    MsgInfo 小姐姐的个人信息

    package com.codingsnail.designpattern.observer;
    
    /**
     * Created by Snail on 12/20/2017 6:02 PM
     * Contact with slowsnail0223@gmail.com
     * 小姐姐对象
     */
    public class MsgInfo {
    
        private int age;
        private String desc;
    }
    

    SubscribeSubject 微信订阅号作为被观察者,被观察者如何发出通知单身狗们我们网站上的信息已经更新了尼?
    1、继承Obserable类产生被观察者类
    2、先调用setChanged()方法,标记现在面向对象的后台信息已经改变
    3、调用notifyObservers(Object obj)和notifyObservers()方法发送通知
    这两个通知的方法,一个是通知的时候已经把数据push过去了,另外一个是接收到通知的时候,主动pull数据

    package com.codingsnail.designpattern.observer;
    
    import java.util.Observable;
    
    /**
     * Created by Snail on 12/20/2017 5:37 PM
     * Contact with slowsnail0223@gmail.com
     * 单身男士-订阅 微信公众号"面向对象"
     * 面向对象订阅号作为被观察者  Subject
     */
    public class SubscribeSubject extends Observable {
    
        /**
         * 刚发布的小姐姐的个人信息
         */
        private MsgInfo msgInfo;
    
        public void noticeMan(MsgInfo msgInfo) {
            this.msgInfo = msgInfo;
            //1、首先必须调用 setChanged()标记状态已经改变
            setChanged();
            //2、调用notifyObservers()通知出去
            notifyObservers(msgInfo);  // push的方式
            notifyObservers();  //  pull拉取的方式
        }
    
        public MsgInfo getMsgInfo() {
            return msgInfo;
        }
    }
    
    

    SingleManObserver 观察者 单身男士订阅 微信公众号,并在获取到公众号推送的消息后试图解决单身问题。
    首先需要实现SingleManObserver 继承Observer ,然后实例化Observable,调用observable的 observable.addObserver(this)来订阅,当已经找到对象的时候就可以调用
    observable.deleteObserver(this)将自己从观察者列表中移除。

    package com.codingsnail.designpattern.observer;
    
    import java.util.Observable;
    import java.util.Observer;
    
    /**
     * Created by Snail on 12/20/2017 5:34 PM
     * Contact with slowsnail0223@gmail.com
     * 单身男士-订阅 微信公众号"面向对象"
     * 单身男士作为观察者
     */
    public class SingleManObserver implements Observer {
    
        private Observable observable;
        private MsgInfo msgInfo;
    
        public SingleManObserver(Observable observable) {
            this.observable = observable;
            observable.addObserver(this);
        }
    
        @Override
        public void update(Observable o, Object obj) {
            // push的方式获取
            if (obj instanceof MsgInfo) {
                this.msgInfo = (MsgInfo) obj;
                readMessage(msgInfo);
                contact(msgInfo);
            }
    
            //pull的方式
            if (o instanceof SubscribeSubject) {
                this.msgInfo = ((SubscribeSubject) o).getMsgInfo();
                readMessage(msgInfo);
                contact(msgInfo);
            }
        }
    
        /**
         * 不在订阅获取资料
         */
        public void deleteObserver() {
            observable.deleteObserver(this);
        }
    
        /**
         * 阅读个人信息
         *
         * @param message 小姐姐个人信息
         */
        private void readMessage(MsgInfo message) {
        }
    
        /**
         * 联系小姐姐
         *
         * @param message 小姐姐个人信息
         */
        private void contact(MsgInfo message) {
        }
    }
    
    

    4、Android中常见的观察者模式

    无论是在Android系统还是在常见的开源框架上,Android运用到了大量的观察者模式,最常见的莫过于ViewPager、ListView,RecycleView的setAdapter(),View.OnClickerListener()
    下面是Android源码BaseAdapter的实现可以看出其如何去实现观察者模式的

    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
        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();
        }
    
        /**
         * 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();
        }
    
        public boolean areAllItemsEnabled() {
            return true;
        }
    
        public boolean isEnabled(int position) {
            return true;
        }
    
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return getView(position, convertView, parent);
        }
    
        public int getItemViewType(int position) {
            return 0;
        }
    
        public int getViewTypeCount() {
            return 1;
        }
        
        public boolean isEmpty() {
            return getCount() == 0;
        }
    }
    

    5、我的Android项目中观察者模式的具体使用

    首先我们有一个业务场景 比如从A页面到B页面去了以后,当B页面的部分数据通过网络修改了以后我们需要通知其他页面上同样的数据需要改变,C,D,E...并且我们有些页面打开的时候没有重新请求网络或者返回键回退到某些页面也没有刷新数据,这个时候我们就需要用用到观察者模式,NO BB,直接上代码。
    定义了接口AccountListener ,里面有onAccountLogin(),onAccountLogout()当用户登录成功后,或者退出登录后,和用户数据相关的页面需要做的业务逻辑处理

    /**
     * Created by Snail on 12/18/2017 7:19 PM
     * Contact with slowsnail0223@gmail.com
     * 用户Listener
     */
    public interface AccountListener {
    
        /**
         * 用户登录回调
         */
        void onAccountLogin();
    
        /**
         * 用户退出登录回调
         */
        void onAccountLogout();
    }
    

    ListenerManager 用来管理我们项目中的接口类

    /**
     * Created by Snail on 12/18/2017 7:18 PM
     * Contact with slowsnail0223@gmail.com
     */
    public class ListenerManager {
    
        public static final HashMap<String, WeakReference<AccountListener>> accountListeners = new HashMap<>();
    
        public static void registerAccountListener(String key, AccountListener listener) {
            synchronized (accountListeners) {
                WeakReference<AccountListener> ref = accountListeners.get( key );
                if (ref != null && ref.get() != null) {
                    ref.clear();
                }
                accountListeners.put( key, new WeakReference<>( listener ) );
            }
    
        }
    
        public static void unregisterAccountListener(String key) {
            synchronized (accountListeners) {
                WeakReference<AccountListener> ref = accountListeners.get( key );
                if (ref != null && ref.get() != null) {
                    ref.clear();
                }
                accountListeners.remove( key );
            }
        }
    }
    

    Test1Activity 第一个页面,上面显示的有用户名信息,注册了AccountListener ,一旦收到用户退出的通知后,页面显示用户已经退出

    class Test1Activity : AppCompatActivity(), AccountListener {
    
        override fun onAccountLogin() {
        }
    
        override fun onAccountLogout() {
            tv_content.text = "当前用户已经退出"
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test1)
            ListenerManager.registerAccountListener(Test1Activity::class.java.simpleName, this)
            tv_jump.setOnClickListener {
                Test2Activity.jumpActivity(this)
            }
        }
    
        override fun onDestroy() {
            super.onDestroy()
            ListenerManager.unregisterAccountListener(Test1Activity::class.java.simpleName)
        }
    
        companion object {
            fun jumpActivity(context: Context) {
                context.startActivity(Intent(context, Test1Activity::class.java))
            }
        }
    }
    
    

    Test2Activity 这个页面有一个按钮,点击的时候模拟用户退出的操作,发送通知,通知所有的观察者用户退出,目前遇到一个未知问题


    image.png

    当用Kotlin语言去接受Java的HashMap的时候,用迭代器获取key value程序出现莫名问题,没有崩溃,手机直接黑屏死机,后来换了Kotlin的foreach{}方法处理的迭代


    image.png
    package com.codingsnail.designpattern.observer
    
    import android.content.Context
    import android.content.Intent
    import android.support.v7.app.AppCompatActivity
    import android.os.Bundle
    import com.codingsnail.designpattern.R
    import kotlinx.android.synthetic.main.activity_test2.*
    
    class Test2Activity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_test2)
    
            tv_logout.setOnClickListener {
                synchronized(ListenerManager.accountListeners) {
    
                    val accountListeners = ListenerManager.accountListeners
                    accountListeners.entries.forEach {
                        val ref = it.value
                        val listener = ref!!.get()
                        listener!!.onAccountLogout()
                        finish()
                    }
    
    //                /**  出现莫名错误以后去发掘 ****/
    //                val keys = ListenerManager.accountListeners.keys
    //                val iterator = keys.iterator()
    //                while (iterator.hasNext()) {
    //                    val key = keys.iterator().next()
    //                    val ref = ListenerManager.accountListeners[key]
    //                    val listener = ref!!.get()
    //                    listener!!.onAccountLogout()
    //                    finish()
    //                }
                }
            }
        }
    
        companion object {
            fun jumpActivity(context: Context) {
                context.startActivity(Intent(context, Test2Activity::class.java))
            }
        }
    }
    
    

    总结:个人不太喜欢用EventBus事件总线去处理工作中的这种一对多的通知,因为EventBus只是通过一个key去做分发和接受,代码阅读性不是很好,用Android常见的setResult有些需要不满足,并且在Fragment里面也要去处理上层Activity的方法,比较繁琐。

    相关文章

      网友评论

          本文标题:观察者模式

          本文链接:https://www.haomeiwen.com/subject/dvomwxtx.html