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

一、综述

如果大家用过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<>();}@Overridepublic void addObserver(ITestObserver observer) {mObservable.addObserver(observer);}@Overridepublic void removeObserver(ITestObserver observer) {mObservable.removeObserver(observer);}@Overridepublic void removeObservers() {mObservable.removeObservers();}@Overridepublic 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;@Overridepublic void addObserver(T observer) {if (observer == null) {throw new NullPointerException("observer == null");}synchronized (this) {if (!mObservers.contains(observer)) {mObservers.add(observer);}}}@Overridepublic synchronized void removeObserver(T observer) {if (mObservers.contains(observer)) {mObservers.remove(observer);}}@Overridepublic synchronized void removeObservers() {mObservers.clear();}@Overridepublic 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看看。

基于反射实现的一个观察者模板相关推荐

  1. 后端思维篇:如何抽一个观察者模板

    前言 今天跟大家聊聊什么是观察者模式,如何应用到工作实践中,以及如何抽取一个观察者模板. 1. 观察者模式定义 观察者模式,也可以称之为发布订阅模式,它在GoF 的<设计模式>中,是这么定 ...

  2. html5快速开发模板生成器,推荐一个基于Vue 的 H5 快速开发模板

    本项目以基于 vue-cli4 和 Vant-ui 搭建的,进行移动端开发中的一些最佳实践方案 模板地址 动动你的小手点颗star 样式适配 在移动端网页开发时,样式适配始终是一个绕不开的问题.对此目 ...

  3. 设计模式-05.01-行为型-观察者模板模式

    文章目录 观察者模式[常用] Demo案例-天气预报 方案一 WeatherData CurrentConditions Client 问题分析 观察者模式方案 Subject[接口] Observe ...

  4. 用JSP创建一个表格模板 .

    项目中要用到一些展示信息的表格,表头不固定,表格内容是即时从后台取的:考虑到复用性,笔者用jsp编写了一个表格模板,可以从request中获取List封装的数据,然后通过JSTL结合EL表达式填充到模 ...

  5. 人工智能阴影检测与去除,实现一种基于反射的阴影检测与去除方法

    人工智能阴影检测与去除,实现一种基于反射的阴影检测与去除方法(特约点评:人工智能阴影检测与去除,实现一种基于反射的阴影检测与去除方法对于阴影检测与去除任务提供了新的思路,这个创新点趣说人工智能必须推荐 ...

  6. 单例模式:基于反射和反序列化破解单例模式的漏洞及其解决方法

    单例模式使得在创建类对象的时候只创建一个对象实例.上一节讲解了五种实现单例模式的方式. 分别为:饿汉模式.懒汉模式.double check.静态内部类.枚举 但是基于反射和反序列化可以破解单例模式的 ...

  7. Java面试题 22 牛客 Java是一门支持反射的语言,基于反射为Java提供了丰富的动态性支持

    Java面试题 22 牛客 Java是一门支持反射的语言,基于反射为Java提供了丰富的动态性支持,下面关于Java反射的描述,哪些是错误的:(          ) A Java反射主要涉及的类如C ...

  8. 依赖注入底层反射原理_PHP基于反射机制实现自动依赖注入的方法详解_php技巧...

    这篇文章主要介绍了PHP基于反射机制实现自动依赖注入的方法,结合实例形式分析了php使用反射实现自动依赖注入的步骤.原理与相关操作技巧,本文实例讲述了PHP基于反射机制实现自动依赖注入的方法.分享给大 ...

  9. 基于gulp编写的一个简单实用的前端开发环境

    自从Node.js出现以来,基于其的前端开发的工具框架也越来越多了,从Grunt到Gulp再到现在很火的WebPack,所有的这些新的东西的出现都极大的解放了我们在前端领域的开发,作为一个在前端领域里 ...

最新文章

  1. C#面向对象三大特性之二:继承
  2. 从 HTTP 到 HTTP/3 的发展简史
  3. golang python性能_Golang构建Python高性能模块
  4. JDK 动态代理和MyBatis 用到的JDK 动态代理有什么区别?
  5. oracle语句求保有率,Oracle之保有量计算(当前记录等于前几条记录之和)
  6. Strus2第一次课:dom4j操作xml
  7. matlab 多 带阻,matlab程序之——滤波器(带通-带阻
  8. 数据结构之树和二叉树的应用:二叉排序树(BST)
  9. r语言 list添加_R语言里面双层list变成长形数据框
  10. Altium AD20将已有的原理图PCB导出为封装库
  11. 绝对定位(HTML、CSS)
  12. 算法 Tricks(四)—— 判断序列中的字符/数值是否交替出现
  13. arcgis runtime for android 100.13.0 入门系列,一、初步引入与运行
  14. linux bridge - mac和vlan转发
  15. Python之生成器练习
  16. 西西里的美丽传说:美的绽放、挣扎与凋零
  17. 笔记本电脑硬盘不见了_笔记本电脑找不到硬盘原因及解决方法
  18. mcpe服务器网页控制台教程,mcpe服务器指令
  19. 将一个数组中重复的元素去除,并且返回一个新数组
  20. python编写程序计算复利-使用Python函数计算复利

热门文章

  1. 利用sql profile固定执行计划加快OGG同步
  2. S5PV210 iNAND/SD卡
  3. ThreeJs 学习之旅(七)
  4. 如何从手机上恢复误删的微信聊天记录
  5. ffmpeg音视频转单声道16位16K赫兹小端点pcm音频
  6. 以太坊五周年:从涅槃中苏醒
  7. opencv实现魔幻笔效果
  8. 掌门1对1获3.5亿美元E-1轮融资,华人文化产业基金、中金甲子基金等投资...
  9. 第四章第二节数据资产盘点-数据资产盘点方法伦
  10. 第三届中医药文化传承与技能发展大会召开助推中医药文化传承创新