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 条评论
登录 后参与评论

相关文章

来自专栏青玉伏案

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

今天是五.四青年节,祝大家节日快乐。看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火。这两道菜大部分人都应该吃过,特别是醋溜土豆丝,...

1699
来自专栏海说

深入理解计算机系统(3.2)---数据格式、访问信息以及操作数指示符

本文转载地址:http://www.cnblogs.com/zuoxiaolong/p/computer14.html

1095
来自专栏彭湖湾的编程世界

【javascript】异步编年史,从“纯回调”到Promise

异步和分块——程序的分块执行 一开始学习javascript的时候, 我对异步的概念一脸懵逼, 因为当时百度了很多文章,但很多各种文章不负责任的把笼统的描述混杂...

1968
来自专栏Golang语言社区

无辜的goroutine

简介: 本文主要是针对一些对于goroutine的“指控”提出我自己的看法,特别是轩脉刃的一篇博客文章《论go语言中goroutine的使用》提出了gorout...

35211
来自专栏carven

浅谈js的date对象对时间字符串的解析

最近的时间都在开发社团内部的应用–隧道口,虽然只有简单的几个页面,但是依然是遇到了不少坑。 其中 date 的时间处理就是一个。

730
来自专栏JetpropelledSnake

Linux学习笔记之Redis中5种数据结构的使用场景介绍

原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码。目前目标是吃透 redis 的数据结构。我们都知...

1041
来自专栏程序员宝库

Java 异常处理的 9 个最佳实践

在 Java 中,异常处理是个很麻烦的事情。初学者觉得它很难理解,甚至是经验丰富的开发者也要花费很长时间决定异常是要处理掉和抛出。 所以很多开发团队约定一些原则...

3449
来自专栏Urahara Blog

Using get_defined_functions To Hidden A PHP Backdoor

1582
来自专栏编程

C语言嵌入式系统编程修炼之性能优化

这是我13年前创作和发表在互联网上的文章,这么多年过去了,这篇文章仍然在到处传播。现在贴回Linuxer公众号。 全文目录: C语言嵌入式系统编程修炼之道——背...

1957
来自专栏葡萄城控件技术团队

C#开发人员应该知道的13件事情

本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助。 1. 开发过程 开发过程是错误和缺陷开始的地方。使用工具可以帮助你在发布之后,解决掉...

1919

扫码关注云+社区