Java8 Lambda Expression & Stream Design Pattern Repenser - Modèle de chaîne de responsabilité -

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. Dernier article a couvert le modèle de méthode de modèle. L'exemple de code est publié sur GitHub.

Ce modèle

«Chaîne de responsabilité» se traduit par «chaîne de responsabilité», mais comme son nom l'indique, c'est un modèle qui résout le problème en passant les responsabilités les unes après les autres dans une chaîne d'objets. La structure statique de la classe est illustrée dans la figure ci-dessous.

cor.png

En fait, il s'agit d'une structure dans laquelle plusieurs gestionnaires sont connectés, comme ConcreteHandler1 instance-> ConcreteHandler2 instance-> ...-> ConcreteHandlerN instance. Le client envoie une requête au premier gestionnaire de la chaîne, et si le premier gestionnaire peut le résoudre, il renvoie le résultat tel quel, mais si le problème ne peut pas être résolu par lui-même, il délègue le traitement au gestionnaire suivant. Ce flux se propage le long de la chaîne, et le gestionnaire qui a finalement résolu le problème renvoie le résultat du traitement au gestionnaire précédent, ce gestionnaire retourne au gestionnaire précédent, et ainsi de suite, et enfin au client. Le résultat du traitement est renvoyé. Le client n'a pas à se soucier du gestionnaire qui a résolu le problème.

Matière

Cet échantillon est basé sur «l'approbation de l'approbation». Le montant qui peut être approuvé augmentera au fur et à mesure que le poste augmentera, comme par exemple «responsable de section» -> «responsable du département» -> «responsable du département commercial». Imaginez un scénario où vous déléguez le traitement.

Implémentation traditionnelle

Le modèle «Chaîne de responsabilité» nécessite d'abord de construire une chaîne d'objets responsables du problème. Si vous pouvez le résoudre vous-même, renvoyez le résultat. Sinon, le comportement de l'appel du processus de destination de délégation peut être partagé, il est donc standard de le définir dans une classe abstraite. De plus, comme chaque processus de résolution est différent pour chaque classe concrète, vous utiliserez probablement le modèle Template Method. Dans l'exemple de code ci-dessous, ʻapprove (int) est la méthode modèle etdoApprove (int)est la méthode hook. Le deuxième jugement nul dans ʻapprove (int) peut être évité en utilisant le modèle Null Object`, mais il est omis ici.

AbstractApprover.java


public abstract class AbstractApprover {
    private AbstractApprover next;
    protected String name;

    public AbstractApprover(String name, AbstractApprover next) {
        this.name = name;
        this.next = next;
    }

    public final Approval approve(int amount) {
        Approval approval = doApprove(amount);
        if (approval != null) {
            return approval;
        } else if (next != null) {
            return next.approve(amount);
        } else {
            System.out.println(String.format("Je ne peux pas approuver.%,3d yen", amount));
            return null;
        }
    }

    protected abstract Approval doApprove(int amount);
}

Un exemple d'implémentation de sous-classe est le suivant.

Kacho.java


public class Kacho extends AbstractApprover {

    public Kacho(String name, AbstractApprover next) {
        super(name, next);
    }

    @Override
    protected Approval doApprove(int amount) {
        if (amount <= 10000) {
            System.out.println(String.format("Approbation du responsable de section(%s) %,3d yen", name, amount));
            return new Approval(name, amount);
        } else {
            return null;
        }
    }
}

L'assemblage et l'exécution de la chaîne sont les suivants. (Je l'assemble moi-même pour le code de test, mais je vais en fait l'obtenir de Factory)

Usage


        JigyoBucho jigyoBucho = new JigyoBucho("Yamada", null);
        Bucho bucho = new Bucho("Tanaka", jigyoBucho);
        Kacho kacho = new Kacho("Suzuki", bucho);
        Approval approval = kacho.approve(10000);
        assertEquals("Suzuki", approval.getApprover());
        approval = kacho.approve(100000);
        assertEquals("Tanaka", approval.getApprover());
        approval = kacho.approve(1000000);
        assertEquals("Yamada", approval.getApprover());
        approval = kacho.approve(1000001);
        assertNull(approval);

Méthode d'implémentation utilisant l'expression lambda

Puisqu'il est difficile de définir et d'implémenter des sous-classes pour chaque différence uniquement dans le jugement de condition tel que le montant d'argent comme dans cet exemple, exprimez-le dans une expression lambda et préparez une méthode pour assembler la chaîne. À Puisque Java 8 vous permet de définir des méthodes par défaut et statiques dans une interface, vous pouvez les définir dans une interface comme suit.

Approvable.java


@FunctionalInterface
public interface Approvable {

    Approval approve(int amount);

    default Approvable delegate(Approvable next) {
        return (amount) -> {
            final Approval result = approve(amount);
            return result != null ? result : next.approve(amount);
        };
    }

    static Approvable chain(Approvable... approvables) {
        return Stream.concat(Stream.of(approvables), Stream.of(tail()))
                .reduce((approvable, next) -> approvable.delegate(next))
                .orElse(null);
    }

    static Approvable approver(String title, String name, Predicate<Integer> predicate) {
        return amount -> {
            if (predicate.test(amount)) {
                System.out.println(String.format("%s approbation(%s) %,3d yen", title, name, amount));
                return new Approval(name, amount);
            } else {
                return null;
            }
        };
    }

    static Approvable tail() {
        return amount -> {
            System.out.println(String.format("Je ne peux pas approuver.%,3d yen", amount));
            return null;
        };
    }

}

Le contenu du code ci-dessus est expliqué ci-dessous.

Premièrement, la responsabilité de régler un montant donné est définie comme la méthode delegate (int): Approval, qui reçoit le montant et renvoie le résultat de l'approbation. Afin d'implémenter ce processus dans une expression lambda pour chaque poste tel que responsable de section ou responsable de service, définissez ce processus comme une interface fonctionnelle qui n'a qu'une seule méthode, et ajoutez l'annotation @ FunctionalInterface.

@FunctionalInterface
public interface Approvable {

    Approval approve(int amount);

La méthode par défaut suivante «déléguer (Approuvable)» est une méthode préparée pour la synthèse de fonctions. java.util.Function <T, R> a des méthodes de composition de fonctions appelées compose et ʻandThen, donc vous pouvez les utiliser si vous connectez simplement les processus. Cependant, dans le cas du modèle Chain of Responsibility, vous devez ** arrêter la propagation du traitement à ce stade si vous pouvez le résoudre **, vous devez donc préparer votre propre méthode. Il renvoie dans une expression lambda le comportement consistant à appeler d'abord son propre processus (ʻapprove) puis à appeler le processus suivant si le résultat est nul.

    default Approvable delegate(Approvable next) {
        return (amount) -> {
            final Approval result = approve(amount);
            return result != null ? result : next.approve(amount);
        };
    }

La méthode statique suivante, «chain (Approvable ...)», assemble la chaîne. Un Stream est formé à partir du tableau de ʻApprovablereçu avec des arguments de longueur variable, et en le réduisant, il est combiné en une fonction et renvoyé. À ce stade, utilisez la méthodedelegate` mentionnée précédemment.

    static Approvable chain(Approvable... approvables) {
        return Stream.concat(Stream.of(approvables), Stream.of(tail()))
                .reduce((approvable, next) -> approvable.delegate(next))
                .orElse(null);
    }

La suivante ʻapprover (String, String, Predicate ) est la Méthode d'usine pour générer l'implémentation réelle de ʻApprovable (le responsable de section ou le responsable de département).

    static Approvable approver(String title, String name, Predicate<Integer> predicate) {
        return amount -> {
            if (predicate.test(amount)) {
                System.out.println(String.format("%s approbation(%s) %,3d yen", title, name, amount));
                return new Approval(name, amount);
            } else {
                return null;
            }
        };
    }

L'assemblage et le traitement d'une chaîne à l'aide de ʻApprovable` ressemble à l'exemple suivant.

Usage


        Approvable kacho = Approvable.approver("Directeur", "Suzuki", amount -> amount <= 10000);
        Approvable bucho = Approvable.approver("Réalisateur", "Tanaka", amount -> amount <= 100000);
        Approvable jigyoBucho = Approvable.approver("Chef de division", "Yamada", amount -> amount <= 1000000);

        Approvable cor = Approvable.chain(kacho, bucho, jigyoBucho);
        Approval approval = cor.approve(10000);
        assertEquals("Suzuki", approval.getApprover());
        approval = cor.approve(100000);
        assertEquals("Tanaka", approval.getApprover());
        approval = cor.approve(1000000);
        assertEquals("Yamada", approval.getApprover());
        approval = cor.approve(1000001);
        assertNull(approval);

Développement: essayez de généraliser

Avec une bonne utilisation des types génériques, les propriétés de la «chaîne de responsabilité» peuvent être généralisées et définies. Ici, nous définissons ses propriétés comme suit et essayons de la généraliser.

L'interface fonctionnelle correspondant à «Approuvable» dans la section précédente est définie comme suit. C'est un peu difficile à lire car il contient des paramètres de type, mais le traitement est le même.

Delegatable.java


@FunctionalInterface
public interface Delegatable<Q, R> {

    R call(Q q);

    default Delegatable<Q, R> delegate(Delegatable<Q, R> next) {
        return q -> {
            final R result = call(q);
            return result != null ? result : next.call(q);
        };
    }
}

Et la classe qui implémente les propriétés ci-dessus est exactement nommée ChainOfResponsibility <Q, R>.

ChainOfResponsibility


public class ChainOfResponsibility<Q, R> {
    private List<Delegatable<Q, R>> chain;

    public ChainOfResponsibility() {
        super();
        chain = new ArrayList<>();
    }

    public ChainOfResponsibility<Q, R> add(Function<Q, R> func) {
        chain.add(q -> func.apply(q));
        return this;
    }

    public ChainOfResponsibility<Q, R> tail(Consumer<Q> tail) {
        chain.add(q -> {
            tail.accept(q);
            return null;
        });
        return this;
    }

    public Function<Q, R> build() {
        Optional<Delegatable<Q, R>> head = chain.stream()
                .reduce((delegatable, next) -> delegatable.delegate(next));
        return head.isPresent() ? q -> head.get().call(q) : null;
    }

}

L'exemple d'utilisation est le suivant.

Usage


    @Test
    public void test() {
        Function<Integer, Approval> kacho = approver("Directeur", "Suzuki", amount -> amount <= 10000);
        Function<Integer, Approval> bucho = approver("Réalisateur", "Tanaka", amount -> amount <= 100000);
        Function<Integer, Approval> jigyoBucho = approver("Chef de division", "Yamada", amount -> amount <= 1000000);

        Function<Integer, Approval> head = new ChainOfResponsibility<Integer, Approval>()
                .add(kacho)
                .add(bucho)
                .add(jigyoBucho)
                .tail(amount -> System.out.println(String.format("Je ne peux pas approuver.%,3d yen", amount)))
                .build();

        Approval approval = head.apply(10000);
        assertEquals("Suzuki", approval.getApprover());
        approval = head.apply(100000);
        assertEquals("Tanaka", approval.getApprover());
        approval = head.apply(1000000);
        assertEquals("Yamada", approval.getApprover());
        approval = head.apply(1000001);
        assertNull(approval);

    }

    private Function<Integer, Approval> approver(String title, String name, Predicate<Integer> predicate) {
        return amount -> {
            if (predicate.test(amount)) {
                System.out.println(String.format("%s approbation(%s) %,3d yen", title, name, amount));
                return new Approval(name, amount);
            } else {
                return null;
            }
        };
    }

Résumé

La propagation du traitement dans le modèle «Chaîne de responsabilité» a été implémentée avec succès en effectuant une synthèse de fonctions. De plus, le modèle peut être généralisé en utilisant le type générique. La programmation fonctionnelle avec des expressions lambda est très puissante, n'est-ce pas?

Recommended Posts

Java8 Lambda Expression & Stream Design Pattern Repenser - Modèle de chaîne de responsabilité -
Repenser le modèle d'expression et de conception de flux Java8 Lambda - Modèle d'objet nul -
Modèle de conception ~ Chaîne de responsabilité ~
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 les modèles de conception avec les expressions lambda Java8 et Stream --Builder pattern -
Modèle de chaîne de responsabilité
Modèle de chaîne de responsabilité
Flux Java8, résumé de l'expression lambda
À propos de Lambda, Stream, LocalDate de Java8
Modèle de conception Java
[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
Utiliser des expressions Java lambda en dehors de l'API Stream
Premiers pas avec les anciens ingénieurs Java (Stream + Lambda)
[Java] Interface fonctionnelle / expression lambda
[Java11] Résumé du flux -Avantages du flux-
[Design pattern] Bibliothèque de base Java
[Java] Résumé des modèles de conception
Contenu d'apprentissage de base Java 9 (expression lambda)
Exemple d'application du modèle de conception (n ° 1)
Flux de traitement de base de Java Stream
Qu'est-ce qu'une expression lambda (Java)
Modèle de conception Java pour débutant (modèle de méthode d'usine)
L'origine des expressions Java lambda
Comparaison des méthodes d'implémentation de thread en Java et de la méthode d'expression lambda
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.