Überdenken von Entwurfsmustern mit Java8-Lambda-Ausdrücken und Stream - Null-Objektmuster -

Einführung

In dieser Reihe werden wir uns überlegen, wie Entwurfsmuster mithilfe der in Java 8 eingeführten Lambda-Ausdrucks- / Stream-API implementiert werden. Der Beispielcode wird auf GitHub hochgeladen.

Dieses Muster

Ich habe das "Null Object" -Muster im vorherigen Artikel erwähnt, aber ich habe die Erklärung weggelassen, daher werde ich es dieses Mal behandeln. Das "Null-Objekt" -Muster ist ein sicherer Code, für den keine Nullprüfung erforderlich ist, indem auf ein "Objekt verwiesen wird, das eine" spezifische Schnittstelle "anstelle einer Nullreferenz implementiert, aber nichts tut (keine Nebenwirkungen hat). Dies ist eine Schreibtechnik.

Traditionelle Implementierung

Angenommen, eine Klasse delegiert die Verarbeitung an die folgenden Schnittstellen.

Discount.java


public interface Discount {
    Integer calcDiscount(Integer totalAmount);
}

Unter der Annahme, dass im Konstruktor oder Setter ein Verweis auf eine Instanz der Implementierungsklasse von "Dicsount" angegeben ist, ist die folgende Nullprüfung erforderlich, um den Fall von Null bei der Verwendung zu berücksichtigen.

OrderService.java(1)


    private Discount discount;

    public Integer getPrice(Integer amount) {
        if (discount != null) {
            amount -= discount.calcDiscount(amount);
        }
        return amount;
    }

Wenn Sie die Variable "Rabatt" an mehreren Stellen verwenden, ist es mühsam, jedes Mal eine Nullprüfung durchzuführen. Stellen Sie daher eine Instanz ein, die nichts tut (um genau zu sein, die Verarbeitung, um die Verarbeitung auf der Benutzerseite nicht zu beeinträchtigen), dh "Null-Objekt", wie folgt. "Null-Objekt" wird häufig als innere Klasse oder anonyme innere Klasse definiert, wie unten gezeigt.

OrderService.java(2)


    private Discount discount2 = new Discount() {
        @Override
        public Integer calcDiscount(Integer totalAmount) {
            return 0;
        }
    };

    public Integer getPrice2(Integer amount) {
        amount -= discount2.calcDiscount(amount);
        return amount;
    }

Übrigens wird "@ FunctionalInterface" nicht zur "Discount" -Schnittstelle hinzugefügt, kann aber als funktionale Schnittstelle behandelt werden, da es nur eine Methode gibt. Das heißt, Sie können ein Nullobjekt auch mit einem Lambda-Ausdruck wie dem folgenden definieren:

OrderService.java(3)


    private Discount discount3 = amount -> 0;

    public Integer getPrice3(Integer amount) {
        amount -= discount3.calcDiscount(amount);
        return amount;
    }

Funktionales Nullobjekt

Das folgende Beispiel ist ein Beispiel für ein Dienstprogramm, das Datumsangaben bearbeitet.

DateTimeBuilder.java


public class DateTimeBuilder {
    private LocalDateTime base;
    private Function<Temporal, Temporal> func;

    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        func = t -> t;
    }

    private void compose(TemporalAdjuster next) {
        func = func.andThen(t -> next.adjustInto(t));
    }

    public DateTimeBuilder nextMonth() {
        compose(t -> t.plus(1, ChronoUnit.MONTHS));
        return this;
    }

    public DateTimeBuilder lastDay() {
        compose(TemporalAdjusters.lastDayOfMonth());
        return this;
    }

    public LocalDateTime get() {
        return (LocalDateTime) func.apply(base);
    }

    public static DateTimeBuilder of(LocalDateTime base) {
        DateTimeBuilder builder = new DateTimeBuilder(base);
        return builder;
    }
}

Das Verwendungsbeispiel lautet wie folgt. Der folgende Code findet den letzten Tag des Monats nach dem im Argument angegebenen Datum ("2017/1/17").

Usage


        LocalDateTime base = LocalDateTime.of(2017, 1, 17, 0, 0);
        LocalDateTime dt = DateTimeBuilder.of(base)
                .nextMonth()
                .lastDay()
                .get();
        assertEquals(LocalDateTime.of(2017, 2, 28, 0, 0), dt);

In DateTimeBuilder wird jedes Mal, wenn nextMonth () oderlastDay ()aufgerufen wird, die Funktion, die das entsprechende Datum konvertiert, in die Instanzvariable func synthetisiert und schließlichget () Die zusammengesetzte Funktion wird beim Aufruf angewendet. Der folgende Code wird synthetisiert, aber zu diesem Zeitpunkt wird das Muster "Null Object" eingeführt, sodass nicht berücksichtigt werden muss, dass "NullPointerException" nicht auftritt, wenn "func" null ist. Wird die Motivation sein für.

#Methode verfassen


    private void compose(TemporalAdjuster next) {
        func = func.andThen(t -> next.adjustInto(t));
    }

Setzen Sie im Konstruktor "func" auf "eine Funktion, die nichts tut". Die Tatsache, dass der Typ "Function <Temporal, Temporal>" nichts bewirkt, bedeutet, dass das als Argument empfangene Objekt vom Typ "Temporal" so zurückgegeben wird, wie es ist (von rechts nach links übergeben). Das ist "t-> t", wenn es im Lambda-Ausdruck geschrieben ist.

Funktion entsprechend NullObject


    private Function<Temporal, Temporal> func;

    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        func = t -> t;
    }

Function # identity ist das Einheitselement

Der obige Lambda-Ausdruck "t-> t" ist besser, um die statische Methode "identity ()" zu verwenden, die in der Schnittstelle "java.util.Function" definiert ist.

Function#identity()Gebrauch von


    private DateTimeBuilder(LocalDateTime base) {
        this.base = base;
        //func = t -> t;
        func = Function.identity();
    }

Function # identity () gibt eine Funktion zurück, die nichts tut, dh eine Funktion, die das Eingabeargument so zurückgibt, wie es ist. Diese Funktion kann mit einer anderen Konvertierungsfunktion (nennen wir sie "g") von links oder von rechts kombiniert werden, um eine Kompositionsfunktion zu erhalten, die das gleiche Ergebnis wie "g" zurückgibt. Aus mathematischer Sicht kann gesagt werden, dass dies ein "Einheitselement" im System "Funktion <Temporal, Temporal>" ist. Tatsächlich ist die englische Übersetzung von "Einheitselement" "Identitätselement".

Zusammenfassung

Bei der Synthese von Funktionen scheint die Verwendung von Function # identity () als Einheitselement als Anfangswert den komplizierten Nullprüfcode zu reduzieren und die Sichtbarkeit des Codes zu verbessern.

Recommended Posts

Überdenken von Entwurfsmustern mit Java8-Lambda-Ausdrücken und Stream - Null-Objektmuster -
Ü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 -
Java8-Stream, Zusammenfassung des Lambda-Ausdrucks
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 8 studieren (Lambda-Ausdruck)
Wieder Java-Lambda-Ausdruck
Zusammenfassung des Java-Entwurfsmusters
Erste Schritte mit älteren Java-Ingenieuren (Stream + Lambda)
[Java] Funktionsschnittstelle / Lambda-Ausdruck
[Entwurfsmuster] Java-Kernbibliothek
Über Lambda, Stream, LocalDate von Java8
Java Basic Learning Content 9 (Lambda-Ausdruck)
Was ist ein Lambda-Ausdruck (Java)
Java-Anfänger-Entwurfsmuster (Factory-Methodenmuster)
Lassen Sie uns nun den Java-Lambda-Ausdruck rekapitulieren
Heutzutage Java Lambda Expressions und Stream API
[Java] Konvertiert Null vom Objekttyp in String-Typ
Ich habe Javas Lambda-Ausdruck Stream-API ein halbes Jahr nach dem Start von Java überprüft.
Verwendung der Java-API mit Lambda-Ausdrücken
Java 8 startet jetzt ~ für jeden und Lambda-Ausdruck ~