Combine Consumers with different arguments or different arguments number

Aleksey Balenko :

Let's say, our method receives input String and returns some List output. This output is the result of some number of generators, some of which depend on input and some of them not - they just add predefined values. I want to implement these generators like a list of some function interfaces (Consumer for example), then combine them into one Consumer and just apply it to the input String.

So, I will able to change small generators easy and independently. But the problem is that not all my generators need input String as a parameter and I just pass this parameter there for only one reason - have an ability to combine such Consumers with others.

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

Is it possible to combine different consumers under one umbrella and apply them in one place? Or this is a bad idea and not the right way to do such things?

Holger :

It is not an unusual pattern to have a common interface in a modular software and adapters, to make particular implementations fit. E.g.

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

So ignoreFirstArg is the general adapter for methods not having that first parameter. There can be an arbitrary number of adapter methods. But note that if an adapter is very specific and thus, only use a single time, it’s also possible to write a lambda expression instead of a method reference, right in the combining code. Note that I changed that code, to not get repeatedly evaluated for every generate(String input) call, as otherwise, there would be no point in combining them when you don’t reuse the combined function, as you could also use

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

or even simpler

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

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

    return output;
}

which is not worse to maintain than the functional code, as still, every generator consists of a single line.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=116579&siteId=1