[JAVA] Utilisez-vous correctement la méthode par défaut de l'interface?

introduction

«Java 8» a introduit la méthode par défaut de l'interface, et à partir de «Java 9», il est également possible de définir une méthode privée pour l'interface. C'est une fonctionnalité utile, mais si elle est mal utilisée, elle peut réduire la maintenabilité de votre application. Alors, quel type d'utilisation est souhaitable?

Mauvais exemple

public interface BadUsage {

    void someMethod();

    // (1)De type utilitaire pour l'héritage de mix-in et d'implémentation
    default String normalize(String str) {
        return str.toUpperCase().trim();
    }

    // (2)Traitement qui dépend d'autres objets
    default int getSomeValue() {
        return Singleton.getInstance().getValue();
    }
}

(1) de l'exemple de code ci-dessus utilise la méthode par défaut dans le but d'ajouter des fonctions en tant que mix-in à la classe qui implémente l'interface ou de fournir des fonctions utilitaires qui peuvent être couramment utilisées du côté de la classe d'implémentation. Je vais. «Le transfert plutôt que l'héritage» est une règle de fer orientée objet. Il est préférable d'éviter cette utilisation et d'utiliser des classes utilitaires et des classes d'assistance. (2) est similaire à (1) en termes d'utilisation, mais ce qui est plus problématique, c'est qu'il décrit des traitements qui dépendent d'autres objets. Dans cet exemple, nous accédons à un objet singleton, mais les autres traitements sont les suivants.

Si vous écrivez un tel processus, le test unitaire sera très difficile. L'interface ne doit pas dépendre de la classe concrète en premier lieu.

Comment identifier une mauvaise utilisation

La méthode par défaut de l'interface est public, qui est une opération qui est exposée à l'utilisateur comme la méthode abstraite. La méthode par défaut devrait donc également faire partie de la responsabilité de l'interface. Puisque l '«objet» en orientation objet est une collection de «données» et de «comportement», la méthode d'instance qui représente le comportement doit donner accès aux données de l'objet (variable d'instance).

Bien sûr, l'interface elle-même ne peut pas avoir de variables d'instance, donc la méthode par défaut accède indirectement aux variables d'instance de la classe d'implémentation en appelant d'autres méthodes abstraites. Inversement, vous devez remettre en question la méthode par défaut, qui n'appelle pas d'autres méthodes abstraites.

Exemple d'utilisation correct

Supposons que vous ayez l'interface suivante: Dans l'interface du «Modèle composite», il y a «Composite» et «Feuille» dans la classe concrète.

Component.java


public interface Component {

    String getName();

    int getPoint();

    List<Component> getChildren();

    boolean isLeaf();

}

Supposons que vous souhaitiez ajouter une méthode find à cette interface qui renvoie une liste de composants répondant à vos critères. Cette méthode peut être décrite comme un processus courant qui ne dépend pas de la classe concrète, donc implémentez-la comme méthode par défaut.

Component.java


    default List<Component> find(Predicate<Component> p) {
        return getChildren().stream().filter(p).collect(Collectors.toList());
    }

Ainsi, quelle que soit la véritable classe concrète, la méthode par défaut devrait implémenter un comportement commun qui serait universel si l'interface Component était correctement implémentée.

Cas exceptionnel

Il existe des cas exceptionnels où il est acceptable (ne pas appeler d'autres méthodes abstraites). Dans ce cas, au lieu de créer une classe de base abstraite qui fournit une implémentation par défaut, vous n'avez besoin que d'une interface. Prenons un exemple concret. Supposons que vous souhaitiez introduire un modèle de visiteur qui analyse la structure composite.

Visitor.java


public interface Visitor {

    void visit(Composite composite);

    void visit(Leaf leaf);
}

Component.java


public interface Component {
    // ...
    void accept(Visitor visitor);
}

Voici un exemple de classe d'implémentation de Visitor concrète.

LeafCounterVisitor.java


public class LeafCounterVisitor implements Visitor {

    private int count = 0;

    @Override
    public void visit(Composite composite) {
        //Je ne suis pas intéressé par les nœuds composites, donc implémentation vide
    }

    @Override
    public void visit(Leaf leaf) {
        count ++;
    }

    public int getCount() {
        return count;
    }
}

Les visites de nœuds qui n'intéressent pas le visiteur sont des implémentations vides comme décrit ci-dessus. Il n'y a que deux types de nœuds cette fois, mais s'il y en a dix, remplacer toutes les méthodes de visite et placer une implémentation vide peut être une tâche ardue. Dans ce cas, il existe un moyen de préparer une classe de base abstraite avec une implémentation vide préparée à l'avance comme indiqué ci-dessous et d'hériter de cette classe. (Dans les classes dérivées, il vous suffit de remplacer la méthode de visite qui vous intéresse)

BaseVisitor.java


public abstract class BaseVisitor implements Visitor {

    @Override
    public void visit(Composite composite) {
        //Mise en œuvre vide
    }

    @Override
    public void visit(Leaf leaf) {
        //Mise en œuvre vide
    }
}

La méthode par défaut de l'interface élimine le besoin de la classe de base abstraite ci-dessus.

Visitor.java(Version modifiée)


public interface Visitor {

    default void visit(Composite composite) {}

    default void visit(Leaf leaf) {}
}

Je pense qu'un tel usage ne s'écarte pas de l'intention initiale de la méthode par défaut en ce sens qu'elle fournit une implémentation par défaut.

Résumé

La méthode par défaut de l'interface doit être traitée comme toute autre méthode abstraite et utilisée comme une conception orientée objet, sauf qu'elle a une implémentation par défaut.

Recommended Posts

Utilisez-vous correctement la méthode par défaut de l'interface?
La version d'Elasticsearch que vous utilisez est-elle compatible avec Java 11?
Le piège que l'implémentation par défaut de l'interface Java 8 apporte
À propos du comportement par défaut de l'arrondi des fractions à l'aide de java.text.NumberFormat
Êtes-vous toujours épuisé par la recherche d'exemples de vidéos? Un bouton pour envoyer des vidéos FANZA à Slack lorsque vous appuyez dessus.
Présentez docker à l'application que vous créez
Utilisez-vous correctement la méthode par défaut de l'interface?
[Java] Le rapport de couverture n'a pas pu être créé en combinant la méthode par défaut de l'interface Cobertura +
Comparaison Java à l'aide de la méthode compareTo ()
[Rails 6] détruit en utilisant la méthode des ressources
Traitement itératif de Ruby en utilisant chaque méthode (trouver la somme de 1 à 10)
Essayez d'utiliser || au lieu de l'opérateur ternaire
Quelles sont les fonctionnalités mises à jour de Java 13
J'ai approfondi ma compréhension de la méthode de fusion
Obtenez un message d'erreur en utilisant n'importe quelle méthode?
Vérifiez le fonctionnement de l'interface à travers le thread
[Java] Lisez-vous correctement le message d'erreur? [Comment lire la trace de la pile]
Si vous utilisez Android Room et que vous souhaitez modifier la définition de colonne
[Order method] Définit l'ordre des données dans Rails
[Java] Gestion des Java Beans dans la chaîne de méthodes
L'ordre des modificateurs de méthode Java est fixe
Lorsque vous souhaitez utiliser la méthode à l'extérieur
Sortie de la façon d'utiliser la méthode slice
Quels sont les avantages de DI et de Thymeleaf?
J'ai essayé d'utiliser le profileur d'IntelliJ IDEA
Arguments avec des valeurs par défaut Prenez la méthode full_title du tutoriel Rails comme exemple
Quel homme aimez-vous? Identifier les cibles d'amour en utilisant le modèle de la chaîne de responsabilité