«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?
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.
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.
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.
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.
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