【问题标题】:Combine Consumers with different arguments or different arguments number结合具有不同参数或不同参数数量的消费者
【发布时间】:2019-04-24 19:36:24
【问题描述】:

假设我们的方法接收输入字符串并返回一些列表输出。此输出是一些生成器的结果,其中一些依赖于输入,而另一些则不依赖于输入——它们只是添加预定义的值。 我想像一些函数接口(例如消费者)的列表一样实现这些生成器,然后将它们组合成一个消费者并将其应用于输入字符串。

所以,我将能够轻松独立地更换小型发电机。但问题是,并不是我所有的生成器都需要 input String 作为参数,我只是将这个参数传递给那里,原因只有一个 - 能够将这些消费者与其他消费者结合起来。

public class ConsumersTest {

private static <T, U> BiConsumer<T, U> combine(List<BiConsumer<T, U>> consumers) {
    return consumers.stream().reduce((arg1, arg2) -> {}, BiConsumer::andThen);
}

List<String> generate(String input) {
    ArrayList<String> output = new ArrayList<>();
    combine(getGenerators()).accept(input, output);
    return output;
}

private List<BiConsumer<String, List<String>>> getGenerators() {
    return Arrays.asList(
            this::addFirstDependent,
            this::addSecondIndependent
    );
}

private void addFirstDependent(String input, List<String> output) {
    if (input.contains("some string")) {
        output.add("First-Dependent");
    }
}

private void addSecondIndependent(String input, List<String> output) {
    output.add("Predefined Output");
}}

是否可以将不同的消费者组合在一个保护伞下并将它们应用到一个地方?或者这是一个坏主意,而不是做这些事情的正确方法?

【问题讨论】:

  • 这是否是个好主意我不能说。但是您似乎希望能够根据参数编号和类型对您的消费者进行分类,然后正确地为这些消费者提供食物。对于您上面描述的简单情况,您可能需要构建两个列表,一个用于两个参数相关的消费者,一个用于单个参数独立。然后对它们进行相应的评估并结合最终的结果。
  • 是的,我想过这样的解决方案,但是还有一个问题——我需要这些消费者按照严格的顺序应用,比如 FirstDependent -> SecondIndependent -> ThirdIndependent -> FourthDeendent 等等。跨度>
  • 好吧,看来我以前也遇到过同样的问题,我所做的不是传递String(在我的情况下我有多个参数),而是构建一个Context。 ..对于需要一些参数的Consumer&lt;Context&gt;,我将使用Consumer.withAge(12).withName("Eugene") 构建它,依此类推,对于其他类型Consumer.empty(),对于这样的消费者,所有getters 都会抛出异常......它可能只是隐藏了问题,但至少有人阅读和实施代码会理解发生了什么。
  • 当每个“消费者”实际上产生一个(可选)值时,接收 List 作为参数看起来是有问题的,所以它要么是供应商,要么是函数。更进一步,去整个combine(getGenerators())操作,每次调用generate(String input),都是资源浪费。
  • 尤金,是的,我也尝试过这种方法。但这只是隐藏未使用的参数。目前,我正在寻找更好的方法来做到这一点。

标签: java lambda java-8 method-reference functional-interface


【解决方案1】:

在模块化软件和适配器中具有通用接口以使特定实现适合,这并不是一种不寻常的模式。例如

public class ConsumersTest {

    List<String> generate(String input) {
        ArrayList<String> output = new ArrayList<>();
        generators.accept(input, output);
        return output;
    }

    private static <T, U> BiConsumer<T, U> ignoreFirstArg(Consumer<U> consumer) {
        return (t, u) -> consumer.accept(u);
    }

    private final BiConsumer<String, List<String>> generators =
        Stream.<BiConsumer<String, List<String>>>of(
                this::addFirstDependent,
                ignoreFirstArg(this::addSecondIndependent)
        ).reduce(BiConsumer::andThen).orElse((arg1, arg2) -> {});

    private void addFirstDependent(String input, List<String> output) {
        if (input.contains("some string")) {
            output.add("First-Dependent");
        }
    }

    private void addSecondIndependent(List<String> output) {
        output.add("Predefined Output");
    }
}

所以ignoreFirstArg 是没有第一个参数的方法的通用适配器。可以有任意数量的适配器方法。但请注意,如果适配器非常具体,因此只使用一次,也可以在组合代码中编写 lambda 表达式而不是方法引用。请注意,我更改了该代码,以便不会为每个 generate(String input) 调用重复评估,否则,当您不重用组合函数时,将它们组合起来是没有意义的,因为您也可以使用

List<String> generate(String input) {
    ArrayList<String> output = new ArrayList<>();
    Stream.<BiConsumer<String, List<String>>>of(
            this::addFirstDependent,
            ignoreFirstArg(this::addSecondIndependent)
    ).forEach(g -> g.accept(input, output));
    return output;
}

甚至更简单

List<String> generate(String input) {
    ArrayList<String> output = new ArrayList<>();

    this.addFirstDependent(input, output);
    this.addSecondIndependent(output);

    return output;
}

这并不比功能代码更难维护,但每个生成器都由一行组成。

【讨论】:

  • 关于适配器的好主意!
  • 谢谢。很好的答案,感谢您指点重复评价。实际上,我已经以类似的方式解决了这个问题,但只使用了 Consumer 而不是 BiConsumer 和 Consumers。因此,由于 addFirstDependentGenerator(String input) 等另一种方法,我将依赖消费者添加到列表中。因此,我不会忽略未使用的参数,而是仅在需要的生成器中使用它。但是,是的,现在为了生成一个完整的列表,我需要传递输入参数。所以,我认为,我应该使用您的选项来避免每次都创建一个列表。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
  • 2015-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多