Android设计模式-观察者模式

Posted vanpersie_9987

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android设计模式-观察者模式相关的知识,希望对你有一定的参考价值。

观察者模式是一个使用概率很高的模式,它最常用的地方时GUI系统、订阅——发布系统。因为这个模式的一个重要作用就是解耦,将被观察者和观察者解耦,使得它们的依赖变小、甚至毫无依赖。

android开发中,UI具有易变性,但是业务逻辑变化不大,此时GUI系统就需要一套机制在应对这种情况,使得UI层与具体的业务逻辑解耦,观察者模式在此就可以派上用场。

定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会的得到通知。

观察者模式的使用场景

  • 关联行为场景,需要注意的是,关联性为是可拆分的,而不是“组合关系”。

  • 事件多级触发场景;

  • 跨系统的消息转换场景,如消息队列、事件总线的处理机制。

观察者模式的角色介绍

  • Subject:抽象主题,也就是被观察者(Observable),抽象主题把所有观察者对象的引用保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。

  • ConcreteSubject:具体主题,该角色将有关对象存入具体观察者对象,在具体主题的内部状态发生改变时给所有注册过的观察者发出通知,具体主题角色又叫做具体被观察者(Concrete Observable)角色。

  • Observer:抽象观察者,该角色是观察者的抽象类,它定义了一个更新接口,使得在得到主题的更改通知说明时更新自己。

  • ConcreteObserver:具体观察者,该角色实现抽象观察者角色所定义的更新接口,以便在主题状态发生变化时更新自身的状态。

观察者模式的简单实现

一些技术网站拥有这个功能:每天会发布优质的内容,同时也会发送给订阅了这些内容的用户,这种模式就是观察者模式(也叫做发布-订阅模式)。

//用户(或者程序员)是观察者
public class Coder implements Observer 
    public String name;
    public Coder(String name) 
        this.name = name;
    
    @Override
    public void update(Observable o, Object arg) 
        System.out.println("Hi " + name + ", DevTechFrontier更新啦,内容:" + arg);
    
    @Override
    public String toString() 
        return "码农:" + name;
    




//某技术网站,这个网站是被观察者,当它有更新时,所有的观察者(即程序员)都会接收到相应的通知
public class DevTechFrontier extends Observable 
    public void postNewPublication(String content) 
        //标识状态或者内容发生改变
        setChanged();
        //通知所有观察者
        notifiyObservers(content);

    


//客户端测试代码
public class Test 
    public static void main(String[] args) 
        //被观察者
        DevTechFrontier devTechFrontier = new DevTechFrontier();
        //观察者
        Coder coder1 = new Coder("coder-1");
        Coder coder2 = new Coder("coder-2");
        Coder coder3 = new Coder("coder-3");
        //将观察者这测到可观察对象的观察者列表中
        devTechFrontier.addObserver(coder1);
        devTechFrontier.addObserver(coder2);
        devTechFrontier.addObserver(coder3);    
        //发布消息
        devTechFrontier.postNewPublication("新的一期技术周报发布啦!"); 
    


//输出
Hi,coder-3,DevTechFrontier更新啦,内容:新的一期技术周报发布啦!
Hi,coder-2,DevTechFrontier更新啦,内容:新的一期技术周报发布啦!
Hi,coder-1,DevTechFrontier更新啦,内容:新的一期技术周报发布啦!

看得出来,一对多的订阅——发布系统就完成了。

Observable和Observer都是JDK中的内置类型,可见它的重要性。这里Observer是抽象的观察者角色,Coder是具体的观察者角色;Observable对应的是抽象主题角色(抽象的被观察者),DevTechFrontier是具体的主题角色(具体的被观察者)。当DevTechFrontier有更新时,会遍历所有观察者(这里是Coder),然后给这些观察者发布一个更新的消息,即调用Coder中的update方法,这样就达到了一对多的通知功能。这个过程中,通知都是依赖Observer和Observable这些抽象类,所以,对于Coder和DevTechFrontier完全没有耦合,保证了订阅系统的灵活性、可扩展性。

实战

为了在Fragment与Fragment、Activity与Fragment之间进行通信,就需要有对方的引用,这回到时耦合性较高,解决办法是,使用事件总线

关于事件总线,有这样的场景:
在Activity-B中回调Activity-A中的某个函数,但Activity又不能手动创建对象来设置一个Listener;在某个Service中想更新Activity或Fragment中的界面,或者类的组件之间的交互问题。。。

以Activity和Fragment之间的通信为例,按照Android官方思路,Activity实现某个接口,然后再Fragment-A关联上Activity后,将Activity强转为接口类型,在某个时刻Fragment中回调这个接口,这个过程可以解耦,但实现复杂。

可见使用常规方式在Activity、Service、Fragment之间实现交互是比较麻烦的,比如使用BroadcastReceiver:在Activity-B中发送一个广播,在Activity-A中注册一个广播来接收,但是用广播接收器较麻烦,如果传递的数据是个实体化的类型,那么还需要实现序列化接口,这成本有些高:

public class ActivityA extends Activity 
    @Override
    protected void onCreate(Bundle saveInstanceState) 
        super.onCreate(saveInstanceState);
        registerReceiver(new BroadcastReceiver() 
            @Override
            public void onReceive(Context context, Intent intent) 
                User person = intent.getParcelableExtra("user");    

            
        , new IntentFilter("my_action"));
    
    ... ...



//在ActivityB中发布广播
public class ActivityB extends Activity 
    @Override
    protected void onCreate(Bundle saveInstanceState) 
        super.onCreate(saveInstanceState);
        Intent intent = new Intent("my_action");
        intent.putExtra("user", new User("mr.simple"));
    
    ... ...
//实体化类 实现序列化
public class implements Parcelable 
    String name;
    public User(String name) 
        this.name = name;

    
    //...省略
    @Override
    public void writeToParcel(Parcel dest, int flags) 
        dest.writeString(name);
    

看得出来,程序很麻烦!

再来看一个示例,在开发过程中,我们经常要这子线程中做一些耗时的工作,然后将结果更新到UI线程,除了AsyncTask外,Thread + Handler使我们经常使用的方式:

class MyActivity extends Activity 
    Handler mHandler = new Handler() 
        public void handleMessage(Message msg) 
            if(msg.what == 1) 
                User user = (User)msg.obj;
            

        
    ;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        // ... ...
        new Thread(new Runnable() 
            public void run() 
                User newUser = new User("simple");
                Message msg = mHandler.obtainMessage();
                msg.what = 1;
                msg.obj = newUser;
                mHandler.sendMessage(msg);
            
        ).start();
    

这也是相当麻烦!

事件总线框架就是为了简化这些操作而出现的,这可以降低组件之间的耦合性。

事件总线就是简化了Activity、Fragment、Service等组件之间的交互,很大程度上降低了它们之间的耦合,使得我们的代码更加简洁,耦合性降低,提升代码的质量。

当然,当今的开源库中有很多已存在的总线库,发现他们或多或少地存在一些问题:

名称订阅函数是否可异步执行特点
Greenrobot使用name pattern模式,效率高,但使用不方便
Otto使用注解,使用方便,但效率比不上EventBus
AndroidEventBus订阅函数支持tag(类似BroadcastReceiver的Action)使得事件的投递更加准确,能适应更多使用场景,使用方便,但效率比不上EventBus

下面我们自己也实现一个事件总线库,它以注解的形式实现,这样使用方便,并且让接收函数可以执行在异步线程,思路如下:

在需要接收事件的对象初始化时将自身注册到事件总线中,事件总线会遍历这个对象的所属类中的所有函数,然后根据订阅函数的时间参数和@Subscriber注解的tag值构建一个EventType,这个EventType标识了了一种事件类型,以这个EventType为Key,订阅对象列表为value构建一个map,存储这整个事件——订阅者列表,当通过EventBus发布事件时,会根据事件类型和tag来构建一个EventType,再根据这个EventType列表中查找所有的订阅者,然后执行它们的订阅函数。

我们先看看Subscriber注解的定义:

//事件接收函数的注解类,运用在函数上

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Subscriber 
    //事件的tag, 类似于BroadcastReceiver 中的Action。事件的标识符
    // @return
    String tag() default EventType.DEFAULT_TAG;
    //事件执行的线程,默认为主线程
    // @return
    ThreadMode mode() default ThreadMode.MAIN;

该注解作用于函数上,并且有一个tag和mode,分别为事件的tag(类似于广播接收器的Action)和该订阅函数的执行过程。

在将订阅对象注册到事件总线时会找到所有使用了Subscriber注解的类,并且以tag和事件类型作为key,订阅函数列表作为value存储在map中,对应代码为:

public final class EventBus 
    //订阅map
    private final Map<EventType, CopyOnWriteArrayList<Subscription>> mSubscriberMap = new ConcurrentHashMap<EventType, CopyOnWriteArrayList<Subscription>>();
    //the subscriber method hunter, find all of the subscriber's methods annotated with @Subscriber
    SubscriberMethodHunter mMethodHunter = new SubscriberMethodHunter(mSunbscriberMap);
    //注册订阅对象
    public void register(Object subscriber) 
        if(subscriber == null) 
            return;
        
        synchronized(this) 
            mMethodHunter.findSubscriberMethods(subscriber);
        
    

查找订阅方法的工作交给了SubscriberMethodHunter类,我们看看相关函数:

public class SubscriberMethodHunter 
    //the event bus's subscriber's map
    Map<EventType, CopyOnWriteArrayList<Subscription>> mSubscriberMap;
    //@param
    public SubscriberMethodHunter(Map<EventType, CopyOnWriteArrayList<Subscription>> subscriberMap) 
        mSubscriberMap = subscriberMap;
    

    // @param subscriber
    // @return
    public void findSubscribeMethod(Object subscriber) 
        if(mSubscriber == null) 
            throw new NullPointerException("the mSubscriberMap is null. ");
        
        Class<?> clazz = subscriber.getClass();
        //查找类中符合要求的注册方法,知道Object类
        while(clazz != null && !isSystemClass(clazz.getName())) 
            final Method[] allMethods = clazz.getDeclaredMethods();
            for(int i = 0;i < allMethods.length; ++i) 
                Method method = allMethods[i];
                //根据注解来解析函数
                Subscriber annotation = method.getAnnotation(Subscriber.class);
                if(annotation != null) 
                    //获取方法参数
                    Class<?>[] paramsTypeClass = method.getParameterTypes();
                    //只支持一个参数的订阅函数
                    if(paramsTypeClass != null && paramsTypeClass.length == 1) 
                        Class<?> paramType = convertType(paramsTypeClass[0]);
                        EventType eventType = new EventType(paramType, annotation.tag());
                        TargetMethod subscribeMethod = new TargetMethod(method, paramType, annotation.mode());
                        subscribe(eventType, subscribeMethod, subscriber);
                    
                

            //end for
            //获取父类Class 类型,以便后续查找父类中符合要求的订阅方法
            clazz = clazz.getSuperClass();
    
    //将找到的订阅函数添加到订阅列表中
    //@param event 事件类型, EventType
    //@param method 目标方法
    //@param subsriber 订阅者
    private void subsribe(EventType event, TargetMethod method, Object subsriber) 
        CopyOnWriteArrayList<Subscription> subscriptionLists = mSubscriberMap.get(events);
        if(subscriptionLists == null) 
            subscriptionLists = new CopyOnWriteArrayList<Subscription>();
        
        Subscription newSubscription = new Subscription(subscriber, method);
        if(subscriptionLists.contains(newSubscription)) 
            return;


        
        subscriptionLists.add(newSubscription);
        //将事件类型key和订阅者信息存储到map中
        mSubscriberMap.put(event, subscriptionLists);
    

这样就将每个订阅对象的所有订阅函数存储在一个表中了,这个key就是EventType,而EventType又由订阅函数的参数类型和tag决定,当发布一个事件时,就从列表中获得订阅了这个事件的所有订阅函数列表,然后将它们执行在指定的线程。

发布事件:

public final class EventBus 
    //the thread local event queue, every single thread has it's own queue.
    ThreadLocal<Queue<EventType>> mLocalEvents = new ThreadLocal<Queue<EventType>>() 
        protected Queue<EventType> initalValue() 
            return new ConcurrentLinkedQueue<EventType>();
        ;
    ;
    //the event dispather
    EventDispatcher mDispatcher = new EventDispatcher();
    //发布事件
    public void post(Object event, String tag) 
        mLocalEvents.get().offer(new EventType(event.getClass(), tag));
        mDispatcher.dispatchEvents(event);
    

可以看到,在发布事件时,首先将事件添加到mLocalEvents这个事件队列中,然后通过EventDispatcher来对事件进行处理,例如,根据EventType找到订阅者列表,根据订阅者列表的线程模型找到对应的事件处理器,最后通过事件处理器执行订阅函数,相关代码:

public final class EventBus 
    //事件队列
    ThreadLocal<Queue<EventType>> mLocalEvents = new ThreadLocal<Queue<EventType>>() 
        protected Queue<EventType> initalValue() 
            return new ConcurrentLinkedQueue<EventType>();
        ;
    ;
    //省略代码... ...
    //事件分发器
    private class EventDisplatcher 
        //将接收方法执行在UI线程
        EventHandler mUIThreadEventHandler = new UIThreadEventHandler();
        //哪个线程执行的post,接收方法就执行在哪个线程
        EventHandler mPostThreadHandler = new DefaultEventHandler();
        //异步线程中执行订阅方法
        EventHandler mAsyncEventHandler = new AsyncEventHandler();
        //处理事件
        void dispatchEvents(Object aEvent) 
            Queue<EventType> eventQueue = mLocalEvents.get();
            while(eventsQueue.size() > 0) 
                deliveryEvent(eventsQueue.poll(), aEvent);
             
        

    
    //根据aEvent查找到所有匹配的集合,然后处理事件
    // @param type
    // @param aEvent
    private void deliveryEvent(EventType type, Object aEvent) 
        Class<?> eventClass = aEvent.getClass();
        List<EventType> eventType = null;
        //如果有缓存则直接从缓存中取
        if(mCacheEventTypes.containsKey(eventClass)) 
            eventTypes = mCacheEventTypes.get(type);
         else 
            //查找订阅者的订阅函数,找到匹配的订阅类型
            eventTypes = mMatchPolicy.findMatchEventTypes(type, aEvent);
            mCacheEventTypes.put(type, eventTypes);
        
        //处理事件
        for(EventType eventType : eventTypes) 
            handleEvent(eventType, aEvent);
        
    
    //处理单个事件
    //@param eventType
    //@param aEvent
    private void handleEvent(EventType eventType, Object aEvent) 
        //根据EventType找到订阅者
        List<Subscription> subscriptions = mSubscriberMap.get(eventType);
        if(subscriptions == null) 
            return;
        
        //将事件分发到所有的订阅者中,也就是执行订阅函数
        for(Subscription subscription : subscriptions) 
            final ThreadMode mode = subscription.threadMode;
            //根据线程模型来获取对应的事件处理器
            EventHandler eventHandler = getEventHandler(mode);
            //处理事件
            eventHandler.handleEvent(subscription, aEvent);
        
    
    private EventHandler getEventHandler(ThreadMode mode) 
        if(mode == ThreadMode.ASYNC) 
            return mAsyncEventHandler;
        
        if(mode == ThreadMode.POST) 
            return mPostThreadHandler;
        
        return mUIThreadEventHandler;
    
//end of EventDispatcher

发布事件之后会将事件添加到事件列表中,会根据事件类型和tag构造EventType,发布的事件是一个,但是EventType可能有多个,比如,用户再注册订阅函数时参数类型为List<String>,在用户发布事件时节能参数就会传递ArrayList<String>类型。因此,在查找订阅函数时会将参数类型是发布事件类型的父类也返回,这样使得在订阅事件参数类型与发布类型之间有父子关系的情况下也能正常匹配到。

找到了对应的EventType后,就会根据这个EventType从列表中获得所有的订阅者,然后根据这个订阅者的线程模型找到一个对应的事件处理器,最后将这个订阅函数执行到对应的线程中。至此,整个订阅发布流程完毕。当然,不要忘记在Activity等对象的生命周期结束时,调用EventBus的unregister函数注销,否则会造成内存泄漏

总结

观察者模式主要的作用就是对象解耦,将观察者和被观察者完全隔离,只依赖于Observer和Observable抽象,如,ListView就是运用了Adapter和观察者模式使得它的扩展性、灵活性非常强,而耦合度却很低,这是设计模式在Android源码中优秀运用的典范。

  • 优点

(1)观察者和被观察者之间是抽象耦合,应对业务变化

(2)增强系统灵活性,可扩展性

  • 缺点

程序中包含一个被观察者,多个观察者,开发和调试会比较复杂,而且在Java中纤细的通知默认是顺序执行,一个观察者卡顿,会影响整体的执行效率,这种情况一般采用异步的方式。

以上是关于Android设计模式-观察者模式的主要内容,如果未能解决你的问题,请参考以下文章

深入理解JavaScript系列(32):设计模式之观察者模式

js 之观察者模式

JavaScript---设计模式之观察者模式

面向对象的设计模式,观察者模式

Android 经常使用设计模式

Android开发模式之观察者模式