2017-08-25 14 views
6

Tôi có một đơn giản, giao diện chức năng:Làm thế nào để bao quanh lời gọi lại với một đoạn mã khác một cách tốt nhất?

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

tôi thực hiện nhiều hoạt động không đồng bộ như:

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

Kết quả phải được xử lý trên một main thread. Cho rằng, tôi có một phương pháp JavaFX:

Platform.runLater(Runnable task);

Mà làm cho mã của tôi một mớ hỗn độn như thế này (và mô hình này được lặp lại trong 50 phương pháp khác):

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

Tôi muốn để bọc từng lời gọi lại với Platform.runLater(...). Điều duy nhất tôi đã đưa ra là một default method:

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

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

Và sau đó, tôi chỉ cần gọi result.invokeOnMain(list).

Có cách tiếp cận tốt hơn cho các mẫu như thế này không?

Trả lời

2

Nó đánh bại trong một cách nào đó các default method intention:

phương pháp mặc định cho phép bạn thêm chức năng mới cho giao diện các thư viện của bạn và đảm bảo khả năng tương thích nhị phân với mã viết cho các phiên bản cũ của các giao diện này.

Tại sao không đặt mã này trong một lớp học cụ thể:

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

Và từ phía khách hàng, bạn cũng có thể sử dụng một static import cho PlatformUtil.invoke để tiếp tục giảm mã tấm nồi hơi.

Nó có thể cung cấp:

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

Tất nhiên bạn có thể làm điều tương tự với một phương pháp dụ.

+0

Chỉ cần một (off-topic) lưu ý rằng văn bản bạn trích dẫn từ Oracle không phải là một tuyên bố đúng. Việc thêm một phương thức mặc định vào một giao diện hiện có không * đảm bảo * tương thích nhị phân: có những trường hợp mà nó có thể phá vỡ mã đã biên dịch hiện có. (Cụ thể, nếu một lớp đang tồn tại thực hiện giao diện được đề cập và cũng thực hiện một giao diện khác đã định nghĩa một phương thức mặc định có cùng chữ ký, bạn sẽ phá vỡ lớp đó bằng cách thêm phương thức mặc định vào giao diện của bạn.) –

+0

Bạn nói đúng. Nó nên được đề cập. Bây giờ, để công bằng, về mặt xác suất, nó thực sự không phổ biến. – davidxxx

1

Sau khi sử dụng Decorator Pattern, bạn thấy rằng bạn không bao giờ cần thay đổi phương thức getSubfolders. và sau đó bạn có thể viết một hệ thống lớp được xác định rõ, khi bạn đặt các thành phần vào gói riêng của mình, ví dụ:

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

Tất cả mọi thứ là tốt, chỉ có một nơi bạn cần thay đổi là nơi bạn gọi getSubfolders Ví dụ:

Callback<T> origin = ... 

getSubfolders(folder, runOnMainThread(origin)); 

Nếu bạn thấy rằng bạn cần phải gọi runOnMainThread nhiều lần trong mô-đun giao diện người dùng của bạn, có lẽ bạn sẽ mất một số khái niệm miền trong lớp UI của bạn. bạn nên trích xuất khái niệm miền mới bằng các lớp hoặc giao diện mới cho những thứ đó, ví dụ: FolderExplorer.

3

Bạn có thể tiến xa hơn một bước so với các đề xuất trong các câu trả lời khác và tóm tắt Platform.runLater() dưới dạng java.util.concurrent.Executor (sau cùng, điều gì đó thực hiện Runnable giây).

Vì vậy, bạn có thể làm điều này:

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

Và bây giờ mã ví dụ của bạn trở thành:

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

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

Những điều tốt đẹp ở đây là bạn có thể sử dụng cùng lớp Invoker với Swing: chỉ cần tạo một

new Invoker(asyncExecutor, SwingUtilities::invokeLater) 

[Lưu ý: Tôi đã không tự mình làm ra điều này; Tôi thấy nó trong một bài đăng trên đây một vài năm trở lại. Tôi không thể tìm thấy bài đăng đó ngay bây giờ để cung cấp tín dụng phù hợp, nhưng sẽ chỉnh sửa thông tin này nếu tôi cố gắng khai thác nó. Nếu người ban đầu đăng ý tưởng này thấy điều này, hãy bình luận và tôi sẽ ghi có bạn]

+1

Không biết nếu điều này sẽ được coi là tốt hơn, nhưng bạn có thể thay đổi việc thực hiện 'invoke' thành' CompletableFuture.supplyAsync (task, bgExec) .thenAcceptAsync (callback, fgExec); '. Có thể dễ đọc hơn? Lưu ý: Tôi không thông thạo 'CompletableFuture' /' CompletionStage' vì vậy hãy sửa tôi nếu điều này có thể dẫn đến các sự cố không mong muốn. – Slaw

Các vấn đề liên quan