Java 如何避免累积回拨或;“地狱”;?

Java 如何避免累积回拨或;“地狱”;?,java,Java,我的程序大量使用(可能)异步调用,其中返回值不立即可用,因此有很多类似的方法: // 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) { // Retri

我的程序大量使用(可能)异步调用,其中返回值不立即可用,因此有很多类似的方法:

// 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);
}
@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()
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)
FooHandler fooHandler = new FooHandler();
FooCallback fooCallback = new BarHandler();
fooHandler.addFooCallback(fooCallback);
...
barHandler.add(new MyZorbResponseHandler());
然而,由于有许多事情依赖于最后一次呼叫,例如,它们开始堆积起来:

// Get foo number 42
fooHandler.getFoo(42, new GetFooCallback()
{
    @Override
    public void onResult(final Foo foo)
    {
        // Foo 42 retrieved, get a related Bar object
        barHandler.getBar(foo.getBarID(), new GetBarCallback()
        {
            @Override
            public void onResult(final Bar bar)
            {
                // Send a new Zorb(foo, bar) to server
                zorbHandler.sendZorbToServer(new Zorb(foo, bar), new ZorbResponseHandler()
                {
                    @Override
                    public void onSuccess()
                    {
                        // Keep dancing
                    }

                    @Override
                    public void onFailure()
                    {
                        // Drop the spoon
                    }
                });
            }
        });
    }
});

这“管用”,但当桩不断增长,很难跟踪发生了什么时,它开始感到相当讨厌。所以,问题是:我如何避免这种堆积?正如我在谷歌上搜索过的“回调地狱”,很多地方都建议使用RxJava或RxAndroid,但我还没有找到任何示例来说明如何将上述示例转换为更简洁的整体。

我不确定这是否会有所帮助,但这里有一个设计思想可能会启发解决方案。灵感来自Python的Twisted。最终结果如下(解释如下):

在这个代码段中,
函数实际上是一个生成对象的函数
foodhandler.getFoo
,而不是接受回调,然后用结果调用回调,而是返回延迟对象。可以将回调添加到延迟对象,当对象激发时调用该对象。因此,不是像这样的
getFoo

// 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);
}
@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()
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)
FooHandler fooHandler = new FooHandler();
FooCallback fooCallback = new BarHandler();
fooHandler.addFooCallback(fooCallback);
...
barHandler.add(new MyZorbResponseHandler());
看起来是这样的:

// 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);
}
@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()
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)
FooHandler fooHandler = new FooHandler();
FooCallback fooCallback = new BarHandler();
fooHandler.addFooCallback(fooCallback);
...
barHandler.add(new MyZorbResponseHandler());
延迟似乎与(比较
addCallback
然后apply
)相似,如果不相同的话

然后,
defer.inlineCallbacks
通过如下操作神奇地连接所有回调和所有延迟:

  • 启动发电机,获得第一个延迟
  • 将回调添加到第一个Deferred,它获取结果并将结果发送到生成器中
  • 重复下一个延迟和下一个结果,依此类推,直到生成器耗尽


也许您可以在Java中实现类似的功能-参见生成器等价物(您必须修改
yield
以返回值),以及
延迟.inlineCallbacks的实现,具体取决于具体的用例和需求,可能有不同的编程方法或范例适合您的特定任务。这些可能包括各种形式的或(例如,使用您已经提到的RxJava),或者通常是某种形式的。甚至可以使用一个用于交换“事件”(在您的例子中是计算结果)的

然而,它们中的大多数是建立在特定的基础设施上的,特别是在需要对系统进行相应建模的库上。例如,您的回调可能必须实现特定的接口,才能收到有关(异步)结果的通知。此外,在某些地方,您必须进行“连接”:您必须指定在结果可用时应调用哪个特定回调—即使这可能与为某个“事件类型”注册此回调一样简单


当然,可以手动为此构建必要的基础设施。您可以相应地实现
foodhandler
类:

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);
    }
}
这基本上归结为不将回调作为方法参数传递,而是在专用列表中维护它们。这至少使接线更简单、更简洁。但它仍将使总体结构变得相当“僵化”,而不像与专用基础设施(该基础设施以更抽象的形式对“侦听器”和信息交换概念进行建模)那样松散耦合


如果您的主要目标是避免您提到的代码可读性和可维护性方面的“堆积”(或者简单地说:缩进级别),那么一种方法可以是简单地将新回调实例的创建提取到实用程序方法中

尽管这肯定不能替代成熟、复杂的消息传递体系结构,但根据您提供的代码,下面是一个小示例:

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();
}

这是一个有争议的话题,有很多观点;让我把重点放在一个具体的解决方案上,并试图论证为什么它比回调更好

解决方案通常称为未来、承诺等;关键是异步函数不接受回调;相反,它返回一个表示正在进行的异步操作的future/promise。这里,让我使用术语
Async
来表示异步操作,因为我觉得这是一个更好的名称

而不是接受回调

    void func1(args, Callback<Foo>)
在这方面,
Async
至少不比回调解决方案差

通常,异步不与回调一起使用;相反,异步是像

func2(args)
    .then(foo->func3(...))
    .then(...)
    ...
首先要注意的是,这是平面的,而不是回调嵌套


除了美学上的原因之外,
Async
还有什么大不了的?有些人认为,它本质上与回调相同,只是使用了另一种语法。然而,有一个很大的区别

OOP最大的秘密是你可以用对象来表示东西。。。这是什么秘密?那不是OOP-101吗?但事实上,人们常常忘记这一点

当我们有一个异步对象来表示异步操作时,我们的程序可以轻松地处理该操作,例如,通过API传递操作;取消操作或设置超时;将多个连续/并行动作组合为一个动作;等等。这些事情可以在回调解决方案中完成,但是,这要困难得多,而且不直观,因为程序无法处理任何有形的对象;相反,行动的概念只存在于我们的头脑中


唯一真正的评判标准是一个解决方案是否只适用于您的应用程序。这是我的,看看是否有帮助。

这不是实质性的改进,但使用Java 8 lambdas,您至少可以减少这里的样板代码。使用