如何以最佳方式用另一段代码包围回调调用?

时间:2017-08-25 20:44:32

标签: java asynchronous javafx callback java-8

我有一个简单实用的界面:

public interface Callback<T> {
    void invoke(T param);
}

我做了很多异步操作,如:

public void getSubfolders(Folder folder, Callback<FolderList> result){
    asyncExecutor.submit(() -> {
        FolderList list = folder.get_SubFolders();
        result.invoke(list);
    });
}

必须在main thread上处理结果。为此,我有一个JavaFX方法:

Platform.runLater(Runnable task);

这使得我的代码像这样一个混乱(并且这种模式在其他50种方法中重复):

public void getSubfolders(Folder folder, Callback<FolderList> result){
     asyncExecutor.submit(() -> {
         FolderList list = folder.get_SubFolders();
         Platform.runLater(() -> result.invoke(list));
     });
}

我想用Platform.runLater(...)包装每个回调调用。 我想出的唯一一件事是default method

public interface Callback<T> {
    void invoke(T param);

    default void invokeOnMain(T param){
        Platform.runLater(() -> invoke(param));
    }
}

然后,我只是致电result.invokeOnMain(list)

对于像这样的模式,是否有更好的方法?

3 个答案:

答案 0 :(得分:3)

您可以比其他答案中的建议更进一步,并将Platform.runLater()抽象为java.util.concurrent.Executor(毕竟,它是执行Runnable s的东西。)< / p>

所以你可以这样做:

import java.util.concurrent.Executor ;
import java.util.function.Consumer ;
import java.util.function.Supplier ;

public class Invoker {

    private final Executor backgroundExecutor ;
    private final Executor foregroundExecutor ;

    public Invoker(Executor backgroundExecutor, Executor foregroundExecutor) {
        this.backgroundExecutor = backgroundExecutor ;
        this.foregroundExecutor = foregroundExecutor ;
    }

    public <T> void invoke(Supplier<? extends T> task, Consumer<? super T> callback) {
        backgroundExecutor.execute(() -> {
            T result = task.get();
            foregroundExecutor.execute(() -> callback.accept(result));
        });
    }
}

现在您的示例代码变为:

Invoker invoker = new Invoker(asyncExecutor, Platform::runLater);
// ...

invoker.invoke(folder::getSubFolders, result::invoke);

这里的好处是你可以在Swing中使用相同的Invoker类:只需创建一个

new Invoker(asyncExecutor, SwingUtilities::invokeLater)

[注意:我自己没有提出这个问题;几年前我在这里的帖子中看到了它。我现在找不到该帖子给予适当的信任,但如果我设法将其挖掘出来,我会编辑它。如果最初发布此想法的人看到了这个,请发表评论,我会相信你的信息]

答案 1 :(得分:2)

它以某种方式击败了default method intention

  

默认方法使您可以向界面添加新功能   您的库并确保与编写的代码的二进制兼容性   对于那些接口的旧版本。

为什么不将此代码放在特定的类中:

public class PlatformUtil {
   public static <T> void invoke(Callback<T> result, T param){
     Platform.runLater(() -> result.invoke(param));
   }
}

从客户端,您还可以使用static import PlatformUtil.invoke来进一步减少锅炉铭牌代码。

它可以给:

import static PlatformUtil.invoke;
...
public void getSubfolders(Folder folder, Callback<FolderList> result){
     asyncExecutor.submit(() -> {
         FolderList list = folder.get_SubFolders();
         invoke(result, list);
     });
}

当然你可以用实例方法做同样的事情。

答案 2 :(得分:1)

使用Decorator Pattern后,您发现根本不需要更改getSubfolders方法。然后,当您将组件放入其自己的包中时,您可以编写一个定义良好的层系统,例如:

//                   v--- move the UiCallback into ui package
package com.projectx.ui;

public class UiCallback<T> implements Callback<T> {
     private final Callback<T> target;
     private UiCallback(Callback<T> target){
       this.target = Objects.requireNonNull(target);
     } 

     public void invoke(T param){
         Platform.runLater(() -> target.invoke(param));
     }

     public static <T> Callback<T> runOnMainThread(Callback<T> source){
        return source instanceof UiCallback? source : new UiCallback<>(source);  
     }
}

一切都很好,只需要改变一个地方即可调用getSubfolders,例如:

Callback<T> origin = ...

getSubfolders(folder, runOnMainThread(origin));

如果您发现需要在UI模块中多次调用runOnMainThread,可能会在UI层中丢失一些域概念。你应该通过新的类或接口来提取新的域概念,例如:FolderExplorer