[JAVA] Refactoriser l'implémentation du pattern Decorator avec une interface fonctionnelle

Aperçu

Refactorisons l'implémentation du modèle Decorator en utilisant l'interface fonctionnelle et la synthèse de fonctions introduites dans Java 8.

Motif décorateur

Le modèle Decorator vous permet d'ajouter dynamiquement de nouvelles fonctionnalités et de nouveaux comportements à des objets existants. Des classes telles que InputStream / OutputStream et Reader / Writer sont souvent désignées en Java. Par exemple, si vous écrivez le code suivant lors de la lecture d'un fichier

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

        //Ce traitement à l'aide du lecteur ...

Vous pouvez ajouter une fonction pour gérer le contenu d'un fichier en unités d'octets à InputStream en unités de caractères (InputStreamReader) et ajouter une fonction de mise en mémoire tampon (BufferedReader).

Dans l'exemple ci-dessus, le traitement des données de fichier à l'aide d'un lecteur se poursuit, mais un prétraitement tel que la mise en forme et la correction des données peut être nécessaire avant le traitement des données. Un tel prétraitement peut également être mis en œuvre avec le modèle Decorator. Voici un exemple d'implémentation (le texte complet de PreprocedReaderOld.java peut être trouvé sur GitHub).

PreprocedReaderOld.java


/**
 *Exemple d'implémentation de prétraitement (version modèle Decorator)
 */
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 "!Pré-traitement! " + line;
    }

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

Cette classe d'exécution de prétraitement est appliquée en ajoutant un appel de constructeur comme indiqué ci-dessous, comme les autres décorateurs.

        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        Reader reader2 = new PreprocedReaderOld(reader1); //Pré-traitement ajouté
        BufferedReader reader = new BufferedReader(reader2);

        //Ce traitement à l'aide du lecteur ...

Si un prétraitement supplémentaire est nécessaire, cet exemple définit une nouvelle classe d'exécution de prétraitement qui hérite de la classe d'exécution de prétraitement PreprocedReaderOld et remplace la méthode preprocess (int lineNo, String line). Nous ajouterons des appels de constructeur.

        InputStream is0 = Files.newInputStream(pathIn);
        InputStreamReader reader1 = new InputStreamReader(is0, csIn);
        Reader reader2 = new PreprocedReaderOld(reader1); //Pré-traitement ajouté
        Reader reader3 = new PreprocedReaderOld2(reader2); //Ajout de la partie de pré-traitement 2
        Reader reader4 = new PreprocedReaderOld3(reader3); //Ajout de la partie de pré-traitement 3
        BufferedReader reader = new BufferedReader(reader4);

        //Ce traitement à l'aide du lecteur ...

Ce qui précède est un exemple d'implémentation utilisant le modèle Decorator pour le prétraitement des fichiers.

Réécrire à l'aide d'une interface fonctionnelle

À partir de maintenant, réécrivons l'exemple d'implémentation ci-dessus en utilisant l'interface fonctionnelle.

Tout d'abord, préparez l'interface fonctionnelle suivante Preprocess.

    /**
     *Interface de pré-traitement
     */
    @FunctionalInterface
    public static interface Preprocess {

        /**
         *Pré-traitement
         *
         * @param lineNo Numéro de ligne cible
         * @param line Chaîne de ligne cible
         * @retour Résultat du prétraitement
         */
        public String apply(int lineNo, String line);

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

        /**
         *Synthèse pré-traitement
         *
         * @param next Pré-traitement à synthétiser
         * @retour Prétraitement après synthèse
         */
        default Preprocess compose(Preprocess next) {
            return (int n, String v) -> next.apply(n, this.apply(n, v));
        }

        /**
         *Élément d'unité
         *
         * @return
         */
        public static Preprocess identity() {
            return (lineNo, line) -> line;
        }

        /**
         *Fonction utilitaire qui synthétise plusieurs pré-processus
         *
         * @param preprocs Prétraitement pour la synthèse
         * @return
         */
        static Preprocess compose(final Preprocess... preprocs) {
            return Stream.of(preprocs).reduce((preproc, next) -> preproc.compose(next)).orElse(identity());
        }
    }

La classe d'exécution de prétraitement est implémentée comme suit. La seule différence par rapport à la version du modèle Decorator de PreprocedReader Old illustrée dans la première moitié est la gestion du prétraitement.

PreprocedReader.java


public class PreprocedReader extends PipedReader {
    private final Reader in;

    /**
     *Pré-traitement (synthétisé).
     *La valeur initiale est l'élément unitaire.
     */
    private Preprocess preprocs = Preprocess.identity();

    public PreprocedReader(Reader in, Preprocess...preprocs) throws IOException {
        this.in = in;
        this.preprocs = Preprocess.compose(preprocs); //Préserver le prétraitement spécifié après la composition
        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)); //Application du prétraitement
                pipedOut.write('\n');
            }
        }
    }

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

Dans la version du modèle Decorator, le prétraitement implémenté en tant que méthode (preprocess (int lineNo, String line)) a été appelé, mais dans le PreprocedReader réécrit, le prétraitement implémenté en tant que fonction ( Preprocess) est appelé. Il est donné comme argument de constructeur, et s'il y a plusieurs pré-processus, ils sont combinés en un seul pré-processus puis appliqués.

Chaque prétraitement est implémenté en implémentant Preprocess. L'exemple suivant implémente trois types de prétraitement.

    /**
     *<< Pré-traitement >> Le traitement d'échappement est effectué
     */
    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(); //Syntaxe d'enrobage de sucre

    /**
     *<< Pré-traitement >> Coupez le fichier à la position de colonne spécifiée
     */
    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) {  //Syntaxe d'enrobage de sucre
        return new PreprocTrimCol(from, to);
    }

    /**
     *<< Pré-traitement >> Émet le contenu de la ligne sur la sortie standard sans effectuer d'opérations sur la ligne.
     */
    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();  //Syntaxe d'enrobage de sucre

Le prétraitement préparé de cette manière est appliqué comme suit.

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); //Pré-traitement ajouté
        BufferedReader reader = new BufferedReader(reader2);

        //Ce traitement à l'aide du lecteur ...

Dans le modèle Decorator, plusieurs constructeurs devaient être imbriqués, mais dans la version de fonction réécrite, le prétraitement peut être répertorié en tant qu'arguments de constructeur. Le prétraitement donné en argument est exécuté dans l'ordre de gauche à droite.

Dans les cas où vous souhaitez ajouter dynamiquement plusieurs fonctions comme celle-ci, il est recommandé d'utiliser l'interface fonctionnelle ci-dessus au lieu du modèle Decorator car le code est souvent simplifié. Je l'utilise souvent pour définir les DSL internes.

Quiz Le prétraitement donné en argument est exécuté de gauche à droite, mais où dans Preprocess doit être modifié pour s'exécuter de droite à gauche?

** Code source: ** Situé sur GitHub


référence:

Recommended Posts

Refactoriser l'implémentation du pattern Decorator avec une interface fonctionnelle
Expliquez les mérites du modèle d'État avec le jugement de notation du film
Jusqu'à ce que l'implémentation de l'interface devienne lambda
Branchement conditionnel avec une interface fluide
Gestion des exceptions avec une interface fluide
Créez des exceptions avec une interface fluide
Créez un fichier jar avec la commande
Exécutez DMN à l'aide du moteur Camunda DMN
Motif décorateur
Motif décorateur
Sortez avec un suffixe sur la méthode
Sortez avec un suffixe sur la méthode 2
Correspond aux annotations sur l'interface avec Spring AOP
Créer une carte multi-touches avec une bibliothèque standard
J'ai créé un schéma de verrouillage à l'aide de la touche de volume avec l'application Android. Édition fragmentée