Überdenken des Java8-Lambda-Ausdrucks- und Stream-Entwurfsmusters - Muster der Verantwortungskette -

Einführung

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.

Dieses Muster

"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.

cor.png

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.

Gegenstand

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.

Traditionelle Implementierung

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

Implementierungsmethode mit Lambda-Ausdruck

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 ) ist die Factory-Methode zum Generieren der tatsächlichen Implementierung von "Approvable" (Abteilungsleiter oder Abteilungsleiter).

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

Entwicklung: Versuchen Sie zu verallgemeinern

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

Zusammenfassung

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?

Recommended Posts

Überdenken des Java8-Lambda-Ausdrucks- und Stream-Entwurfsmusters - Muster der Verantwortungskette -
Überdenken von Entwurfsmustern mit Java8-Lambda-Ausdrücken und Stream - Null-Objektmuster -
Entwurfsmuster ~ Verantwortungskette ~
Ü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 - Builder-Muster -
Muster der Verantwortungskette
Muster der Verantwortungskette
Java8-Stream, Zusammenfassung des Lambda-Ausdrucks
Über Lambda, Stream, LocalDate von Java8
Java-Entwurfsmuster
[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
Verwenden Sie Java-Lambda-Ausdrücke außerhalb der Stream-API
Erste Schritte mit älteren Java-Ingenieuren (Stream + Lambda)
[Java] Funktionsschnittstelle / Lambda-Ausdruck
[Java11] Stream-Zusammenfassung - Vorteile von Stream -
[Entwurfsmuster] Java-Kernbibliothek
[Java] Zusammenfassung der Entwurfsmuster
Java Basic Learning Content 9 (Lambda-Ausdruck)
Anwendungsbeispiel für Entwurfsmuster (Nr. 1)
Grundlegender Verarbeitungsablauf von Java Stream
Was ist ein Lambda-Ausdruck (Java)
Java-Anfänger-Entwurfsmuster (Factory-Methodenmuster)
Der Ursprung von Java-Lambda-Ausdrücken
Vergleich der Thread-Implementierungsmethoden in Java und der Lambda-Ausdrucksmethode
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.