为内部子接口创建“默认构造函数”

3

好的,标题可能有点难懂。我没有找到正确的东西。 因此,基本上我正在使用Java 8函数来创建可重试API。我想要这些接口的简单实现,所以我在每个Retryable接口的实现中创建了一个of(...)方法,我们可以使用lambda表达式,而不是手动创建匿名类。

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public interface Retryable<T, R> extends Function<T, R>{

    void retrying(Exception e);

    void skipping(Exception e);

    int trials();

    @Override
    default R apply(T t) {
        int trial = 0;
        while (true) {
            trial++;
            try {
                return action(t);
            } catch (Exception e) {
                if (trial < trials()) {
                    retrying(e);
                } else {
                    skipping(e);
                    return null;
                }
            }
        }
    }

    R action(T input) throws Exception;

    interface RunnableRetryable extends Retryable<Void, Void> {

        static RunnableRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedRunnable runnable) {
            return new RunnableRetryable() {
                @Override
                public void retrying(Exception e) {
                    retrying.accept(e);
                }

                @Override
                public void skipping(Exception e) {
                    skipping.accept(e);
                }

                @Override
                public int trials() {
                    return trials;
                }

                @Override
                public Void action(Void v) throws Exception {
                    runnable.tryRun();
                    return null;
                }
            };
        }

        @FunctionalInterface
        interface CheckedRunnable extends Runnable {

            void tryRun() throws Exception;

            @Override
            default void run() {
                try {
                    tryRun();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    interface ConsumerRetryable<T> extends Retryable<T, Void> {

        static <T> ConsumerRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) {
            return new ConsumerRetryable<T>() {
                @Override
                public void retrying(Exception e) {
                    retrying.accept(e);
                }

                @Override
                public void skipping(Exception e) {
                    skipping.accept(e);
                }

                @Override
                public int trials() {
                    return trials;
                }

                @Override
                public Void action(T t) throws Exception {
                    consumer.tryAccept(t);
                    return null;
                }
            };
        }

        @FunctionalInterface
        interface CheckedConsumer<T> extends Consumer<T> {

            void tryAccept(T t) throws Exception;

            @Override
            default void accept(T t) {
                try {
                    tryAccept(t);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    interface SupplierRetryable<T> extends Retryable<Void, T> {

        static <T> SupplierRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) {
            return new SupplierRetryable<T>() {
                @Override
                public void retrying(Exception e) {
                    retrying.accept(e);
                }

                @Override
                public void skipping(Exception e) {
                    skipping.accept(e);
                }

                @Override
                public int trials() {
                    return trials;
                }

                @Override
                public T action(Void v) throws Exception {
                    return supplier.tryGet();
                }
            };
        }

        @FunctionalInterface
        interface CheckedSupplier<T> extends Supplier<T> {

            T tryGet() throws Exception;

            @Override
            default T get() {
                try {
                    return tryGet();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    interface FunctionRetryable<T, R> extends Retryable<T, R> {

        static <T, R> FunctionRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) {
            return new FunctionRetryable<T, R>() {
                @Override
                public void retrying(Exception e) {
                    retrying.accept(e);
                }

                @Override
                public void skipping(Exception e) {
                    skipping.accept(e);
                }

                @Override
                public int trials() {
                    return trials;
                }

                @Override
                public R action(T t) throws Exception {
                    return function.tryApply(t);
                }
            };
        }

        @FunctionalInterface
        interface CheckedFunction<T, R> extends Function<T, R> {

            R tryApply(T t) throws Exception;

            @Override
            default R apply(T t) {
                try {
                    return tryApply(t);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

但是可以看到,在每个 of(...) 方法中有很多重复代码。我可以在Retryable接口中创建一种“构造函数”(因为接口不能有构造函数,所以这不是正确的术语),但我不知道如何做。有人有想法吗?


2
我不认为这些冗余的内部接口存在的理由。您只需要为不同参数化的“Retryable”拥有多个工厂方法。 - Holger
2个回答

5
主要问题是你的API爆炸。所有这些嵌套的接口扩展Retryable并不添加任何功能,但要求此代码的用户在它们成为API的一部分后要处理它们。此外,它们是代码重复的原因,因为每个冗余接口都需要自己的实现,而所有的实现基本上都做着相同的事情。
在移除这些过时类型之后,您可以简单地通过委托实现操作:
public interface Retryable<T, R> extends Function<T, R>{
    void retrying(Exception e);
    void skipping(Exception e);
    int trials();
    @Override default R apply(T t) {
        try { return action(t); }
        catch(Exception e) {
            for(int trial = 1; trial < trials(); trial++) {
                retrying(e);
                try { return action(t); } catch (Exception next) { e=next; }
            }
            skipping(e);
            return null;
        }
    }

    R action(T input) throws Exception;

    public static Retryable<Void, Void> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedRunnable runnable) {
        return of(retrying, skipping, trials, x -> { runnable.tryRun(); return null; });
    }

    @FunctionalInterface interface CheckedRunnable extends Runnable {
        void tryRun() throws Exception;
        @Override default void run() {
            try { tryRun(); } catch (Exception e) { throw new RuntimeException(e); }
        }
    }

    public static <T> Retryable<T, Void> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) {
        return of(retrying, skipping, trials,
                  value -> { consumer.tryAccept(value); return null; });
    }

    @FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
        void tryAccept(T t) throws Exception;
        @Override default void accept(T t) {
            try { tryAccept(t); } catch (Exception e) { throw new RuntimeException(e); }
        }
    }

    public static <T> Retryable<Void, T> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) {
        return of(retrying, skipping, trials, voidArg -> { return supplier.tryGet(); });
    }

    @FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
        T tryGet() throws Exception;
        @Override default T get() {
            try { return tryGet(); }
            catch (Exception e) { throw new RuntimeException(e); }
        }
    }

    public static <T, R> Retryable<T, R> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) {
        return new Retryable<T, R>() {
            @Override public void retrying(Exception e) { retrying.accept(e); }
            @Override public void skipping(Exception e) { skipping.accept(e); }
            @Override public int trials() { return trials; }
            @Override public R action(T t) throws Exception {
                return function.tryApply(t);
            }
        };
    }

    @FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
        R tryApply(T t) throws Exception;
        @Override default R apply(T t) {
            try { return tryApply(t); }
            catch (Exception e) { throw new RuntimeException(e); }
        }
    }
}

只需要一个实现类,它必须能够处理参数和返回值,其他实现类可以使用适配器函数简单地委托给它,可以选择放弃参数或返回null

对于大多数用例,lambda表达式的形状是选择正确方法的合适方式,例如:

Retryable<Void,Void> r = Retryable.of(e -> {}, e -> {}, 3, () -> {});
Retryable<Void,String> s = Retryable.of(e -> {}, e -> {}, 3, () -> "foo");
Retryable<Integer,Integer> f = Retryable.of(e -> {}, e -> {}, 3, i -> i/0);

但有时候可能需要一些提示:

// braces required to disambiguate between Function and Consumer
Retryable<String,Void> c = Retryable.of(e->{}, e ->{}, 3,
                                        str -> { System.out.println(str); });

你说得完全正确。我在徒劳地寻找太远了。现在一切都清楚了。谢谢! - Franckyi
@Holger,我们也有重试机制(谁没有呢?),但是OP的想法和你的回答可能会给我们的代码带来新的形式... - Eugene

0

看起来你可以将其中一些内容提取出来,放到一个(可能是包私有的)抽象类中:

abstract class AbstractRetryable<T, R> implements Retryable<T, R> {
    private final Consumer<Exception> retrying;
    private final Consumer<Exception> skipping;
    private final int                 trials;
    AbstractRetryable(Consumer<Exception> retrying,
                      Consumer<Exception> skipping,
                      int                 trials) {
        this.retrying = Objects.requireNonNull(retrying, "retrying");
        this.skipping = Objects.requireNonNull(skipping, "skipping");
        this.trials   = trials;
    }
    @Override
    public void retrying(Exception x) {
        retrying.accept(x);
    }
    @Override
    public void skipping(Exception x) {
        skipping.accept(x);
    }
    @Override
    public int trials() {
        return trials;
    }
}

唯一的问题是你正在使用子接口,所以你不能创建一个既扩展抽象类又实现子接口的匿名类。
然后你可以编写更多(可能是包私有)的子类:
final class RunnableRetryableImpl
extends    AbstractRetryable<Void, Void>
implements RunnableRetryable {
    private final CheckedRunnable runnable;
    RunnableRetryableImpl(Consumer<Exception> retrying,
                          Consumer<Exception> skipping,
                          int                 trials,
                          CheckedRunnable     runnable) {
        super(retrying, skipping, trials);
        this.runnable = Objects.requireNonNull(runnable, "runnable");
    }
    @Override
    public Void apply(Void ignored) {
        try {
            runnable.tryRun();
        } catch (Exception x) {
            // BTW I would consider doing this.
            if (x instanceof RuntimeException)
                throw (RuntimeException) x;
            // I would also probably write a class like:
            // class RethrownException extends RuntimeException {
            //     RethrownException(Exception cause) {
            //         super(cause);
            //     }
            // }
            // This way the caller can catch a specific type if
            // they want to.
            // (See e.g. java.io.UncheckedIOException)
            throw new RuntimeException(x);
        }
        return null;
    }
}

或者你可以通过使用本地类来减少代码行数:

static RunnableRetryable of(Consumer<Exception> retrying,
                            Consumer<Exception> skipping,
                            int                 trials,
                            CheckedRunnable     runnable) {
    Objects.requireNonNull(runnable, "runnable");
    final class RunnableRetryableImpl
    extends    AbstractRetryable<Void, Void>
    implements RunnableRetryable {
        RunnableRetryable() {
            // Avoid explicitly declaring parameters
            // and passing arguments.
            super(retrying, skipping, trials);
        }
        @Override
        public Void apply(Void ignored) {
            try {
               runnable.tryRun();
            } catch (Exception x) {
                if (x instanceof RuntimeException)
                    throw (RuntimeException) x;
                throw new RuntimeException(x);
            }
            return null;
        }
    }
    return new RunnableRetryableImpl();
}

个人而言,我认为我会编写包私有实现,而不是本地类,但这肯定需要相当数量的样板代码。

此外,顺便提一下,当您编写返回匿名类的工厂时,应在方法本身内使用requireNonNull(就像我在我的of方法中所做的那样)。这是为了如果将null传递给该方法,则该方法抛出NPE,而不是例如某些调用retryingskipping在稍后某个时间抛出NPE。


3
不必在catch子句中使用instanceof,可以直接创建一个带有该类型的第二个catch子句,例如:catch(RuntimeException|Error unchecked) { throw unchecked; } catch(Throwable checked) { throw new RuntimeException(checked); } - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接