Реализация паттерна проектирования конвейера

Это вопрос проектирования относительно реализации конвейера. Вот моя наивная реализация.

Интерфейс для отдельных шагов / стадий в конвейере:

public interface Step<T, U> {
    public U execute(T input);
}

Конкретные реализации шагов / этапов в конвейере:

public class StepOne implements Step<Integer, Integer> {
    @Override
    public Integer execute(Integer input) {
        return input + 100;
    }
}

public class StepTwo implements Step<Integer, Integer> {
    @Override
    public Integer execute(Integer input) {
        return input + 500;
    }
}

public class StepThree implements Step<Integer, String> {
    @Override
    public String execute(Integer input) {
        return "The final amount is " + input;
    }
}

Класс конвейера будет удерживать / регистрировать шаги в конвейере и выполнять их один за другим:

public class Pipeline {
    private List<Step> pipelineSteps = new ArrayList<>();
    private Object firstStepInput = 100;

    public void addStep(Step step) {
        pipelineSteps.add(step);
    }

    public void execute() {
        for (Step step : pipelineSteps) {
            Object out = step.execute(firstStepInput);
            firstStepInput = out;
        }
   }
}

Программа Diver для выполнения конвейера:

public class Main {
    public static void main(String[] args) {
        Pipeline pipeline = new Pipeline();
        pipeline.addStep(new StepOne());
        pipeline.addStep(new StepTwo());
        pipeline.addStep(new StepThree());

        pipeline.execute();
    } 
}

Однако, как видите, наивная реализация имеет множество ограничений.

Один из основных состоит в том, что, поскольку требуется, чтобы выходные данные каждого шага могли быть любого типа, наивная реализация не является типобезопасной (метод execute в классе Pipeline). Если я неправильно подключу шаги в конвейере, приложение выйдет из строя.

Может ли кто-нибудь помочь мне разработать решение, добавив к тому, что я закодировал, или указать мне на уже существующий шаблон для решения этой проблемы?


person Prashant Chauhan    schedule 09.10.2016    source источник
comment
этот вопрос ссылается на этот документ, который ссылается на шаблоны.   -  person Nick Bell    schedule 09.10.2016
comment
Спасибо @NickBell за то, что указали на газету. Однако, читая документ, я не могу понять, как можно спроектировать конвейер, чтобы он мог обрабатывать этапы / шаги с разными типами вывода.   -  person Prashant Chauhan    schedule 09.10.2016
comment
Я бы рассмотрел вопрос о Java 1.8+ потоки, поскольку они предоставляют функциональность / документы к указанному вами примеру. См. Здесь дубликат   -  person Nick Bell    schedule 09.10.2016
comment
Спасибо Нику за то, что разместил ссылку на повторяющийся вопрос. Приведенный здесь пример очень помог.   -  person Prashant Chauhan    schedule 10.10.2016
comment
Ссылка @NickBell не работает   -  person yolob 21    schedule 16.04.2020


Ответы (6)


Я бы сосредоточился на

Если я неправильно подключу шаги в конвейере, приложение выйдет из строя.

Да, это проблема. StepThree здесь незнакомец. Я не думаю, что какой-то простой шаблон может помочь, я думаю, что это должна быть комбинация стратегии и шаблона построения. Например:

Pipeline<Integer,Integer> intPipe = new Pipeline<>();
intPipe = intPipe.add(new StepOne()); // increment 100
intPipe = intPipe.add(new StepTwo()); // increment 500
Pipeline<String, Integer> strPipe = intPipe.add(new StepThree()); // convert

Whereat Pipeline выглядит так:

public static class Pipeline<IN, OUT> {
   //...
   public<A> Pipeline<OUT,A> add(Step<IN,A> step) {
     pipelineSteps.add(step);
     return (Pipeline<OUT,A>)this;
   }
}

Используя синтаксис быстрого построения, это может сработать:

Pipeline<String, Integer> pipe = new Pipeline<Integer, Integer>()
    .add(new StepOne()).add(new StepTwo()).add(new StepThree());

Это должно работать, поскольку дженерики не являются частью байт-кода.

person Grim    schedule 09.10.2016
comment
Спасибо, Питер, за ответ на вопрос! :) - person Prashant Chauhan; 10.10.2016
comment
@PrashantChauhan к вашим услугам; D - person Grim; 10.10.2016

зачем вам дополнительный Pipeline класс? Я думаю, вы можете убрать среднего человека. это упростит ваш api, например:

Step<Integer, String> source = Step.of(Object::toString);
Step<Integer, Integer> toHex = source.pipe(it -> Integer.parseInt(it, 16));

toHex.execute(11/*0x11*/);// return 17;

вы можете реализовать свой шаблон конвейера просто в java-8, как показано ниже:

interface Step<I, O> {

    O execute(I value);

    default <R> Step<I, R> pipe(Step<O, R> source) {
        return value -> source.execute(execute(value));
    }

    static <I, O> Step<I, O> of(Step<I, O> source) {
        return source;
    }
}

в предыдущей версии java вместо этого можно было использовать абстрактный класс:

abstract static class Step<I, O> {

    public abstract O execute(I value);

    public <R> Step<I, R> pipe(Step<O, R> source) {
        return new Step<I, R>() {
            @Override
            public R execute(I value) {
                return source.execute(Step.this.execute(value));
            }
        };
    }

    public static <I, O> Step<I, O> of(Step<I, O> source) {
        return source;
    }
}
person holi-java    schedule 24.06.2017
comment
По порядку, недооцененный ответ! - person Grim; 30.08.2017
comment
Это потрясающе. Вы можете объяснить, как работает Step.of(Object::toString)? Как toString интерпретируется как Step<I, O>? - person z0r; 24.05.2019
comment
Просто реализовал это в приложении, так мало кода, но так эффективно. Очень хорошо. - person RedShift; 01.11.2019
comment
@ z0r: это то же самое, что и Step.of(input -> input.toString());. Оператор :: - это лямбда-выражение для ссылки на метод класса. - person RedShift; 01.11.2019

Для этого не нужно создавать новый интерфейс.

В Java 8 уже есть функциональный интерфейс, называемый функцией, и он позволяет вам создавать цепочки функций (другими словами, ваш конвейер).

Function<Integer, Integer> addOne = it -> {
            System.out.println(it + 1);
            return it + 1;
        };

Function<Integer, Integer> addTwo = it -> {
            System.out.println(it + 2);
            return it + 2;
        };

Function<Integer, Integer> timesTwo = input -> {
            System.out.println(input * 2);
            return input * 2;
        };

final Function<Integer, Integer> pipe = addOne
        .andThen(timesTwo)
        .andThen(addTwo);

pipe.apply(10);

Если вы хотите узнать больше о функциональных интерфейсах: https://medium.com/@julio.falbo/java-recent-history-java-8-part-2-functional-interface-predefined-functional-interface-2494f25610d5

person Júlio Falbo    schedule 05.11.2019
comment
Можем ли мы также иметь абстрактный метод внутри алгоритма и заставить пользователя реализовать этот метод. Что-то вроде: abstract Function ‹Integer, Integer› userDefined; final Функция ‹Целое число, Целое число› pipe = sourceInt.andThen (userDefined) .andThen () ... - person mayank bisht; 29.03.2020
comment
Проголосуйте за использование встроенных интерфейсов. Это простейшее решение. - person Mr Jedi; 07.05.2020

Ваш подход довольно хорош. Однако я бы закодировал класс Pipeline следующим образом:

public class Pipeline {
    private List<Step> pipelineSteps = new ArrayList<>();
    private Object firstStepInput = 100;

    public Pipeline() {
        pipelineSteps.add(new StepOne());
        pipelineSteps.add(new StepTwo());
        pipelineSteps.add(new StepThree());
    }

    public void execute() {
        for (Step step : pipelineSteps) {
            Object out = step.execute(firstStepInput);
            firstStepInput = out;
        }
    }

    public String getResult() {
        return (String) firstStepInput;
    }
}

Таким образом, все конкретные сведения о шагах инкапсулируются в класс конвейера.

В этом случае метод execute может выполнять цикл. Однако при необходимости класс выполнения может выполнять шаги один за другим.

person Gilbert Le Blanc    schedule 09.10.2016
comment
Спасибо, что указали, как правильно инкапсулировать класс Pipeline. - person Prashant Chauhan; 10.10.2016

Вы можете использовать шаблон проектирования цепочки ответственности.

person Vladimir Stazhilov    schedule 30.06.2017
comment
Я думаю, что это Декоратор - person alfiogang; 22.04.2021

person    schedule
comment
Пожалуйста, отформатируйте (когда вы пишете свой ответ появится окно предварительного просмотра) и объясните свой ответ. Вы ведь хотите, чтобы люди поняли, что вы сделали, не так ли? - person Rafalon; 06.10.2017