Überdenken des Entwurfsmusters mit Java8 Lambda-Ausdruck & Stream - Befehlsmuster -

Einführung

In dieser Reihe werden wir uns überlegen, wie Entwurfsmuster mithilfe der in Java 8 eingeführten Lambda-Ausdrucks- / Stream-API implementiert werden. Der vorherige Artikel (http://qiita.com/yonetty/items/e99b01470c3416af761c) befasste sich mit dem Muster "Vorlagenmethode".

Dieses Thema

Dieses Mal werden wir das "Befehl" -Muster aufgreifen. Schreiben Sie im Beispielprogramm den Befehl zum Verschieben des Stücks ("Frieden"), das sich auf den zweidimensionalen Koordinaten mit dem "Befehl" -Muster bewegt, und versuchen Sie, es zu einer Lambda-Ausdrucksversion zu machen.

Die zu bedienende "Piece" -Klasse hat x-Koordinate, y-Koordinate und Richtung ("Richtung") als Felder und bewegt sich hin und her ("moveForward ()", "moveBackward ()") und legt die Richtung ("setDirection" ("setDirection") fest. Ändern Sie den Status nach Richtung)) `). Es wird angenommen, dass der Ausgangszustand der Ursprung (0,0) ist und nach Norden (nach oben) zeigt.

Piece.java


public class Piece {
    private Direction direction = Direction.NORTH;
    private int x = 0;
    private int y = 0;

    public void moveForward() {
        this.x += this.direction.x();
        this.y += this.direction.y();
    }

    public void moveBackward() {
        this.x -= this.direction.x();
        this.y -= this.direction.y();
    }

    public Direction getDirection() {
        return direction;
    }

    public void setDirection(Direction direction) {
        this.direction = direction;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public static enum Direction {
        NORTH(Math.PI / 2), EAST(0), SOUTH(Math.PI / -2), WEST(Math.PI);

        final private double radian;
        final private int x;
        final private int y;

        Direction(double radian) {
            this.radian = radian;
            this.x = (int) Math.cos(radian);
            this.y = (int) Math.sin(radian);
        }

        public static Direction valueOf(double radian) {
            return Stream.of(Direction.values())
                    .filter(d -> d.x == (int) Math.cos(radian) && d.y == (int) Math.sin(radian))
                    .findAny()
                    .orElseThrow(IllegalArgumentException::new);
        }

        public int x() {
            return this.x;
        }

        public int y() {
            return this.y;
        }

        public double radian() {
            return this.radian;
        }
    }
}

Konventionelle Montagemethode

Definieren Sie zunächst die Schnittstelle für "Befehl". Es hat eine "execute (Piece)" - Methode, die ein "Piece" -Objekt empfängt und verarbeitet.

PieceCommand.java


public interface PieceCommand {

    void execute(Piece piece);
}

Bereiten Sie als konkrete Command-Implementierung einen Befehl vor, um die angegebene Anzahl von Quadraten ( Forward, Backward) und eine Richtungsänderung nach links und rechts ( TurnRight, TurnLeft) hin und her zu bewegen. Zusätzlich ist die Richtungserweiterung nach links und rechts eine Drehung von ± 90 Grad ("π / 2" Radian), und aus der aktuellen Richtung wird eine neue Richtung erhalten.

Forward.java



public class Forward implements PieceCommand {
    private final int step;

    public Forward(int step) {
        this.step = step;
    }

    @Override
    public void execute(Piece piece) {
        for (int i = 0; i < step; i++) {
            piece.moveForward();
        }
    }
}

TurnLeft.java


public class TurnLeft implements PieceCommand {

    @Override
    public void execute(Piece piece) {
        double radian = piece.getDirection().radian() + Math.PI / 2;
        piece.setDirection(Piece.Direction.valueOf(radian));
    }

}

Hier ist ein Codebeispiel, das diese Anweisungen verwendet, um Piece zu verschieben:

Usage


        PieceCommand cmd1 = new Forward(5);
        cmd1.execute(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

        PieceCommand cmd2 = new TurnRight();
        cmd2.execute(piece);

        assertEquals(Piece.Direction.EAST, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

        PieceCommand cmd3 = new Backward(3);
        cmd3.execute(piece);

        assertEquals(Piece.Direction.EAST, piece.getDirection());
        assertEquals(-3, piece.getX());
        assertEquals(5, piece.getY());

        PieceCommand cmd4 = new TurnLeft();
        cmd4.execute(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(-3, piece.getX());
        assertEquals(5, piece.getY());

Implementierung mit Lambda-Ausdruck

Lassen Sie uns nun das Befehlsmuster mit einem Lambda-Ausdruck implementieren. Zunächst empfängt jeder Befehl ein "Piece" -Objekt und führt Vorwärts- / Rückwärtsbewegungs- und Richtungsänderungsoperationen durch. Lassen Sie uns dies also mit der vom Java 8-Standard bereitgestellten Funktionsschnittstelle "Consumer " realisieren. Der PieceCommand im vorherigen Abschnitt wird durch den TypConsumer <Piece> ersetzt. Und wir werden eine "Fabrik" vorbereiten, um die Funktion zu erhalten, die jede Anweisung darstellt.

Die Implementierung von "Forward.java" im vorherigen Abschnitt wagte es, die altmodische Syntax zu verwenden, dies wurde jedoch auch mit "Stream API" auf die Schreibmethode korrigiert.

PieceCommandFactory.java


public class PieceCommandFactory {

    static Consumer<Piece> ofForward(final int step) {
        return p -> {
            IntStream.range(0, step)
                    .forEach(i -> p.moveForward());
        };
    }

    static Consumer<Piece> ofBackward(final int step) {
        return p -> {
            IntStream.range(0, step)
                    .forEach(i -> p.moveBackward());
        };
    }

    static Consumer<Piece> ofTurnRight() {
        return p -> {
            double radian = p.getDirection().radian() - Math.PI / 2;
            p.setDirection(Piece.Direction.valueOf(radian));
        };
    }

    static Consumer<Piece> ofTurnLeft() {
        return p -> {
            double radian = p.getDirection().radian() + Math.PI / 2;
            p.setDirection(Piece.Direction.valueOf(radian));
        };
    }

}

Hier ist ein Codebeispiel mit der Lambda-Ausdrucksversion:

Usage


        Consumer<Piece> cmd1 = PieceCommandFactory.ofForward(5);
        cmd1.accept(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

        Consumer<Piece> cmd2 = PieceCommandFactory.ofTurnRight();
        cmd2.accept(piece);

        assertEquals(Piece.Direction.EAST, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

        Consumer<Piece> cmd3 = PieceCommandFactory.ofBackward(3);
        cmd3.accept(piece);

        assertEquals(Piece.Direction.EAST, piece.getDirection());
        assertEquals(-3, piece.getX());
        assertEquals(5, piece.getY());

        Consumer<Piece> cmd4 = PieceCommandFactory.ofTurnLeft();
        cmd4.accept(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(-3, piece.getX());
        assertEquals(5, piece.getY());

Funktionszusammensetzung

Der Vorteil der Lambda-Ausdrucksversion besteht darin, dass Sie Funktionen synthetisieren können. Insbesondere wird die in "Consumer " definierte Standardmethode "andThen (Consumer <? Super T>)" verwendet.

UsegeOfAndThen


        Consumer<Piece> composite = PieceCommandFactory.ofForward(5)
                .andThen(PieceCommandFactory.ofTurnRight())
                .andThen(PieceCommandFactory.ofBackward(3))
                .andThen(PieceCommandFactory.ofTurnLeft());
        composite.accept(piece);

Da das synthetisierte Ergebnis dieselbe Funktion vom Typ "Consumer " ist, kann es als Befehl (Befehl) behandelt werden, der "Piece" ausführt, ähnlich dem, der als statische Methode von "PieceCommandFactory" definiert ist. Sie können neue Funktionen definieren und verwenden, indem Sie wie folgt zusammensetzen.

Composite


        //Wiederholen Sie "rechts für rechts" und "rechts abbiegen"
        Consumer<Piece> reverse = PieceCommandFactory.ofTurnRight()
                .andThen(PieceCommandFactory.ofTurnRight());

Wie wird die Funktionszusammensetzung übrigens implementiert? Wenn man sich die Quelle von "Consumer " ansieht, hat es die folgende Implementierung.

Consumer.java


    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

Es ist ziemlich einfach. Es ist eine Zeile mit Ausnahme der Vorbedingungsprüfung. Nach dem Aufruf des "accept" der "Consumer " - Instanz selbst, zu der das "andThen" gehört, wird das "accept" des als Argument übergebenen "Consumer " aufgerufen. Ich denke, es ist eine gute Idee, in Ihrem Kopf zu simulieren, wie der Lambda-Ausdruck konstruiert und ausgeführt wird, wenn mehrere andThens verkettet werden.

Reduce Anstatt "andThen" nacheinander zu verketten, können Sie eine Dienstprogrammmethode implementieren, die ein variables Array empfängt und den synthetisierten "Consumer " wie folgt zurückgibt.

PieceCommandFactory#chain


    @SafeVarargs
    static Consumer<Piece> chain(Consumer<Piece>... commands) {
        return Stream.of(commands)
                .reduce((c1, c2) -> c1.andThen(c2))
                .orElse(p -> {
                });
    }

Durch sequentielles Synthetisieren der beiden Funktionen mit "andThen" können sie zu einer Funktion als Ganzes kombiniert werden. Da "orElse" einen Lambda-Ausdruck angibt, der nichts bewirkt, funktioniert er schließlich auch dann sicher, wenn das Argument "Befehle" "Null" ist. Es ist in Ordnung, wenn die Länge 1 ist.

SafeChain


        Consumer<Piece> composite = PieceCommandFactory.chain(PieceCommandFactory.ofForward(5));
        composite.accept(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

        Consumer<Piece> empty = PieceCommandFactory.chain();
        empty.accept(piece);

        assertEquals(Piece.Direction.NORTH, piece.getDirection());
        assertEquals(0, piece.getX());
        assertEquals(5, piece.getY());

Zusammenfassung

Die Eigenschaft des Befehlsmusters, mit der Verarbeitung fortzufahren, indem mehrere Befehle nacheinander angewendet werden, scheint mit dem Konzept der ** Funktionszusammensetzung ** in der Funktionsprogrammierung kompatibel zu sein. Als nächstes werde ich das Muster der "Kette der Verantwortung" ("Kette der Verantwortung") aufgreifen.

Recommended Posts

Überdenken des Entwurfsmusters mit Java8 Lambda-Ausdruck & Stream - Befehlsmuster -
Überdenken des Java8-Lambda-Ausdrucks- und Stream-Entwurfsmusters - Muster der Vorlagenmethode -
Überdenken von Entwurfsmustern mit Java8-Lambda-Ausdrücken und Stream - Null-Objektmuster -
Überdenken des Java8-Lambda-Ausdrucks- und Stream-Entwurfsmusters - Muster der Verantwortungskette -
Java8-Stream, Zusammenfassung des Lambda-Ausdrucks
Überdenken von Entwurfsmustern mit Java8-Lambda-Ausdrücken und Stream - Builder-Muster -
Java-Entwurfsmuster
Entwurfsmuster ~ Befehl ~
[Java] Lambda-Ausdruck
Java Lambda Ausdruck
Java Neutral Lambda Tabellenausdruck 1
Variationen von Java-Lambda-Ausdrücken
Java 8 Lambda-Ausdruck Feature
Java Lambda Ausdruck Memo
Java Lambda Ausdruck [Notiz schreiben]
Java 8 studieren (Lambda-Ausdruck)
Überprüfen Sie java8 ~ Lambda Typ ~
Wieder Java-Lambda-Ausdruck
Zusammenfassung des Java-Entwurfsmusters
Erste Schritte mit älteren Java-Ingenieuren (Stream + Lambda)
[Java] Funktionsschnittstelle / Lambda-Ausdruck
Über Lambda, Stream, LocalDate von Java8
Java Basic Learning Content 9 (Lambda-Ausdruck)
Was ist ein Lambda-Ausdruck (Java)
Java-Anfänger-Entwurfsmuster (Factory-Methodenmuster)
Lassen Sie uns nun den Java-Lambda-Ausdruck rekapitulieren
Heutzutage Java Lambda Expressions und Stream API
Ich habe Javas Lambda-Ausdruck Stream-API ein halbes Jahr nach dem Start von Java überprüft.
Verwendung der Java-API mit Lambda-Ausdrücken
Java 8 startet jetzt ~ für jeden und Lambda-Ausdruck ~
Entwurfsmuster ~ Builder ~
[Java] Strategiemuster
Entwurfsmuster ~ Besucher ~
Java-Rückrufmuster
Entwurfsmuster ~ Proxy ~
Probieren Sie Java 8 Stream aus
Entwurfsmuster ~ Zustand ~
Entwurfsmuster ~ Strategie ~
Entwurfsmuster ~ Singleton ~
Entwurfsmuster (2): Builder
Entwurfsmuster (1): AbstractFactory
[Java] Singleton-Muster
Java Stream API
Entwurfsmuster ~ Iterator ~
Java-Entwurfsmuster
Entwurfsmuster ~ Fassade ~
Entwurfsmuster ~ Brücke ~
Entwurfsmuster ~ Mediator ~
Entwurfsmuster ~ Dekorateur ~
Entwurfsmuster ~ Interpreter ~
Hallo Java Lambda
Entwurfsmuster ~ Beobachter ~
Java 8 studieren (Stream)
Entwurfsmuster ~ Prototyp ~
[Java] Adaptermuster
Entwurfsmuster ~ Memento ~
Entwurfsmuster ~ Adapter ~
Java Stream-Beendigung
[Java] Stream-Verarbeitung