关键词:联动
本文约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 语言中有 didSet 和 willSet 这样的属性包装器,分别用于在属性赋值之后和赋值之前运行代码。这是一个很常用的语法特性,在 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及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
