Repenser le modèle d'expression et de conception de flux Java8 Lambda - Modèle de commande -

introduction

Dans cette série, nous examinerons comment implémenter des modèles de conception à l'aide de l'expression lambda / API Stream introduite dans Java 8. L'article précédent (http://qiita.com/yonetty/items/e99b01470c3416af761c) couvrait le modèle Template Method.

Ce thème

Cette fois, nous reprendrons le modèle Command. Dans l'exemple de programme, écrivez la commande pour déplacer la pièce (Peace) qui se déplace sur les coordonnées bidimensionnelles avec le modèle Command, et essayez d'en faire une version d'expression lambda.

La classe Piece à utiliser a la coordonnée x, la coordonnée y et la direction ( Direction) comme champs, et se déplace d'avant en arrière (moveForward (), moveBackward ()) et définit la direction (setDirection ( setDirection () Changer l'état par Direction)) `). On suppose que l'état initial est l'origine (0,0) et fait face au nord (vers le haut).

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

Méthode de montage conventionnelle

Tout d'abord, définissez l'interface pour Command. Avoir une méthode ʻexecute (Piece) qui reçoit et traite l'objet Piece`.

PieceCommand.java


public interface PieceCommand {

    void execute(Piece piece);
}

En tant qu'implémentation concrète de Commande, préparez une commande pour aller et venir du nombre de carrés spécifié ( Avant, Arrière) et un changement de direction vers la gauche et la droite ( TurnRight, TurnLeft). De plus, l'expansion de direction vers la gauche et la droite est une rotation de ± 90 degrés («π / 2» radian), et une nouvelle direction est obtenue à partir de la direction du courant.

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

}

Voici un exemple de code qui utilise ces instructions pour déplacer le Piece:

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

Implémentation à l'aide d'une expression lambda

Maintenant, implémentons le modèle Command en utilisant une expression lambda. Tout d'abord, chaque instruction reçoit un objet Piece et effectue des opérations de mouvement avant / arrière et de changement de direction, alors réalisons-la avec l'interface fonctionnelleConsumer <T> fournie par le standard Java 8. La PieceCommand dans la section précédente sera remplacée par le typeConsumer <Piece>. Et nous allons préparer une Factory pour obtenir la fonction qui représente chaque instruction.

L'implémentation de Forward.java dans la section précédente a osé utiliser la syntaxe à l'ancienne, mais cela a également été corrigé par la méthode d'écriture utilisant Stream API.

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

}

Voici un exemple de code utilisant la version de l'expression lambda:

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

Composition des fonctions

L'avantage de la version d'expression lambda est que vous pouvez synthétiser des fonctions. Plus précisément, la méthode par défaut ʻandThen (Consumer <? Super T>) définie dans Consumer `est utilisée.

UsegeOfAndThen


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

Puisque le résultat synthétisé est la même fonction de type «Consumer », il peut être traité comme une commande (instruction) qui opère «Piece», similaire à celle définie comme une méthode statique de «PieceCommandFactory». Vous pouvez définir et utiliser de nouvelles fonctions en composant comme suit.

Composite


        //Répétez "droite pour droite" et "tourner à droite"
        Consumer<Piece> reverse = PieceCommandFactory.ofTurnRight()
                .andThen(PieceCommandFactory.ofTurnRight());

Au fait, comment la composition des fonctions est-elle implémentée? En regardant la source de Consumer <T>, il a l'implémentation suivante.

Consumer.java


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

C'est assez simple. Il s'agit d'une ligne sauf pour le contrôle des conditions préalables. Après avoir appelé le propre ʻacceptde l'instanceConsumer auquel appartient ʻandThen, leConsumer <T>passé comme argument est appelé ʻaccept. Je pense que c'est une bonne idée de simuler dans votre esprit comment l'expression lambda est construite et exécutée lorsque plusieurs ʻet Thens sont concaténés.

Reduce Au lieu de concaténer «et ensuite» en séquence, vous pouvez implémenter une méthode utilitaire qui reçoit un tableau de variables et renvoie le «Consumer » synthétisé comme suit.

PieceCommandFactory#chain


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

En synthétisant les deux fonctions séquentiellement avec «et Alors», elles peuvent être combinées en une seule fonction dans son ensemble. Enfin, ʻou Elsespécifie une expression lambda qui ne fait rien, donc même si l'argumentcommandes est null`, cela fonctionne en toute sécurité. Ce n'est pas grave si la longueur est de 1.

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

Résumé

La propriété du modèle «Command» de procéder en appliquant plusieurs commandes séquentiellement semble être compatible avec le concept de ** composition de fonction ** en programmation fonctionnelle. Ensuite, je vais reprendre le modèle de «Chaîne de responsabilité» («chaîne de responsabilité»).

Recommended Posts

Repenser le modèle d'expression et de conception de flux Java8 Lambda - Modèle de commande -
Repenser les modèles d'expression et de conception de flux Java8 - Modèle de méthode
Repenser le modèle d'expression et de conception de flux Java8 Lambda - Modèle d'objet nul -
Java8 Lambda Expression & Stream Design Pattern Repenser - Modèle de chaîne de responsabilité -
Flux Java8, résumé de l'expression lambda
Repenser les modèles de conception avec les expressions lambda Java8 et Stream --Builder pattern -
Modèle de conception Java
Modèle de conception ~ Commande ~
[Java] Expression Lambda
Expression lambda Java
expression 1 de la table lambda neutre java
Variations d'expressions lambda Java
Fonctionnalité d'expression lambda Java 8
mémo d'expression java lambda
expression lambda java [écriture de notes]
Etudier Java 8 (expression lambda)
Évaluer java8 ~ Type Lambda ~
Expression lambda Java à nouveau
Résumé du modèle de conception Java
Premiers pas avec les anciens ingénieurs Java (Stream + Lambda)
[Java] Interface fonctionnelle / expression lambda
À propos de Lambda, Stream, LocalDate de Java8
Contenu d'apprentissage de base Java 9 (expression lambda)
Qu'est-ce qu'une expression lambda (Java)
Modèle de conception Java pour débutant (modèle de méthode d'usine)
Récapitulons maintenant l'expression lambda Java
De nos jours, les expressions Java Lambda et l'API de flux
J'ai examiné l'expression lambda de Java, l'API de flux, six mois après avoir commencé Java.
Comment utiliser l'API Java avec des expressions lambda
Java 8 pour démarrer maintenant ~ for Each et expression lambda ~
Modèle de conception ~ Constructeur ~
[Java] Modèle de stratégie
Modèle de conception ~ Visiteur ~
modèle de rappel java
Modèle de conception ~ Proxy ~
Essayez Java 8 Stream
Modèle de conception ~ État ~
Modèle de conception ~ Stratégie ~
Modèle de conception ~ Singleton ~
Modèle de conception (2): constructeur
Modèle de conception (1): AbstractFactory
[Java] Motif singleton
API Java Stream
Modèle de conception ~ Itérateur ~
Modèles de conception Java
Modèle de conception ~ Façade ~
Modèle de conception ~ Pont ~
Modèle de conception ~ Médiateur ~
Modèle de conception ~ Décorateur ~
Modèle de conception ~ Interprète ~
Bonjour Java Lambda
Modèle de conception ~ Observateur ~
Étudier Java 8 (Stream)
Modèle de conception ~ Prototype ~
[Java] Modèle d'adaptateur
Modèle de conception ~ Memento ~
Modèle de conception ~ Adaptateur ~
Terminaison du flux Java
[Java] Traitement de flux