从 SimpleIntegerProperty 看 Java属性绑定(property binding) 与 观察者模式(Observable)

//TODO:ExpressionHelper 、bindBidirectional双向绑定、以及IntegerExpression的一系列算术方法和返回的IntegerBinding暂未详细解析(比如,通过 sip.divide(2) 返回的IntegerBinding对象,是如何实现当sip修改时,其get方法的值也能做到除2【随便猜测可能就类似于单向绑定一样,维护observable并记录算术操作,在get时,调用observable.get并加上算术操作】)
//注:关于观察者模式和事件监听模式(具体有没有这个定义都还待定),虽然表现不太一样但实现逻辑都一样的,观察者模式说一对多的依赖关系,当改变时其他相关依赖对象都对得到通知并更新,其实就等于调用监听器的监听方法

一、背景

使用过 SimpXXXProperty 系列的类都知道,这些类是支持属性绑定以及改变监听的,在实际开发中这种机制非常有用。

但包括Observable接口在内的这一系列类,均是由javafx所引入,在javafx包下。为了避免包引入看起来不论不类、也加深自己的理解,以SimpleIntegerProperty为例学习下实现原理。

二、使用示例

2.1 属性绑定示例

例1:javafx窗口界面中有一个圆,若想实现无论怎么拉伸,使圆均处于窗口中心位置的话,就可以使用绑定机制

    circle.centerXProperty().bind(stage.widthProperty().divide(2));

例2:小demo,让一个属性始终为另一个的一半

    SimpleIntegerProperty half = new SimpleIntegerProperty();
    SimpleIntegerProperty target = new SimpleIntegerProperty(8);
    half.bind(target .divide(2));
    System.out.println(half.get());

2.2 修改监听示例

需求:比如做响应式页面,当窗口宽度小于某个阈值时,执行某些操作。

    stage.widthProperty().addListener(new ChangeListener() {
        @Override
        public void changed(ObservableValue observable, Number oldValue, Number newValue) {
        if(newValue < 333)
        System.out.println("当前小于333");
        }
    });

三、机制概述

从表现上来看有两个特性

3.1 属性绑定(property binding)

允许同步两个属性的值,其中一个修改时,另一个属性的获取值会同步更新。
有两个绑定方法 bindbinBidirectional 分别对应两种绑定方式:

  • 单向绑定(Unidirectional binding):比如属性A绑定B,当B属性改变时,A的获取值会同步更新。且A将无法手动修改,只能修改B,否则会报异常RuntimeException: A bound value cannot be set
  • 双向绑定(Bidirectional binding):只要A、B其中一个修改,另一个的获取值将同步更新。

3.2 修改监听(ChangeListener)

为属性设置修改事件监听器,当属性值修改时,自动回调传入监听器方法。

四、实现原理解析

与我们熟知的观察者模式不同,通过源码我们可以看到在Observable接口中定义的是InvalidationListener类型监听器添加方法,而在ObservableValue接口中才定义了ChangeListener

由此引出疑问:什么是失效监听器(Invalidation Listener)?这涉及到JavaFx属性绑定的 延迟计算(lazy evaluation) 机制。

4.1 属性绑定原理

如 A.bind(B),当绑定目标对象B更新时,并不是通过修改A自身的值来实现同步的。而是在使用bind()进行绑定时,通过传入的绑定目标对象(无论是直接的 SimpleIntegerProeprty 或是通过 add、divide等方法返回的IntegerBinding对象)来构建维护 observable 字段。当调用get()尝试获取A的值时,则调用 observable.get()来获取。

注:由上述属性绑定逻辑我们可知,当绑定目标改变发生时并不直接重新计算,而是只有当此值被get()请求时,才调用 observable.get() 来返回最新值。因此在刚发生绑定操作或绑定目标修改后,还未get()使用前,则存在“失效”状态【具体逻辑参考后面源码解析】

4.2 监听机制原理

调用 addListener 时,通过自身字段ExpressionHelper helper 来附加存储监听器,当属性值修改或是解绑时,则通过 markInvalid() 方法调用 ExpressionHelper.fireValueChangedEvent(helper),来回调所有附加的监听器方法。

五、源码解析

绝大部分字段(即类的成员变量,为了避免与 '属性' 混淆,用字段一词代替)都定义在抽象类 IntegerPropertyBase 中,而SimpleIntegerProperty则继承自该抽象类。

        public abstract class IntegerPropertyBase extends IntegerProperty {

            private int value;  //在非绑定情况下,类本身的值
        
            //当使用bind()方法时,以传入对象为基础构建的目标绑定实例【具体逻辑参考下面bind方法源码】,在get()等方法中用到,参考下面方法解释。
            private ObservableIntegerValue observable = null;  
        
            //失效监听器,进行bind()时,则自动构建该监听器。
            //作用:【参考下面Listener源码】作为InvalidationListener添加到绑定目标observable中,实现当observable改变时,将本实例设置为Invalid(失效)状态的效果。
            //创建时机:【参考下面bind代码】当使用bind()方法时,以自身实例(this)作为参数构建 Listener 对象【Listener 为内部类继承自 InvalidationListener,参考下面代码】
            private InvalidationListener listener = null;  
        
            //当前实例是否有效,创建实例时默认为有效
            private boolean valid = true;
        
            //用于存储添加的失效或改变监听器
            private ExpressionHelper helper = null;
        
            // 获取值的get方法,其逻辑为:若进行过绑定,则调用observable来获取绑定值;若未绑定,则返回本身的值
            @Override
            public int get() {
                valid = true;
                return observable == null ? value : observable.get();
            }
        
            // 根据observable是否为空判断当前是否绑定
            @Override
            public boolean isBound() {
                return observable != null;
            }
        
            // 解绑方法
            @Override
            public void unbind() {
                if (observable != null) {
                    value = observable.get(); //解绑时将自身值更新到最新状态
                    observable.removeListener(listener); //移除失效监听器【关于"失效监听器"参考下面源码解释】
                    observable = null; //将绑定目标observable置null
                }
            }
        
            //绑定方法
            @Override
            public void bind(final ObservableValue rawObservable){
                ObservableIntegerValue newObservable;
                // …省略newObservable的构建代码。逻辑为:若传入对象是ObservableIntegerValue类型实例则为传入对象本身;否则则以传入对象为基础构建IntegerBinding实例)
        
                if (!newObservable.equals(observable)) {
                    unbind(); //先解绑,若本身未绑定则等于没执行
                    observable = newObservable;  //为绑定目标字段赋值
                    if (listener == null) {
                        listener = new Listener(this); //以自身实例为参数构建Listener失效监听器
                    }
                    observable.addListener(listener);//将失效监听器添加到绑定目标中
                    markInvalid(); //设置为失效状态(因为延迟计算机制)
                }
            }
        
            //标记为失效的方法
            private void markInvalid() {
                if (valid) {
                    valid = false;
                    invalidated();  //默认为空实现,子类继承时可自行重写实现(SimpleIntegerProperty未重写实现)
                    fireValueChangedEvent();  //激活hepler
                }
            }
            
            //官方注释:给所有注册的InvalidationListener和ChangeListeners发送通知(即开始回调各个监听器方法)
            protected void fireValueChangedEvent() {
                ExpressionHelper.fireValueChangedEvent(helper);
            }
        
            //属性修改方法
            //修改后会调用markInvalid()方法标记为失效,并回调所有附加的监听器
            @Override
            public void set(int newValue) {
                if (isBound()) {
                    throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
                            getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
                }
                if (value != newValue) {
                    value = newValue;
                    markInvalid();
                }
            }
        
            //内部类 失效监听器,在bind()方法中被使用
            private static class Listener implements InvalidationListener {
                private final WeakReference wref;
                public Listener(IntegerPropertyBase ref) {
                    this.wref = new WeakReference<>(ref);
                }
                //失效监听器逻辑很简单,直接调用传入实例的markInvalid方法
                //即实现了:当绑定目标修改时,则回调该监听器来将"绑定发起属性"置为失效。
                @Override
                public void invalidated(Observable observable) {
                    IntegerPropertyBase ref = wref.get();
                    if (ref == null) {
                        observable.removeListener(this);
                    } else {
                        ref.markInvalid(); 
                    }
                }
            }
        
            //…
        }

参考
https://www.dummies.com/programming/java/javafx-binding-properties/
http://www.javafxchina.net/blog/2015/08/javafx-properties-binding/

转载于:https://www.cnblogs.com/simpleito/p/10877826.html

你可能感兴趣的:(java)