In dieser Reihe werden wir uns überlegen, wie Entwurfsmuster mithilfe der in Java 8 eingeführten Lambda-Ausdrucks- / Stream-API implementiert werden. Letzter Artikel behandelte das Muster der Vorlagenmethode. Der Beispielcode wird auf GitHub veröffentlicht.
"Kette der Verantwortung" wird übersetzt als "Kette der Verantwortung", aber wie der Name schon sagt, ist es ein Muster, das das Problem löst, indem die Verantwortung nacheinander in einer Kette von Objekten weitergegeben wird. Die statische Struktur der Klasse ist in der folgenden Abbildung dargestellt.
Tatsächlich ist die Struktur so, dass mehrere Handler verbunden sind, z. B. "ConcreteHandler1-Instanz" -> "ConcreteHandler2-Instanz" -> ...-> "ConcreteHandlerN-Instanz". Der Client sendet eine Anforderung an den ersten Handler in der Kette. Wenn der erste Handler sie lösen kann, gibt er das Ergebnis so zurück, wie es ist. Wenn das Problem jedoch nicht selbst gelöst werden kann, delegiert er die Verarbeitung an den nächsten Handler. Dieser Fluss breitet sich entlang der Kette aus, und der Handler, der das Problem endgültig gelöst hat, gibt das Verarbeitungsergebnis an den vorherigen Handler zurück, dieser Handler kehrt an den vorherigen Handler zurück und so weiter und schließlich an den Client. Das Verarbeitungsergebnis wird zurückgegeben. Der Client muss sich keine Gedanken darüber machen, welcher Handler das Problem gelöst hat.
Dieses Muster basiert auf der "Genehmigung der Genehmigung". Wenn die Position steigt, erhöht sich der Betrag, der genehmigt werden kann, mit zunehmender Position, z. B. "Abteilungsleiter" -> "Abteilungsleiter" -> "Abteilungsleiter". Stellen Sie sich ein Szenario vor, in dem Sie die Verarbeitung delegieren.
Das Muster "Verantwortungskette" erfordert zunächst den Aufbau einer Kette von Objekten, die für das Problem verantwortlich sind. `Wenn Sie es selbst lösen können, geben Sie das Ergebnis zurück. Andernfalls kann das Verhalten beim Aufrufen des Delegierungszielprozesses gemeinsam genutzt werden. Daher ist es Standard, ihn in einer abstrakten Klasse zu definieren. Da jeder Auflösungsprozess für jede konkrete Klasse unterschiedlich ist, werden Sie wahrscheinlich das Muster "Vorlagenmethode" verwenden. Im folgenden Beispielcode ist "genehmigen (int)" die Vorlagenmethode und "doApprove (int)" die Hook-Methode. Das zweite Nullurteil in "genehmigen (int)" kann durch Verwendung des Musters "Nullobjekt" vermieden werden, wird hier jedoch weggelassen.
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("Ich kann nicht zustimmen.%,3d Yen", amount));
return null;
}
}
protected abstract Approval doApprove(int amount);
}
Ein Beispiel für die Implementierung von Unterklassen ist wie folgt.
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("Genehmigung des Abteilungsleiters(%s) %,3d Yen", name, amount));
return new Approval(name, amount);
} else {
return null;
}
}
}
Das Zusammenbauen und Ausführen der Kette ist wie folgt. (Ich baue es selbst für den Testcode zusammen, aber ich werde es tatsächlich von Factory
bekommen)
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);
Da es schwierig ist, Unterklassen für jeden Unterschied nur bei der Zustandsbeurteilung wie dem Geldbetrag wie in diesem Beispiel zu definieren und zu implementieren, drücken Sie ihn in einem Lambda-Ausdruck aus und bereiten Sie eine Methode zum Zusammensetzen der Kette vor. Zu Da Sie mit Java 8 Standard- und statische Methoden in einer Schnittstelle definieren können, können Sie diese wie folgt in einer Schnittstelle definieren.
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 Zustimmung(%s) %,3d Yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
static Approvable tail() {
return amount -> {
System.out.println(String.format("Ich kann nicht zustimmen.%,3d Yen", amount));
return null;
};
}
}
Der Inhalt des obigen Codes wird unten erläutert.
Zunächst wird die Verantwortung für die Abrechnung eines bestimmten Betrags als die Methode "Delegate (int): Approval" definiert, die den Betrag erhält und das Genehmigungsergebnis zurückgibt. Um diesen Prozess in einem Lambda-Ausdruck für jede Position wie Abteilungsleiter oder Abteilungsleiter zu implementieren, definieren Sie diesen Prozess als funktionale Schnittstelle mit nur einer Methode und fügen Sie die Annotation "@ FunctionalInterface" hinzu.
@FunctionalInterface
public interface Approvable {
Approval approve(int amount);
Die folgende Standardmethode "delegate (Approvable)" ist eine für die Funktionssynthese vorbereitete Methode.
java.util.Function <T, R>
verfügt über Methoden zur Funktionszusammensetzung mit den Namen compose
und andThen
, sodass Sie diese verwenden können, wenn Sie einfach die Prozesse verbinden.
Im Fall des Musters "Chain of Responsibility" müssen Sie jedoch ** die Weitergabe der Verarbeitung an diesem Punkt stoppen, wenn Sie sie beheben können **, sodass Sie Ihre eigene Methode vorbereiten müssen.
Es gibt in einem Lambda-Ausdruck das Verhalten zurück, zuerst seinen eigenen Prozess aufzurufen (genehmigen
) und dann den nächsten Prozess aufzurufen, wenn das Ergebnis null ist.
default Approvable delegate(Approvable next) {
return (amount) -> {
final Approval result = approve(amount);
return result != null ? result : next.approve(amount);
};
}
Die nächste statische Methode, "chain (Approvable ...)", setzt die Kette zusammen. Ein Stream wird aus dem Array von "Approvable" gebildet, das mit Argumenten variabler Länge empfangen wird, und durch Reduzieren wird es zu einer Funktion kombiniert und zurückgegeben. Verwenden Sie zu diesem Zeitpunkt die zuvor erwähnte Methode "delegieren".
static Approvable chain(Approvable... approvables) {
return Stream.concat(Stream.of(approvables), Stream.of(tail()))
.reduce((approvable, next) -> approvable.delegate(next))
.orElse(null);
}
Der folgende "Genehmiger" (String, String, Predicate
static Approvable approver(String title, String name, Predicate<Integer> predicate) {
return amount -> {
if (predicate.test(amount)) {
System.out.println(String.format("%s Zustimmung(%s) %,3d Yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
Das folgende Beispiel zeigt, wie eine Kette mit "Approvable" zusammengesetzt und ausgeführt wird.
Usage
Approvable kacho = Approvable.approver("Manager", "Suzuki", amount -> amount <= 10000);
Approvable bucho = Approvable.approver("Direktor", "Tanaka", amount -> amount <= 100000);
Approvable jigyoBucho = Approvable.approver("Bereichsleiter", "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);
Wenn Sie den generischen Typ gut verwenden, können Sie die Eigenschaften von "Chain of Responsiblity" verallgemeinern und definieren. Hier definieren wir seine Eigenschaften wie folgt und versuchen, es zu verallgemeinern.
Die Funktionsschnittstelle, die im vorherigen Abschnitt "Genehmigend" entspricht, ist wie folgt definiert. Es ist etwas schwer zu lesen, da es Typparameter enthält, aber die Verarbeitung ist dieselbe.
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);
};
}
}
Und die Klasse, die die obigen Eigenschaften implementiert, heißt genau "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;
}
}
Das Verwendungsbeispiel lautet wie folgt.
Usage
@Test
public void test() {
Function<Integer, Approval> kacho = approver("Manager", "Suzuki", amount -> amount <= 10000);
Function<Integer, Approval> bucho = approver("Direktor", "Tanaka", amount -> amount <= 100000);
Function<Integer, Approval> jigyoBucho = approver("Bereichsleiter", "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("Ich kann nicht zustimmen.%,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 Zustimmung(%s) %,3d Yen", title, name, amount));
return new Approval(name, amount);
} else {
return null;
}
};
}
Die Ausbreitung der Verarbeitung im Muster "Chain of Responsibility" wurde erfolgreich durchgeführt, indem eine Funktionssynthese durchgeführt wurde. Darüber hinaus kann das Muster mithilfe des generischen Typs verallgemeinert werden. Funktionale Programmierung mit Lambda-Ausdrücken ist sehr leistungsfähig, nicht wahr?