it-swarm.com.de

Implementierung eines Pipeline-Entwurfsmusters

Dies ist eine Designfrage bezüglich der Implementierung einer Pipeline. Das Folgende ist meine naive Implementierung.

Schnittstelle für einzelne Schritte/Phasen in der Pipeline:

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

Konkrete Implementierungen von Schritten/Phasen in der Pipeline:

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

Die Pipeline-Klasse hält/registriert die Schritte in der Pipeline und führt sie nacheinander aus:

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-Programm zum Ausführen der Pipeline:

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

Wie Sie sehen, hat die naive Implementierung jedoch viele Einschränkungen. 

Eine der wichtigsten ist, dass die naive Implementierung nicht typsicher ist (die Ausführungsmethode in der Pipeline-Klasse), da die Anforderung jedes Schrittes ein beliebiger Typ sein kann. Wenn ich die Schritte in der Pipeline falsch verdrahte, schlägt die App fehl.

Kann mir jemand beim Entwerfen der Lösung helfen, indem ich das, was ich codiert habe, hinzufüge, oder mich auf ein bereits vorhandenes Muster hinweisen, um dieses Problem zu lösen?

16

Ich würde mich konzentrieren

Wenn ich die Schritte in der Pipeline falsch verdrahte, schlägt die App fehl.

Ja, das ist ein Problem. StepThree ist hier der Fremde. Ich denke nicht, dass ein einfaches Muster helfen könnte, ich denke, es muss eine Kombination aus Strategie und Builder-Muster sein. Zum Beispiel:

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 ist wie folgt:

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

Mit der Fast-Builder-Syntax könnte dies funktionieren:

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

Dies sollte funktionieren, da Generika nicht Teil des Bytecodes sind.

8
Peter Rader

warum brauchten Sie eine zusätzliche Klasse Pipeline? Ich denke, du kannst den mittleren Mann entfernen. Dieser Wille macht Ihre API zum Beispiel einfach:

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;

sie können Ihr Pipeline-Muster einfach in Java-8 wie folgt implementieren:

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

in früheren Java-Versionen können Sie stattdessen eine abstrakte Klasse verwenden:

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;
    }
}
10
holi-java

Ihr Ansatz ist ziemlich gut. Ich würde die Pipeline-Klasse jedoch folgendermaßen codieren:

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

Auf diese Weise wird das gesamte Wissen über die einzelnen Schritte in der Pipeline-Klasse zusammengefasst.

In diesem Fall kann die Ausführungsmethode eine Schleife ausführen. Die Execute-Klasse kann die Schritte jedoch nacheinander nacheinander ausführen.

5
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());
}
0
murali

Sie können grundsätzlich Verantwortungskettenmuster verwenden

0