如何安全地装饰现有的回调?
How can I safely decorate an existing callback?
假设我正在使用以下回调 API:
/**
* Registers a new action which will be run at some later time on
* some other thread, clearing any previously set callback action.
*
* @param callback an action to be run later.
* @returns the previously registered action.
*/
public Runnable register(Runnable callback);
我想注册我自己的操作,但我想保留任何设置的行为。换句话说,我希望我的操作看起来像:
new Runnable() {
public void run() {
// do my work
originalCallback.run();
}
}
向我的 Runnable
提供 originalCallback
的最干净的方法是什么?
我想到的幼稚解决方案有引入 window 时间的风险,其中 originalCallback
在调用回调时不可用,或者涉及一些复杂的锁定。
经过更多挖掘,我发现了 Guava 的 SettableFuture
and Java 8's CompletableFuture
。我会把我的 BlockingSupplier
留给后代,但这些 Future
实现中的任何一个都将更标准,并且工作方式相同。
您基本上需要一个带阻塞 get()
方法的支架 class。像这样:
public class BlockingSupplier<E> implements Supplier<E> {
private final CountDownLatch latch = new CountDownLatch(1);
private volatile E value;
public synchronized void set(E value) {
checkState(latch.getCount() > 0, "Cannot call set more than once.");
this.value = value;
latch.countDown();
}
@Override
public E get() {
latch.await(); // will block until set() is called
return value;
}
}
然后就可以这样使用了:
BlockingSupplier<Runnable> supplier = new BlockingSupplier<>();
// Pass the BlockingSupplier to our callback
DecoratorCallback myAction = new DecoratorCallback(supplier);
// Register the callback, and set the BlockingSupplier to the old callback
supplier.set(register(myAction));
其中 DecoratorCallback
的 run()
看起来像这样:
public void run() {
// do my work
// This will block until supplier.set() returns
originalCallbackSupplier.get().run();
}
正如 durron597 提到的,有更好的方法来设计回调 API,但考虑到问题中的 API,这似乎是合理的。
这是获得 API 的糟糕方式。 Single Responsibility Principle 适用于此。按照您现在的操作方式,您的可运行对象负责:
- 无论其他工作是什么
- 正在调用另一个回调。
您的 API 设计本身就违反了 SRP!每个使用这个 API 的 class 都已经从一开始就坏了。
幸运的是,您可以使用 Guava 的 ListenableFuture
轻松解决这个问题,其工作原理如下:
- 提交任务
- 取回
ListenableFuture
对象
- 使用
Futures.addCallback
附加回调
这样做可以确保您的系统将管理多线程和 happensBefore 关系的代码放在一个地方,而实际执行工作的代码放在另一个地方。
假设我正在使用以下回调 API:
/**
* Registers a new action which will be run at some later time on
* some other thread, clearing any previously set callback action.
*
* @param callback an action to be run later.
* @returns the previously registered action.
*/
public Runnable register(Runnable callback);
我想注册我自己的操作,但我想保留任何设置的行为。换句话说,我希望我的操作看起来像:
new Runnable() {
public void run() {
// do my work
originalCallback.run();
}
}
向我的 Runnable
提供 originalCallback
的最干净的方法是什么?
我想到的幼稚解决方案有引入 window 时间的风险,其中 originalCallback
在调用回调时不可用,或者涉及一些复杂的锁定。
经过更多挖掘,我发现了 Guava 的 SettableFuture
and Java 8's CompletableFuture
。我会把我的 BlockingSupplier
留给后代,但这些 Future
实现中的任何一个都将更标准,并且工作方式相同。
您基本上需要一个带阻塞 get()
方法的支架 class。像这样:
public class BlockingSupplier<E> implements Supplier<E> {
private final CountDownLatch latch = new CountDownLatch(1);
private volatile E value;
public synchronized void set(E value) {
checkState(latch.getCount() > 0, "Cannot call set more than once.");
this.value = value;
latch.countDown();
}
@Override
public E get() {
latch.await(); // will block until set() is called
return value;
}
}
然后就可以这样使用了:
BlockingSupplier<Runnable> supplier = new BlockingSupplier<>();
// Pass the BlockingSupplier to our callback
DecoratorCallback myAction = new DecoratorCallback(supplier);
// Register the callback, and set the BlockingSupplier to the old callback
supplier.set(register(myAction));
其中 DecoratorCallback
的 run()
看起来像这样:
public void run() {
// do my work
// This will block until supplier.set() returns
originalCallbackSupplier.get().run();
}
正如 durron597 提到的,有更好的方法来设计回调 API,但考虑到问题中的 API,这似乎是合理的。
这是获得 API 的糟糕方式。 Single Responsibility Principle 适用于此。按照您现在的操作方式,您的可运行对象负责:
- 无论其他工作是什么
- 正在调用另一个回调。
您的 API 设计本身就违反了 SRP!每个使用这个 API 的 class 都已经从一开始就坏了。
幸运的是,您可以使用 Guava 的 ListenableFuture
轻松解决这个问题,其工作原理如下:
- 提交任务
- 取回
ListenableFuture
对象 - 使用
Futures.addCallback
附加回调
这样做可以确保您的系统将管理多线程和 happensBefore 关系的代码放在一个地方,而实际执行工作的代码放在另一个地方。