问题
Suppose I'm working with the following 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);
I'd like to register my own action, but I want to preserve any set behavior. In other words I'd like my action to look something like:
new Runnable() {
public void run() {
// do my work
originalCallback.run();
}
}
What's the cleanest way to provide originalCallback to my Runnable?
The naive solutions that come to mind risk introducing a window of time where originalCallback isn't available when the callback is called, or that involve some intricate locking.
回答1:
After some more digging I found Guava's SettableFuture and Java 8's CompletableFuture. I'll leave my BlockingSupplier up for posterity, but either of these Future implementations would be more standard, and work just the same.
You basically need a holder class with a blocking get() method. Something like this:
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;
}
}
Then you can use it like so:
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));
Where DecoratorCallback's run() looks like this:
public void run() {
// do my work
// This will block until supplier.set() returns
originalCallbackSupplier.get().run();
}
As durron597 mentions there are better ways to design a callback API, but given the API in the question, this seems reasonable.
回答2:
This is a terrible way to have an API. The Single Responsibility Principle applies here. The way you are doing it now, your runnable is responsible for:
- Whatever it's other job is
- Calling the other callback.
You are breaking SRP inherently in your API design! Every class that uses this API is already broken from the getgo.
Fortunately, you can easily solve this problem with Guava's ListenableFuture, which works like this:
- Submit the task
- Get ListenableFuture object back
- Attach callbacks with Futures.addCallback
Doing it this way ensures that your system puts the code for managing multithreading and happensBefore relationships in one place, and the code that actually does the work in another.
来源:https://stackoverflow.com/questions/32193064/how-can-i-safely-decorate-an-existing-callback