jface databinding:更简单的ISideEffect实现多目标单边数据绑定塈其原理分析

Eclipse 4.6 提供了名为 ISideEffect的数据绑定工具. ISideEffect可以实现当一个或多个观察对象(IObservable)改变时执行特定代码。 ISideEffect很像一个侦听器,但它却不需要开发者像侦听器那样作任何依附对象的动作(addChangeListener/removeChangeListener)。当被监控的观察对象改变时它会自动反应执行指定的代码。 口说无凭,还是举个简单的栗子吧:

下面这个代码片中,当userFirstName或userLastName 改变时会自动更新Label 对象中的内容,

IObservableValue<String> userFirstName = ...
IObservableValue<String> userLastName = ...
Label yourUsername = ...

ISideEffect sideEffect =
  ISideEffect.create(
       () -> {return "Your username is: " + userFirstName .getValue()+"."+userLastName.getValue();},// 返回观察对象列表
       yourUsername::setText// 要执行的动作
       );

在这段代码中,一般做数据绑定必须的WidgetProperties,DataBindingContext对象和bindValue方法都没有粗线,就一个简单的ISideEffect.create方法就完成了userFirstName和userLastName -> yourUsername 之间的单向数据绑定。 刚看到的这个例子时,我震惊了。。。尼玛这是何方神器啊?好牛逼啊。 深入研究ISideEffect.create方法的源码,才搞明白原理: 要完全讲清楚它的机制说起来太麻烦也没那能力,就只简单说说它的实现原理了几个关键点吧。 说到底,ISideEffect的实现基本原理还是通过加载侦听器(addChangeListener)到被观察对象来实现数据绑定的。 只不过这载侦听器的动作以及很多相关动作都在开发者没有察觉的情况下被悄悄完成了。 首先调用create方法后,ISideEffect会自动分析并获取第一个参数中涉及的所有被观察对象(IObservable)。 怎么获取的呢? 这就要说到另一个神器ObservableTracker,ObservableTracker中的runAndMonitor方法有一个神奇的功能就是可以返回第一个参数中所有被读取过的IObservable对象列表。 很显然上面的例子中,第一个参数是个lambda表达式,() -> {return "Your username is: " + userFirstName.getValue()+"."+userLastName.getValue();},这个表达式用调用了userFirstName.getValue()userLastName.getValue()。 于是ISideEffect就知道需要监控userFirstName 和userLastName这两个Observable对象。 然后就会在userFirstName 和userLastName上添加ChangeListener,当userFirstName 和userLastName中任何一个对象改变时,会先执行第一参数指定的lambda表达式,返回”Your username is: xxxx”,然后执行第二个表达式,将yourUsername内容设置为第一个lambda表达返回的值。

那么再问一句:ObservableTracker.runAndMonitor又是如何能分析出所有被观察对象的呢? 简单说,这完全依赖于另一个方法的配合ObservableTracker.getterCalled,所有的IObservable对象都会在getter方法中调用ObservableTracker.getterCalled,是它收集并向ObservableTracker透露了消息,才让ObservableTracker.runAndMonitor达成目的。 下面是ObservableTracker.getterCalled的调用层次结构图

下面是ObservableTracker.getterCalled的源码

    public static void getterCalled(IObservable observable) {
        if (observable.isDisposed())
            Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$
                    + toString(observable));
        Realm realm = observable.getRealm();
        if (!realm.isCurrent())
            Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$
                    + toString(observable));

        if (isIgnore())
            return;

        Set<IObservable> getterCalledSet = currentGetterCalledSet.get();
        // 下面这句getterCalledSet.add(observable)就像间谍一样把当前的observable对象提供给了ObservableTracker
        if (getterCalledSet != null && getterCalledSet.add(observable)) {
            // If anyone is listening for observable usage...
            IChangeListener changeListener = currentChangeListener.get();
            if (changeListener != null)
                observable.addChangeListener(changeListener);
            IStaleListener staleListener = currentStaleListener.get();
            if (staleListener != null)
                observable.addStaleListener(staleListener);
        }
    }

关于ObservableTracker.getterCalled更详细的说明参见本文最后附参考资料中的《Tracked Getter》

ISideEffect与DataBindingContext 的区别

ISideEffect与原有的DataBindingContext binding机制相比有着明显区别,它们之间一种相互补充的关系: DataBindingContext实现的是一对一的数据绑定,支持双向数据同步更新,支持数据类型转换、数据验证,几乎方方面面都照顾到了,可以看作是个大而全的体系。 但是这个大而全的体系并不是在所有的场景下用起来都顺手,有时还显得臃肿, 比如我们在很多应用场景下并不需要双向数据同步更新,只需要单向的控制,也不需要类型转换和数据验证,这时DataBindingContext复杂的调用方式就显得麻烦臃肿。(参见我的下一篇博客《jface databinding: Radio Button group及ISideEffect绑定数据对象的例子》中用ISideEffect控制组件visiable状态的例子)。 再比如当多个Observable对象更新时都要同时更新同一个数据对象时(比如状态条),DataBindingContext就要创建多个绑定,好麻烦,这个数据对象就会被短时间内更新多次。 再回头来看ISideEffect,DataBindingContext的缺点就是ISideEffect的优点,我们可以把ISideEffect视为支持一对一、一对多、多对一、多对多的单向数据绑定机制(自然不支持双向数据更新),因为它不局限于一对一的灵活性,所以它没有数据类型转换、数据验证的概念。换个角度来看,可以把ISideEffect理解为一个触发器,当一个或多个Observable对象改变时自动触发执行指定的动作,具体是什么动作,可以是任意的,不一定是数据更新,播放一段音乐也是可以的。。。

前面说过了,在多对一、多对多的场景下,当多个观察对象(IObservable)更新时,ISideEffect会自动响应,所以在短时间内有多个观察对象(IObservable)更新的的情况下,ISideEffect只响应一次,可以避免过多的更新动作,这是DataBindingContext做不到的。

参考资料: 《Tracked Getter》 《ObservableTracker》 《Using the ISideEffect API》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序猿的那些趣事

JavaScript设计模式与实践--适配器模式

适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协...

871
来自专栏静默虚空的博客

[设计模式]工厂方法模式

简介 工厂方法模式 (Factory Method)定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其他子类。  工厂模式是...

1726
来自专栏醒者呆

缘分一道桥——桥接模式

桥接模式是一种很实用的结构型设计模式,它是将抽象部分与它的实现部分分离,使他们都可以独立地变化。 首先介绍一个标准的桥接模式的使用场景: 如果我想买汽车Ca...

3517
来自专栏Java技术栈

Java 10的10个新特性,将彻底改变你写代码的方式!

Java 9才发布几个月,很多玩意都没整明白,现在Java 10又要来了。。 这时候我真尼玛想说:线上用的JDK 7 甚至JDK 6,JDK 8 还没用熟,JD...

3948
来自专栏从零开始学 Web 前端

嵌入式面试题(一)

4. 空指针(null pointer)指向了内存的什么地方(空指针的内部实现)?

662
来自专栏决胜机器学习

设计模式专题(七)——建造者模式

设计模式专题(七)——建造者模式 (原创内容,转载请注明来源,谢谢) 一、概述 建造者模式(Builder),又称生成器模式,是将一个复杂的对象的构建与它的表...

35211
来自专栏Albert陈凯

Scala Essentials: 字符串内插值

字符串插值 Scala是一门高度可扩展性的程序设计语言,保持微小的内核,但具有无穷大的扩展能力。例如,「字符串内插」功能,Scala语言并不是原生地支持该特性...

2647
来自专栏沈唁志

如何优化PHP性能呢?PHP性能优化总结

1413
来自专栏阮一峰的网络日志

Redux 入门教程(一):基本用法

一年半前,我写了《React 入门实例教程》,介绍了 React 的基本用法。 React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两...

4215
来自专栏Java Edge

UML 类图1 类

在UML 2.0的13种图形中,类图是使用频率最高的UML图之一。Martin Fowler在其著作《UML Distilled: A Brief Guide ...

481

扫码关注云+社区