我的程序大量使用(可能)异步调用,其中返回值不是立即可用的,因此有很多这样的方法:
self.resultSearchController.active
然而,由于有很多事情依赖于这样的最后一次调用,他们开始堆积起来:
// A simple callback interface
public interface GetFooCallback
{
void onResult(Foo foo);
};
// A method that magically retrieves a foo
public void getFoo(long fooID, GetFooCallback callback)
{
// Retrieve "somehow": local cache, from server etc.
// Not necessarily this simple.
Foo foo = ...;
callback.onResult(foo);
}
这"工作"但是当桩不断增长并且难以跟踪发生的事情时,它开始感觉相当icky。所以,问题是:我如何避免这种堆积?正如我用google搜索"回调地狱",许多地方建议使用RxJava或RxAndroid,但我还没有真正找到任何一个例子来说明如何将上述示例转换为更简洁的整体
答案 0 :(得分:9)
这是一个备受争议的话题,有很多意见;让我专注于一个特定的解决方案,并试图争论为什么它比回调更好。
解决方案通常被称为未来,承诺等;关键是异步函数不进行回调;相反,它返回表示正在进行的异步操作的future / promise。在这里,让我使用术语Async
来表示异步操作,因为我觉得这是一个更好的名称。
而不是接受回调
void func1(args, Callback<Foo>)
返回Async而不是
Async<Foo> func2(args)
Async
确实包含在完成时接受回调的方法,因此func2
可以与func1
func1(args, callback);
// vs
func2(args).onCompletion( callback )
在这方面,Async
至少不比回调解决方案差。
通常,Async不与回调一起使用;相反,Asyncs被链接为
func2(args)
.then(foo->func3(...))
.then(...)
...
首先要注意的是,与回调嵌套相反,这是平的。
除了审美原因,Async
有什么大不了的?有些人认为它与回调基本相同,只是使用另一种语法。但是,有很大的不同。
OOP的最大秘诀在于你可以用对象来表示东西......这是一个什么样的分泌?那不是OOP-101吗?但实际上人们常常忘记这一点。
当我们有一个Async对象来表示异步操作时,我们的程序可以轻松地执行操作,例如,通过API传递操作;取消操作或设置超时;将多个顺序/并行动作组成一个动作;这些事情可以在回调解决方案中完成,但是,它只是更加困难和非直观,因为没有程序可以使用的有形对象;相反,行动的概念只在我们的脑海中。
唯一真正的判断是解决方案是否仅仅是您的应用程序。这是我的async library,看看它是否有帮助。
答案 1 :(得分:1)
根据具体的使用案例和要求,可能有不同的编程方法或范例可能适合您的特定任务。这些可能包括各种形式的Message Passing或Actor models,Reactive Programming(例如,您已经提到的RxJava),或者通常是某种形式的Flow-based programming。甚至可以使用Event Bus来交换“事件”(在您的情况下是计算结果)。
但是,大多数都是基于某些基础架构构建的 - 特别是在需要系统建模的库上。例如,您的回调可能必须实现特定接口才能获得有关(异步)结果的通知。此外,在某些地方,您将需要进行“连线”:您必须指定在结果可用时应调用哪个特定回调 - 即使这可能就像为某个“事件”注册此回调一样简单型”。
当然,可以手动为此构建必要的基础架构。您可以相应地实施FooHandler
课程:
class FooHandler
{
// Maintain a list of FooCallbacks
private List<FooCallback> fooCallbacks = new ArrayList<>();
public void addFooCallback(FooCallback fooCallback)
{
fooCallbacks.add(fooCallbacks);
}
public void getFoo(long fooID)
{
// Retrieve "somehow": local cache, from server etc.
// Not necessarily this simple.
Foo foo = ...;
publish(foo);
}
// Offer a method to broadcast the result to all callbacks
private void publish(Foo foo)
{
for (FooCallback fooCallback : fooCallbacks)
{
fooCallback.onResult(foo);
}
}
}
class BarHandler implements FooCallback
{
// Maintain a list of BarCallbacks, analogously to FooHandler
...
@Override
public void onResult(Foo foo)
{
Object id = foo.getBarID();
Bar bar = getFrom(id);
publish(bar);
}
}
这样您就可以使用如下代码组装回调结构:
FooHandler fooHandler = new FooHandler();
FooCallback fooCallback = new BarHandler();
fooHandler.addFooCallback(fooCallback);
...
barHandler.add(new MyZorbResponseHandler());
这基本归结为不将回调作为方法参数传递,而是将它们保存在专用列表中。这至少使布线更容易,更简洁。但它仍然会使一般结构相当“僵化”,而不是像一个专门的基础设施那样松散耦合,以更抽象的形式模拟这个“听众”和信息交换概念。
如果您的主要目标是避免您提到的“堆积”,就代码的可读性和可维护性而言(或明显地:缩进级别),一种方法可能是简单地提取将新回调实例创建为实用程序方法。
虽然这肯定不能替代完整的,复杂的消息传递架构,但根据您提供的代码,这里有一个小例子:
class CompactCallbacks
{
public static void main(String[] args)
{
FooHandler fooHandler = null;
BarHandler barHandler = null;
ZorbHandler zorbHandler = null;
fooHandler.getFoo(42,
createFooCallback(barHandler, zorbHandler));
}
private static GetFooCallback createFooCallback(
BarHandler barHandler, ZorbHandler zorbHandler)
{
return foo -> barHandler.getBar(
foo.getBarID(), createGetBarCallback(zorbHandler, foo));
}
private static GetBarCallback createGetBarCallback(
ZorbHandler zorbHandler, Foo foo)
{
return bar -> zorbHandler.sendZorbToServer(
new Zorb(foo, bar), createZorbResponseHandler());
}
private static ZorbResponseHandler createZorbResponseHandler()
{
return new ZorbResponseHandler()
{
@Override
public void onSuccess()
{
// Keep dancing
}
@Override
public void onFailure()
{
// Drop the spoon
}
};
}
}
//============================================================================
// Only dummy classes below this line
class FooHandler
{
public void getFoo(int i, GetFooCallback getFooCallback)
{
}
}
interface GetFooCallback
{
public void onResult(final Foo foo);
}
class Foo
{
public int getBarID()
{
return 0;
}
}
class BarHandler
{
public void getBar(int i, GetBarCallback getBarCallback)
{
}
}
interface GetBarCallback
{
public void onResult(final Bar bar);
}
class Bar
{
}
class ZorbHandler
{
public void getZorb(int i, GetZorbCallback getZorbCallback)
{
}
public void sendZorbToServer(Zorb zorb,
ZorbResponseHandler zorbResponseHandler)
{
}
}
interface GetZorbCallback
{
public void onResult(final Zorb Zorb);
}
class Zorb
{
public Zorb(Foo foo, Bar bar)
{
}
public int getBarID()
{
return 0;
}
}
interface ZorbResponseHandler
{
void onSuccess();
void onFailure();
}
答案 2 :(得分:-2)
我不确定这是否会有所帮助,但这是一个可能激发解决方案的设计理念。灵感来自Python的Twisted。最终结果如下(解释如下):
Generator<Deferred> theFunc = Deferred.inlineCallbacks(new Generator<Deferred>() {
public void run() throws InterruptedException {
Foo foo = (Foo)yield(fooHandler.getFoo(42));
Bar bar = (Bar)yield(barHandler.getBar(foo.getBarID());
try {
yield(zorbHandler.sendZorbToServer(new Zorb(foo, bar));
} catch (Exception e) {
// Drop the sooon
return;
}
// Keep dancing
}
});
theFunc();
注意缺乏嵌套和回调地狱。
为了解释,我将解释它如何在Twisted中起作用。 使用Twisted,您的代码看起来像这样:
@defer.inlineCallbacks
def the_func():
foo = yield fooHandler.getFoo(42)
bar = yield barHandler.getBar(foo.getBarID())
try:
yield zorbHandler.sendZorbToServer(Zorb(foo, bar))
# Keep dancing
except Exception:
# Drop the spoon
the_func()
在此代码段中,the_func
实际上是generator,可生成Deferred个对象。 fooHandler.getFoo
,而不是采用回调,然后使用结果调用回调,而是返回一个Deferred对象。可以将回调添加到Deferred对象,该对象在对象触发时调用。因此,而不是getFoo
看起来像这样:
def getFoo(self, value, callback):
# do some stuff and then in another thread
doSomeStuffInThread(value, lambda foob: callback(foob))
# Usage:
getFoo(42, myCallback)
看起来像这样:
def getFoo(self, value):
deferred = Deferred()
doSomeStuffInThread(value, lambda foob: deferred.callback(foob))
return deferred
deferred = getFoo(42)
deferred.addCallback(myCallback)
延迟似乎与CompletableFutures相似(如果不相同)(将addCallback
与thenApply
进行比较)。
然后,defer.inlineCallbacks
通过执行以下操作,神奇地连接所有回调和所有延迟:
也许您可以在Java中实现类似的东西 - 请参阅this answer了解生成器等效项(您必须修改yield
以返回值),并Twisted source code表示实施defer.inlineCallbacks
。