Как реализовать обещание в Java, которое меняет тип вывода - PullRequest
0 голосов
/ 06 июля 2018

Я пытаюсь реализовать простую систему обещаний в Java. Я делаю это для специальных целей, поэтому, пожалуйста, не рекомендуйте никаких библиотек.

У меня проблема при попытке реализовать метод thenApply(), который принимает функцию в качестве параметра, аналогично тому, что имеет CompletableFuture , и поэтому возвращает обещание с другим типом.

Интерфейс обещания:

public interface Promise<T> {
    Promise<T> then(Consumer<T> handler);

    <U> Promise<U> thenApply(Function<T, U> handler);
}

Моя реализация пока:

public class PromiseImpl<T> implements Promise<T> {

    private List<Consumer<T>> resultHandlers = new ArrayList<>();

    public PromiseImpl(CompletableFuture<T> future) {
        future.thenAccept(this::doWork);
    }

    @Override
    public Promise<T> then(Consumer<T> handler) {
        resultHandlers.add(handler);
        return this;
    }

    @Override
    public <U> Promise<U> thenApply(Function<T, U> handler) {
        // How to implement here??? I don't have the result yet
        handler.apply(?);
    }

    private void onResult(T result) {
        for (Consumer<T> handler : resultHandlers) {
            handler.accept(result);
        }
    }

    private Object doWork(T result) {
        onResult(result);
        return null;
    }
}

Проблема в том, что я не знаю результат моего первоначального будущего в методе thenApply(), поэтому я не могу вызвать свой обработчик. Кроме того, я не хочу вызывать future.get(), потому что этот метод блокирует.

Как я мог сделать эту работу?

Ответы [ 3 ]

0 голосов
/ 06 июля 2018

Настоящая проблема заключается в дизайне вашего типа Promise. Он содержит набор обратных вызовов, которые должны быть вызваны по завершении. Это фундаментальная проблема (ограничение универсальной функциональности вокруг типа, возвращаемого функцией thenApply). Эту проблему можно решить, изменив реализацию Promise так, чтобы она возвращала обещание new всякий раз, когда обработчик зарегистрирован, вместо возврата this, так что каждый объект обещания будет иметь свой собственный обработчик для вызова.

В дополнение к решению этой проблемы, это лучший дизайн для программирования в функциональном стиле, поскольку вы можете сделать ваши Promise объекты неизменяемыми.

Я бы изменил интерфейс на:

interface Promise<T> {
    <U> Promise<U> thenApply(Function<T, U> handler);
    Promise<Void> thenAccept(Consumer<T> consumer);
}

Затем можно создать «цепочку» обратных вызовов вокруг будущих объектов, на которые ссылаются цепочки Promise. Так что реализация может выглядеть так:

class PromiseImpl<T> implements Promise<T> {

    private CompletableFuture<T> future;

    public PromiseImpl(CompletableFuture<T> future) {
        this.future = future;
    }

    @Override
    public <U> Promise<U> thenApply(Function<T, U> function) {
        return new PromiseImpl<>(this.future.thenApply(function));
    }

    @Override
    public Promise<Void> thenAccept(Consumer<T> consumer) {
        return new PromiseImpl<>(this.future.thenAccept(consumer));
    }

    private void onResult(T result) {
        this.future.complete(result);
    }

    private Object doWork(T result) {
        onResult(result);
        return null;
    }
}

И использовать это можно так же просто, как:

Promise<String> stringPromise = new PromiseImpl<>(new CompletableFuture<String>());
Promise<Long> longPromise = stringPromise.thenApply(str -> Long.valueOf(str.length()));
Promise<Void> voidPromise = stringPromise.thenAccept(str -> System.out.println(str));

EDIT:
Относительно комментария Майкла о получении значения: оно не было добавлено, как не было в оригинальном Promise API. Но достаточно просто добавить:

T get(); //To the interface

И реализовано с помощью:

public T get() {
    //try-catch 
    return this.future.get();
}

Примечание: это начинает все больше и больше походить на дублирование CompletableFuture, что поднимает вопрос, зачем вообще это делать. Но при условии, что в этом интерфейсе появятся дополнительные Promise -подобные методы, метод обернет будущий API.


Если вам нужно использовать один и тот же объект Promise со списком обратных вызовов, у вас нет другого выбора, кроме как параметризовать интерфейс Promise с обоими Function параметрами конкретного типа:

public interface Promise<T, U>

И U не сможет быть универсальным параметром метода для then или thenApply.

0 голосов
/ 06 июля 2018

Если вы хотите, чтобы остальная часть вашего класса оставалась такой же, и просто реализовать метод thenApply, вам нужно создать новый CompletableFuture, поскольку в настоящее время это единственный способ создать новый Promise:

@Override
public <U> Promise<U> thenApply(Function<T, U> handler) {
    CompletableFuture<U> downstream = new CompletableFuture<>();
    this.then(t -> downstream.complete(handler.apply(t)));
    return new PromiseImpl<>(downstream);
}

Если вы можете добавить частный конструктор без аргументов для PromiseImpl, вы можете избежать создания нового CompletableFuture:

@Override
public <U> Promise<U> thenApply(Function<T, U> handler) {
    PromiseImpl result = new PromiseImpl();
    this.then(t -> result.doWork(handler.apply(t)));
    return result;
}

Но на самом деле, если вы хотите реализовать собственный API поверх CompletableFuture, вам действительно нужно использовать шаблон декоратора и обернуть экземпляр CompletableFuture в качестве закрытой переменной в PromiseImpl.

0 голосов
/ 06 июля 2018

Вы можете вернуть некоторый анонимный класс, который расширяет ваш PromiseImpl и переопределяет onResult, поэтому обработчики принимают результат применения функции mapper.Не забудьте вызвать родительский объект onResult для вызова родительских обработчиков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...