赋值联动

735 阅读4分钟
关键词:联动
本文约500字,建议阅读时间3分钟。


                              


联动

程序员朋友有没有过这样一种编程经验:当一个变量被重新赋值时,和它相关联的另外几个变量也需要跟着更新?我们不妨称这个变量为原生变量,称相关联的变量为这个原生变量的依赖变量

假定在原生变量被更新的时候,其依赖变量的更新在逻辑上是必要的,你会不会考虑将这个必然的联动操作封装起来呢?基于原生变量可能在多个地方被更新这个前提,我会考虑做这件事。理由是:如果已经有多个地方修改了原生变量,同时也要求并且实现了联动操作,我们是可以预见可能还会有更多这样的代码出现的。

这个时候问题就来了,为每一处联动操作的地方都书写同样一份代码是不是重复劳动呢?退一步讲,即使我们不害怕代码臃肿冗余,谁又能保证每一次动了原生变量都会记得执行联动操作呢?

因此,我们需要将这件事作为一个整体包裹起来,外部只需要更新最原始的那个触发点—原生变量,剩下的事则交由这个包裹的内部自动完成。以下是用于封装原生变量和联动逻辑的示例接口,供你参考。

/**
 *  赋值包装器,可为一个匿名引用在赋值时绑定特定的操作
 */
public class AssignmentWrapper<T> {
    @Nullable 
    private T value;

    /**
     * 赋值前置操作接口
     */
    public interface IBeforeAssign {
        /**
         * 决定是否接受赋值
         *
         * @param value 赋值的右值
         * @return true表示接受赋值; false表示拒绝赋值
         */
        boolean claimAssign(@Nullable Object value);

        /**
         * 匿名引用被赋值前的操作
         *
         * @param value 赋值的右值
         */
        void beforeAssign(@Nullable Object value);
    }

    @Nullable 
    private IBeforeAssign beforeAssign;

    /**
     * 赋值后置操作接口
     */
    public interface IOnAssigned {
        /**
         * 匿名引用被赋值时的后置操作
         *
         * @param value 赋值的右值
         */
        void onAssigned(@Nullable Object value);
    }

    @NonNull
    private IOnAssigned onAssigned;

    public AssignmentWrapper(@NonNull IOnAssigned onAssigned) {
        this.onAssigned = onAssigned;
    }

    public AssignmentWrapper(@NonNull IOnAssigned onAssigned, 
                             @Nullable IBeforeAssign beforeAssign) {
        this.onAssigned = onAssigned;
        this.beforeAssign = beforeAssign;
    }

    /**
     * 判定包装器是否能够接受给定值
     *
     * @param value 赋值右值
     * @return true表示能够接受给定值; false表示不能接受给定值
     */
    public boolean accept(@Nullable T value) {
        return beforeAssign != null && beforeAssign.claimAssign(value);
    }

    /**
     * 将给定对象赋值给包装器内部的匿名引用
     *
     * @param value 赋值右值
     * @return true表示赋值成功; false表示赋值失败
     */
    public boolean assign(@Nullable T value) {
        return assign(value, true, true, true);
    }

    /**
     * 将给定对象赋值给包装器内部的匿名引用
     *
     * @param value           赋值右值
     * @param doClaimAssign   指定赋值是否需要经过确认,
     *                        如果需要经过确认,则要求包装器指定了前置操作接口并实现了
                              决定是否接受赋值的方法
     * @param doBeforeAssign  指定是否执行赋值前置逻辑
     * @param doAfterAssigned 指定是否执行赋值后置逻辑
     * @return                true表示赋值成功; false表示赋值失败,
     *                        只有指定了需要赋值确认的情况下赋值才有可能失败
     */
    public boolean assign(@Nullable T value, 
                          final boolean doClaimAssign, 
                          final boolean doBeforeAssign,
                          final boolean doAfterAssigned) {
        if (beforeAssign != null) {
            if (doClaimAssign) {
                if (!beforeAssign.claimAssign(value)) {
                    return false;
                }
            }

            if (doBeforeAssign) {
                beforeAssign.beforeAssign(value);
            }
        }

        this.value = value;
        if (doAfterAssigned) {
            onAssigned.onAssigned(value);
        }

        return true;
    }

    @Nullable
    public T value() {
        return this.value;
    }
}

Swift 语言中有 didSetwillSet 这样的属性包装器,分别用于在属性赋值之后和赋值之前运行代码。这是一个很常用的语法特性,在 Apple 的各种新的技术框架中已经被广泛使用。Java,Kotlin 语言虽然没有内建的机制。但是我们可以自行实现。

Java的实现方式就像前面提供的方案那样,不再赘述。Kotlin也有几种方案:

标准库中有一个 observable(...) 的delegate,允许你观察属性的改变

var foo: String by Delegates.observable("bar") { property, old, new - >    
    println("$property has changed from $old to $new") 
}

在这里,"bar" 是属性foo 的初始值,其后的 lambda 表达式在每一次属性被赋值时都会被调用。还有一个叫 vetoable(...) 的 delegate,允许你阻止属性值的改变,就像上文中AssignmentWrapper 在 beforeAssign 中可以做一个是否接受赋值的判定。

你还可以通过自定义属性的setter,在实际值改变之前或者之后执行任意代码,这个方式跟上文Java的方式是一个意思了。

var foo: String = "foo" set(value: String) {         
    beforeAssign()         
    field = value         
    afterAssign() 
}


我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~