[JAVA] Refactor die Implementierung des Decorator-Musters mit einer funktionalen Schnittstelle

Überblick

Lassen Sie uns die Implementierung des Decorator-Musters mithilfe der in Java 8 eingeführten Funktionsschnittstelle und Funktionssynthese überarbeiten.

Dekorationsmuster

Mit dem Decorator-Muster können Sie vorhandenen Objekten dynamisch neue Funktionen und Verhaltensweisen hinzufügen. In Java wird häufig auf Klassen wie InputStream / OutputStream und Reader / Writer verwiesen. Zum Beispiel, wenn Sie beim Lesen einer Datei den folgenden Code schreiben

        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        BufferedReader reader = new BufferedReader(reader1);

        //Diese Verarbeitung mit Reader ...

Sie können InputStream (InputStreamReader) eine Funktion hinzufügen, um den Inhalt einer Datei in Byte-Einheiten zu verarbeiten, und eine Pufferfunktion (BufferedReader) hinzufügen.

In dem obigen Beispiel wird die Dateidatenverarbeitung unter Verwendung eines Lesegeräts fortgesetzt, jedoch kann vor der Datenverarbeitung eine Vorverarbeitung wie Datenformung und -korrektur erforderlich sein. Eine solche Vorverarbeitung kann auch mit dem Decorator-Muster implementiert werden. Hier ist eine Beispielimplementierung (der vollständige Text von PreprocedReaderOld.java finden Sie unter GitHub).

PreprocedReaderOld.java


/**
 *Implementierungsbeispiel für die Vorverarbeitung (Decorator-Musterversion)
 */
public class PreprocedReaderOld extends PipedReader {
    protected Reader in;

    public PreprocedReaderOld(Reader in) throws IOException {
        this.in = in;
        doPreproc();
    }

    protected void doPreproc() throws IOException {
        PipedWriter pipedOut = new PipedWriter();
        this.connect(pipedOut);

        BufferedReader reader = new BufferedReader(in);
        BufferedWriter writer = new BufferedWriter(pipedOut);
        try (reader; writer;) {
            String line;
            int lineNo = 1;
            while ((line = reader.readLine()) != null) {
                pipedOut.write(preprocess(lineNo, line));
                pipedOut.write('\n');
                lineNo++;
            }
        }
    }

    protected String preprocess(int lineNo, String line) {
        return "!Vorverarbeitung! " + line;
    }

    @Override
    public void close() throws IOException {
        in.close();
    }
}

Diese Ausführungsklasse für die Vorverarbeitung wird angewendet, indem wie bei anderen Dekoratoren ein Konstruktoraufruf wie unten gezeigt hinzugefügt wird.

        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        Reader reader2 = new PreprocedReaderOld(reader1); //Vorverarbeitung hinzugefügt
        BufferedReader reader = new BufferedReader(reader2);

        //Diese Verarbeitung mit Reader ...

Wenn zusätzliche Vorverarbeitung erforderlich ist, definiert dieses Beispiel eine neue Ausführungsklasse für die Vorverarbeitung, die die Ausführungsklasse für die Vorverarbeitung "PreprocedReaderOld" erbt und die Methode "preprocess (int lineNo, String line)" überschreibt. Wir werden Konstruktoraufrufe hinzufügen.

        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        Reader reader2 = new PreprocedReaderOld(reader1); //Vorverarbeitung hinzugefügt
        Reader reader3 = new PreprocedReaderOld2(reader2); //Vorverarbeitung Teil 2 hinzugefügt
        Reader reader4 = new PreprocedReaderOld3(reader3); //Vorverarbeitungsteil 3 hinzugefügt
        BufferedReader reader = new BufferedReader(reader4);

        //Diese Verarbeitung mit Reader ...

Das obige Beispiel ist ein Implementierungsbeispiel, bei dem das Decorator-Muster für die Vorverarbeitung von Dateien verwendet wird.

Über eine funktionale Schnittstelle umschreiben

Lassen Sie uns von nun an das obige Implementierungsbeispiel über die Funktionsschnittstelle neu schreiben.

Bereiten Sie zunächst die folgende Funktionsschnittstelle "Preprocess" vor.

    /**
     *Vorverarbeitungsschnittstelle
     */
    @FunctionalInterface
    public static interface Preprocess {

        /**
         *Vorverarbeitung
         *
         * @param lineNo Zielzeilennummer
         * @param line Zielzeilenzeichenfolge
         * @Ergebnis der Vorverarbeitung zurückgeben
         */
        public String apply(int lineNo, String line);

        // ----- ----- //

        /**
         *Vorverarbeitungssynthese
         *
         * @param next Vorverarbeitung zum Synthetisieren
         * @Rückverarbeitung nach der Synthese
         */
        default Preprocess compose(Preprocess next) {
            return (int n, String v) -> next.apply(n, this.apply(n, v));
        }

        /**
         *Einheitselement
         *
         * @return
         */
        public static Preprocess identity() {
            return (lineNo, line) -> line;
        }

        /**
         *Utility-Funktion, die mehrere Vorprozesse synthetisiert
         *
         * @param preprocs Vorverarbeitung für die Synthese
         * @return
         */
        static Preprocess compose(final Preprocess... preprocs) {
            return Stream.of(preprocs).reduce((preproc, next) -> preproc.compose(next)).orElse(identity());
        }
    }

Die Vorverarbeitungsausführungsklasse wird wie folgt implementiert. Der einzige Unterschied zu der in der ersten Hälfte dargestellten Decorator-Musterversion von "PreprocedReader Old" besteht in der Handhabung der Vorverarbeitung.

PreprocedReader.java


public class PreprocedReader extends PipedReader {
    private final Reader in;

    /**
     *Vorverarbeitung (synthetisiert).
     *Der Anfangswert ist das Einheitselement.
     */
    private Preprocess preprocs = Preprocess.identity();

    public PreprocedReader(Reader in, Preprocess...preprocs) throws IOException {
        this.in = in;
        this.preprocs = Preprocess.compose(preprocs); //Behalten Sie die angegebene Vorverarbeitung nach dem Compositing bei
        doPreproc();
    }

    private void doPreproc() throws IOException {
        PipedWriter pipedOut = new PipedWriter();
        this.connect(pipedOut);

        BufferedReader reader = new BufferedReader(in);
        BufferedWriter writer = new BufferedWriter(pipedOut);
        try (reader; writer;) {
            String line;
            int lineNo = 1;
            while ((line = reader.readLine()) != null) {
                pipedOut.write(preprocs.apply(lineNo, line)); //Anwendung der Vorverarbeitung
                pipedOut.write('\n');
            }
        }
    }

    @Override
    public void close() throws IOException {
        in.close();
    }
}

In der Decorator-Musterversion wurde die als Methode implementierte Vorverarbeitung (preprocess (int lineNo, String line)) aufgerufen, im neu geschriebenen PreprocedReader wird die als Funktion implementierte Vorverarbeitung ( Preprocess) aufgerufen. Es wird als Konstruktorargument angegeben. Wenn mehrere Vorprozesse vorhanden sind, werden diese zu einem Vorprozess kombiniert und dann angewendet.

Jeder Vorprozess wird durch Implementieren von "Vorprozess" implementiert. Das folgende Beispiel implementiert drei Arten der Vorverarbeitung.

    /**
     *<< Vorverarbeitung >> Die Escape-Verarbeitung wird durchgeführt
     */
    private static class PreprocEscape implements Preprocess {
        @Override
        public String apply(int lineNo, String line) {
            return org.apache.commons.text.StringEscapeUtils.escapeJava(line);
        }
    }
    public static final Preprocess ESCAPE = new PreprocEscape(); //Syntax der Zuckerbeschichtung

    /**
     *<< Vorverarbeitung >> Schneiden Sie die Datei an der angegebenen Spaltenposition
     */
    private static class PreprocTrimCol implements Preprocess {

        private final int from;
        private final int to;

        public PreprocTrimCol(int from, int to) {
            this.from = from;
            this.to = to;
        }

        @Override
        public String apply(int lineNo, String line) {
            final int len = line.length();
            if (len < to) {
                return line.substring(from);
            } else if (to <= from) {
                return "";
            } else {
                return line.substring(from, to);
            }
        }
    }
    public static final Preprocess TRIM_COL(int from, int to) {  //Syntax der Zuckerbeschichtung
        return new PreprocTrimCol(from, to);
    }

    /**
     *<< Vorverarbeitung >> Gibt den Zeileninhalt an die Standardausgabe aus, ohne Zeilenoperationen auszuführen.
     */
    private static class PreprocDumpStdout implements Preprocess {
        @Override
        public String apply(int lineNo, String line) {
            System.out.println("[DUMP]"+line);

            return line;
        }
    }
    public static final Preprocess DUMP_STDOUT = new PreprocDumpStdout();  //Syntax der Zuckerbeschichtung

Die auf diese Weise hergestellte Vorverarbeitung wird wie folgt angewendet.

App.java


        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        Reader reader2 = new PreprocedReader(reader1, TRIM_COL(0, 5), DUMP_STDOUT, ESCAPE); //Vorverarbeitung hinzugefügt
        BufferedReader reader = new BufferedReader(reader2);

        //Diese Verarbeitung mit Reader ...

Im Decorator-Muster mussten mehrere Konstruktoren verschachtelt werden, aber in der umgeschriebenen Funktionsversion kann die Vorverarbeitung als Konstruktorargument aufgeführt werden. Die als Argument angegebene Vorverarbeitung wird in der Reihenfolge von links nach rechts ausgeführt.

In Fällen, in denen Sie mehrere Funktionen dynamisch wie folgt hinzufügen möchten, wird empfohlen, anstelle des Decorator-Musters die oben genannte Funktionsschnittstelle zu verwenden, da der Code häufig vereinfacht wird. Ich benutze es oft beim Definieren interner DSLs.

Quiz Die als Argument angegebene Vorverarbeitung wird von links nach rechts ausgeführt, aber wo in "Vorverarbeitung" sollte die Ausführung von rechts nach links geändert werden?

** Quellcode: ** Befindet sich unter GitHub


Referenz:

Recommended Posts

Refactor die Implementierung des Decorator-Musters mit einer funktionalen Schnittstelle
Erläutern Sie die Vorzüge des staatlichen Musters anhand des Bewertungsurteils des Films
Bis die Implementierung der Schnittstelle Lambda wird
Bedingte Verzweigung mit fließender Schnittstelle
Ausnahmebehandlung mit einer Fluidschnittstelle
Erstellen Sie Ausnahmen mit einer Fluid-Schnittstelle
Erstellen Sie mit dem Befehl eine JAR-Datei
Führen Sie DMN mit der Camunda DMN Engine aus
Dekorationsmuster
Dekorateur Muster
Geben Sie ein Suffix für die Methode an
Geben Sie ein Suffix für Methode 2 heraus
Stimmt die Anmerkungen auf der Schnittstelle mit Spring AOP überein
Erstellen Sie eine Mehrschlüsselkarte mit einer Standardbibliothek
Ich habe mit der Lautstärketaste mit der Android-App ein Sperrmuster erstellt. Fragment Edition