基于反射实现的一个观察者模板

本文介绍了如何使用反射创建一个观察者模式的实现,以解决Java内置Observable的限制。通过新设计的IObservable、Observable和EventKeeper类,实现了更灵活的事件通知,支持组合和继承,并具备事件截断功能。尽管新观察者使用反射可能带来轻微性能损失,但在大多数情况下影响甚微。文章详细阐述了新观察者的使用方法和源码解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天这篇文章比较简单,主要是用于记录一个小功能,谢谢大家观看。

一、综述

如果大家用过Java的观察者实现,就会发现自带的观察者java.util.Observable使用起来比较尴尬,其内部使用了changed成员变量标识是否需要通知事件给各个观察者,但是置位此标签的代码如下:

protected void setChanged() {
    changed = true;
}

是的,这方法被限定为同包和子类才能访问,同包肯定不是它的目的,所以它的目标一开始就是让我们继承Observable来实现观察者。
同时,Java的Observable只支持一个通知接口,不管什么事件都是通过update接口对外通知:

public interface Observer {
    void update(Observable observable, Object data);
}

遗憾的是,现实使用中很多被观察对象都有自己的父类需要继承,而且观察的事件也多种多样,所以一般只能自己写观察者,自己写倒也不难,一个ArrayList存储观察者,一个add/remove操作观察者列表,再在事件发生时逐个调用观察者的相应事件即可。

但是,一次两次能忍,写的多了,自然就不想写了,毕竟我辈都是懒人,谁也不想写太多重复代码。所以我这里基于Java的反射机制写了个简单的观察者实现,用于替代原生的Observable,下面会有主要代码介绍,不想在网页看代码的,可以去看:https://github.com/qqliu10u/Observable.git

虽然是个git工程,但其实观察者这个事情实现比较简单,总共就三个类就可以搞定。在新的观察者实现中,有以下几个主体:

  1. 观察者,在原框架内是一个接口Observer,新设计中被泛化为一个泛型;
  2. 被观察者:Observable类,去掉了changed标签限制;
  3. 可被观察者的接口:IObservable;
  4. EventKeeper:处理反射工作;

总共有价值的类就三个:IObservable、Observable和EventKeeper。

下面就来看看新观察者的用法吧。

二、使用方法

对于被观察者,有两种集成方式:

1)实现IObservable接口并利用组合的方式使用Observable;
2)直接继承Observable类。

通常应该使用组合比较常见。下面是使用组合的实例,所有实现IObservable的功能都委托给Observable,实现IObservable的目的是为了在待观察者类内提供add/remove观察者的能力。IObservable内的泛型就是观察者(ITestObserver)。

public class TestObservable implements IObservable<ITestObserver> {

    private Observable<ITestObserver> mObservable;

    public TestObservable() {
        mObservable = new Observable<>();
    }

    @Override
    public void addObserver(ITestObserver observer) {
        mObservable.addObserver(observer);
    }

    @Override
    public void removeObserver(ITestObserver observer) {
        mObservable.removeObserver(observer);
    }

    @Override
    public void removeObservers() {
        mObservable.removeObservers();
    }

    @Override
    public int countObservers() {
        return mObservable.countObservers();
    }

    //产生各种事件,并通知给观察者
    public void handleClick(View view) {
        //通知到接口handleEvent(String)
        mObservable.sendEvent("handleEvent", "test");

        //通知到接口handleEvent(float, int)
        mObservable.sendEvent("handleEvent", 1.02f, 3);

        //通知到接口handleEvent(InterfaceParam)
        RealParam objectParam = new RealParam();
        objectParam.value = 50;
        mObservable.sendEvent("handleEvent", objectParam);
    }
}

嗯,这样就完啦!!是不是很简单,集成时需要做的只是在被观察者中实现接口IObservable并增加一个成员变量Observable,然后关联一下两者即可。比起java.util.Observable,我们可以自由选择继承还是组合、任意决定要通知到哪个接口,灵活性增加了不少。
另外,新观察者支持一种场景,如果事件返回boolean型值,且值为true,则截断事件向其他观察者的传递。这个特性可以用在部分独占性事件中。

不过,新观察者是不是一点缺点都没有呢?
不,你想多了!!!

1) 新观察者使用反射来发送事件,性能肯定要略弱一些。但是,一般观察者个数都不会太多,最多也就十几个,这点性能差异其实是无法感知的。而且,在实现的时候EventKeeper将事件对应的方法反射后就缓存了起来,等于用内存换了一些性能,所以只要不是对大批量的观察者进行操作,性能差距应该很小。
2)新观察者使用传入的参数来判断对应的接口,所以对于基本类型,需要明确指定数据的类型,比如直接写1.0,会被认为是double,如果观察者实现是float,则无法接收到事件。

嗯,集成方式和缺点讲完之后,我们来看看具体的实现吧,其实没有什么高深的东西,就是最简单的反射与缓存而已。

三、源码解析

3.1 可观察者IObservable定义

public interface IObservable<T> {
    void addObserver(T observer);
    void removeObserver(T observer);
    void removeObservers();
    int countObservers();
}

定义了四个可观察对象应实现的接口。非常简单,略。

3.2 具体可观察者Observable实现

public class Observable<T> implements IObservable<T> {

    private static final String TAG = "Observable";

    private ArrayList<T> mObservers = new ArrayList<T>();

    private EventKeeper<T> mEventKeeper;

    @Override
    public void addObserver(T observer) {
        if (observer == null) {
            throw new NullPointerException("observer == null");
        }

        synchronized (this) {
            if (!mObservers.contains(observer)) {
                mObservers.add(observer);
            }
        }
    }

    @Override
    public synchronized void removeObserver(T observer) {
        if (mObservers.contains(observer)) {
            mObservers.remove(observer);
        }
    }

    @Override
    public synchronized void removeObservers() {
        mObservers.clear();
    }

    @Override
    public int countObservers() {
        return mObservers.size();
    }

    /***
     * 通知事件
     *
     * @param event
     * @param params
     */
    public void sendEvent(String event, Object... params) {
        if (Utils.isEmpty(event)) {
            return;
        }

        if (null == mEventKeeper) {
            mEventKeeper = new EventKeeper<T>();
        }

        //找到标的对象
        if(mObservers.size() <= 0) {
            return;
        }
        T targetObserver = mObservers.get(0);

        Method notifyMethod = mEventKeeper.getNotifyMethod(
                targetObserver, event, params);

        if (null == notifyMethod) {
            return;
        }

        ArrayList<T> tmpListeners
                = (ArrayList<T>) mObservers.clone();
        for (T observer : tmpListeners) {
            try {
                Object result = notifyMethod.invoke(observer, params);

                //如果事件处理完成后返回true,则截断事件向其他观察者传递
                if (result instanceof Boolean
                        && ((Boolean) result)) {
                    break;
                }
            } catch (Exception ex) {
                Log.d(TAG, "sendEvent()| error happened", ex);
            }
        }
    }
}

注意,代码内用到了一个类Utils,其实就是做数组、字符串和集合的空判断用的,大家可以自行补全(null == xx || xx.length == 0),或去github上下载。
Observable实现的功能很简单,其内部维护一个观察者列表mObservers和一个事件反射帮助类mEventKeeper,提供对观察者列表的一系列操作。
在发送观察事件时,通过EventKeeper和传入的函数名称/事件参数来确定调用哪个接口Method,然后对观察者列表逐个调用Method。所以稍微复杂点的逻辑——事件反射(EventKeeper)才是重点。

3.3 EventKeeper实现

事件反射需要根据传入的事件名称和事件参数来确定一个合适的接口。需要考虑的有几点:

  1. 同名不同参事件;
  2. 参数装箱/反装箱;
  3. 类继承与接口实现;

同名不同参问题会影响方法缓存的设计,我最终用的是名称对ArrayList的映射进行缓存。
基本类型参数在传递过程中,被语言的可变参…改为了装箱类型,在确定某个方法是否合适时,应考虑此因素,否则找不到合适的方法。子类与接口实现的问题类似,不过这个比较简单,可以通过class.isAssignableFrom(class)来判断。下面是事件的反射与缓存类:

public class EventKeeper<T> {
    private HashMap<String, ArrayList<Method>> mNotifyMethodMap
            = new HashMap<String, ArrayList<Method>>();

    private List<Method> mAllMethodList = null;

    public Method getNotifyMethod(T observer, String event, Object[] params) {
        if (null == observer
                || Utils.isEmpty(event)) {
            return null;
        }

        //存在缓存,则使用缓存方法
        Method notifyMethod = findMethodInList(
                mNotifyMethodMap.get(event),
                event,
                params);
        if (null != notifyMethod) {
            return notifyMethod;
        }

        //获取标的对象的所有方法,用于查找监听器
        if (null == mAllMethodList) {
            mAllMethodList = Arrays.asList(observer.getClass().getDeclaredMethods());
        }

        notifyMethod = findMethodInList(mAllMethodList, event, params);

        if (null == notifyMethod) {
            return null;
        }

        ArrayList<Method> methodList = mNotifyMethodMap.get(event);
        if (null == methodList) {
            methodList = new ArrayList<>(1);
            methodList.add(notifyMethod);
            mNotifyMethodMap.put(event, methodList);
        } else {
            methodList.add(notifyMethod);
        }
        return notifyMethod;
    }

    private Method findMethodInList(List<Method> methodList, String event, Object[] params) {
        if (Utils.isEmpty(methodList)
                || Utils.isEmpty(event)) {
            return null;
        }

        Method result = null;
        for (Method method : methodList) {
            //名字要对上
            if (!event.equals(method.getName())) {
                continue;
            }

            Class<?>[] targetParamClassArray = method.getParameterTypes();

            if (Utils.isEmpty(targetParamClassArray)) {
                //无参的方法查找
                if (Utils.isEmpty(params)) {
                    result = method;
                    break;
                } else {
                    //不匹配,找下一个
                    continue;
                }
            }

            if (targetParamClassArray.length != params.length) {
                //参数个数不一样,则对不上,寻找下一个
                continue;
            }

            boolean hasFound = true;
            for (int i = 0; i < targetParamClassArray.length; i++) {
                Class<?> targetParamClass = targetParamClassArray[i];
                if(null == params[i]) {
                    hasFound = false;
                    break;
                }
                Class<?> realParamClass = params[i].getClass();
                if (!targetParamClass.isAssignableFrom(realParamClass)
                        && !isBoxClass(targetParamClass, realParamClass)) {
                    //参数类型不匹配
                    hasFound = false;
                    break;
                }
            }

            if (hasFound) {
                result = method;
                break;
            }
        }

        return result;
    }

    /***
     * 在目标类是基本类型时,保证传入的真实类是基础类的装箱
     */
    private boolean isBoxClass(Class<?> targetParamClass, Class<?> realParamClass) {
        if (!targetParamClass.isPrimitive()) {
            return false;
        }

        try {
            Class<?> primitiveType = (Class<?>) realParamClass.getField("TYPE").get(null);
            return targetParamClass == primitiveType;

        } catch (Exception ex) {
            return false;
        }
    }
}

重点需要关注两个方法的调用isAssignableFrom和isBoxClass,前者确定实参可以被赋值给形参,后者确定基本类型的事件查找不出差错。

四、总结

好了,新观察者讲完了,就我个人来看,这套用法算是非常简单的了。其实这功能本来也不复杂,但是每次都写重复代码,我实在是忍受不了了,所以就花了点时间整理了这么几个类,大家如果用得上,可以copy下直接使用,或者去https://github.com/qqliu10u/Observable.git看看。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值