听一个可观察的实例并对它的变化做出反应很有趣。 做一些必要的事情来打断或结束这种聆听会变得很有趣。 让我们看看问题的根源和解决方法。
总览
这篇文章将首先讨论这种情况,然后再讨论常见的方法和问题所在。 然后,它将提供解决大多数问题的简单抽象。
尽管示例使用Java,但许多其他语言也存在缺陷。 提出的解决方案可以应用于所有面向对象的语言。 那些懒于自己在Java中实现抽象的人可以使用LibFX 。
情况
假设我们想听听属性值的变化。 这很简单:
不支持删除的简单案例
private void startListeningToNameChanges(Property<String> name) {
name.addListener((obs, oldValue, newValue) -> nameChanged(newValue));
}
现在假设我们要在特定间隔内中断监听或完全停止监听。
保持参考
解决此问题的最常见方法是保留对侦听器的引用,并保留对周围属性的引用。 根据具体的用例,实现会有所不同,但是它们都可以归结为以下形式:
默认方式删除侦听器
private Property<String> listenedName;
private ChangeListener<String> nameListener;
...
private void startListeningToNameChanges(Property<String> name) {
listenedName = name;
nameListener = (obs, oldValue, newValue) -> nameChanged(newValue);
listenedName.addListener(nameListener);
}
private void stopListeningToNameChanges() {
listenedName.removeListener(nameListener);
}
尽管这看起来不错,但我确信这实际上是一个糟糕的解决方案(尽管是默认解决方案)。
首先,额外的引用使代码混乱。 很难让他们表达出为什么要留在身边的意图,因此它们降低了可读性。
其次,它们通过向类添加新的不变式来增加复杂性:该属性必须始终是添加了侦听器的属性。 否则,对removeListener
的调用将无提示地执行任何操作,并且在将来的更改时仍将执行该侦听器。 放开这可能是讨厌的。 如果类很短,则坚持不变性是容易的,但如果变得越来越复杂,则可能成为问题。
第三,引用(尤其是该属性的引用)邀请与它们进行进一步的交互。 这可能不是故意的,但没有任何办法阻止下一个开发人员继续这样做(请参阅第一点)。 而且,如果有人确实开始对该物业进行操作,第二点将成为非常现实的风险。
这些方面已经使它无法成为默认解决方案。 但是还有更多! 在许多类中必须这样做会导致代码重复。 最后,上面的实现包含一个竞争条件。
侦听器句柄
大多数问题来自直接在需要中断/结束侦听的类中处理可观察对象和侦听器。 这是不必要的,所有这些问题都可以通过一个简单的抽象来解决: ListenerHandle
。
ListenerHandle
public interface ListenerHandle {
void attach();
void detach();
}
ListenerHandle保留对可观察对象和侦听器的引用。 在调用attach()
或detach()
它要么将侦听器添加到可观察对象,要么将其删除。 为了将此语言嵌入语言,当前将侦听器添加到可观察对象的所有方法都应返回该组合的句柄。
现在剩下要做的就是针对所有可能的情况实际实现句柄。 或者说服那些开发您喜欢的编程语言的人来做。 这留给读者练习。
注意,这解决了上面提到的所有问题,除了争用条件之外。 有两种解决方法:
- 处理实现可能本质上是线程安全的
- 可以实现一个同步装饰器
LibFX中的ListenerHandles
作为Java开发人员,您可以使用LibFX ,它支持三个级别的侦听器句柄。
功能了解ListenerHandles
添加侦听ListenerHandle
时, LibFX的所有可实现此功能而不与Java API冲突的功能都会返回一个ListenerHandle
。
将“ ListenerHandle”获取到“ WebViewHyperlinkListener”
WebView webView;
ListenerHandle eventProcessingListener = WebViews
.addHyperlinkListener(webView, this::processEvent);
JavaFX实用程序
由于LibFX与JavaFX有紧密的联系(可能会想到!),它提供了一个实用程序类,该类将侦听器添加到可观察对象并返回句柄。 这适用于JavaFX中存在的所有可观察/侦听器组合。
例如,让我们看一下ObservableValue<T>
/ ChangeListener<? superT>
的组合ChangeListener<? superT>
ChangeListener<? superT>
:
'ListenerHandles'中的一些方法
public static <T> ListenerHandle createAttached(
ObservableValue<T> observableValue,
ChangeListener<? super T> changeListener);
public static <T> ListenerHandle createDetached(
ObservableValue<T> observableValue,
ChangeListener<? super T> changeListener);
ListenerHandleBuilder
在所有其他情况下,即对于上面未涵盖的任何可观察/侦听器组合,可以使用构建器来创建手柄:
为自定义类创建“ ListenerHandle”
// These classes do not need to implement any special interfaces.
// Their only connection are the methods 'doTheAdding' and 'doTheRemoving',
// which the builder does not need to know about.
MyCustomObservable customObservable;
MyCustomListener customListener;
ListenerHandles
.createFor(customObservable, customListener)
.onAttach((obs, listener) -> obs.doTheAdding(listener))
.onDetach((obs, listener) -> obs.doTheRemoving(listener))
.buildAttached();
反应式编程
虽然这不是关于反应式编程的文章 ,但仍应提及。 查看ReactiveX (用于许多语言,包括Java,Scala,Python,C ++,C#和更多语言)或ReactFX (或此介绍性文章 )以了解一些实现。
反射
我们已经看到,从可观察对象中删除侦听器的默认方法会产生许多危害,需要避免。 侦听器句柄抽象提供了解决许多问题的干净方法,而LibFX提供了一种实现。
翻译自: https://www.javacodegeeks.com/2015/01/dont-remove-listeners-use-listenerhandles.html