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.
«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.
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.
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.
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);
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éthode
delegate` 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);
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;
}
};
}
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