Quel est le nom du motif (anti) suivant? Quels sont ses avantages et ses inconvénients?

28

Au cours des derniers mois, je suis tombé plusieurs fois sur la technique / le schéma suivant. Cependant, je n'arrive pas à trouver un nom spécifique, et je ne suis pas sûr à 100% de tous ses avantages et inconvénients.

Le schéma se présente comme suit:

Dans une interface Java, un ensemble de méthodes communes est défini comme d'habitude. Cependant, en utilisant une classe interne, une instance par défaut est divulguée via l'interface.

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

Pour moi, il semble que le plus grand avantage réside dans le fait qu'un développeur n'a besoin que de connaître l'interface et non ses implémentations, par exemple au cas où il souhaiterait rapidement créer une instance.

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

De plus, j'ai vu cette technique utilisée avec Spring afin de fournir dynamiquement des instances en fonction de la configuration. À cet égard, il semble également que cela puisse aider à la modularisation.

Néanmoins, je ne peux pas secouer le sentiment qu'il s'agit d'une mauvaise utilisation de l'interface car elle couple l'interface avec l'une de ses implémentations. (Principe d'inversion des dépendances, etc.) Quelqu'un pourrait-il m'expliquer comment s'appelle cette technique, ainsi que ses avantages et ses inconvénients?

Mise à jour:

Après un certain temps de réflexion, j'ai revérifié et remarqué que la version singleton suivante du modèle était utilisée beaucoup plus souvent. Dans cette version, une instance statique publique est exposée via l'interface qui n'est initialisée qu'une seule fois (car le champ est final). De plus, l'instance est presque toujours récupérée à l'aide de Spring ou d'une fabrique générique qui dissocie l'interface de l'implémentation.

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

En bref: il semble qu'il s'agisse d'un modèle singleton / factory personnalisé, qui permet essentiellement d'exposer une instance ou un singleton via son interface. En ce qui concerne les inconvénients, quelques-uns ont été nommés dans les réponses et commentaires ci-dessous. Jusqu'à présent, l'avantage semble résider dans sa commodité.

Jérôme
la source
une sorte de motif singleton?
Bryan Chen
Je pense que vous avez raison de dire que l'interface ne doit pas "connaître" ses implémentations. Doit probablement Vehicle.Defaultêtre déplacé vers le haut dans l'espace de noms du package en tant que classe d'usine, par exemple VehicleFactory.
août
7
Soit dit en passant,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski
20
L'anti-modèle ici est d'utiliser l'anti-modèle d'analogie automobile, étroitement lié à l'anti-modèle d'analogie animale. La plupart des logiciels n'ont ni roues ni pieds.
Pieter B
@PieterB: :-)))) Tu as fait ma journée!
Doc Brown

Réponses:

24

Default.getInstanceest à mon humble avis juste une forme très spécifique d'une méthode d'usine , mélangée à une convention de dénomination tirée du modèle singleton (mais sans être une implémentation de ce dernier). Dans la forme actuelle, il s'agit d'une violation du " principe de responsabilité unique ", car l'interface (qui sert déjà à déclarer une API de classe) prend la responsabilité supplémentaire de fournir une instance par défaut, qui serait beaucoup mieux placée dans une autre VehicleFactoryclasse.

Le principal problème causé par cette construction est qu'elle induit une dépendance cyclique entre Vehicleet Car. Par exemple, dans le formulaire actuel, il ne sera pas possible de placer Vehicledans une bibliothèque et Cardans une autre. Utiliser une classe d'usine distincte et y placer la Default.getInstanceméthode résoudrait ce problème. Il peut également être judicieux de donner un nom différent à cette méthode pour éviter toute confusion liée aux singletons. Le résultat pourrait donc être quelque chose comme

VehicleFactory.createDefaultInstance()

Doc Brown
la source
Merci pour votre réponse! Je conviens que cet exemple particulier est une violation du PÉR. Cependant, je ne sais pas s'il s'agit toujours d'une violation au cas où l'implémentation serait récupérée dynamiquement. Par exemple en utilisant Spring (ou un framework similaire) afin de récupérer une instance particulière définie par exemple par un fichier de configuration.
Jérôme
1
@ Jérôme: À mon humble avis, une classe non imbriquée nommée VehicleFactoryest plus conventionnelle que la construction imbriquée Vehicle.Default, en particulier avec la méthode trompeuse "getInstance". Bien qu'il soit possible de contourner la dépendance cyclique en utilisant Spring, je préférerais personnellement la première variante juste pour le bon sens. Et, cela vous laisse toujours la possibilité de déplacer l'usine vers un autre composant, de changer ensuite l'implémentation pour qu'elle fonctionne sans Spring etc.
Doc Brown
Souvent, la raison d'utiliser une interface plutôt qu'une classe est de permettre aux classes qui ont d'autres objectifs d'être utilisables par du code qui a besoin d'une implémentation d'interface. Si une classe d'implémentation par défaut peut répondre à tous les besoins de code qui ont simplement besoin de quelque chose qui implémente l'interface de manière "normale", spécifier un moyen de créer une instance semblerait une chose utile pour l'interface.
supercat
Si Car est une implémentation standard très générique de Vehicle, il ne s'agit pas d'une violation de SRP. Le véhicule n'a toujours qu'une seule raison de changer, par exemple lorsque Google ajoute une autodrive()méthode. Votre voiture très générique doit alors changer pour implémenter cette méthode, par exemple en lançant une exception NotImplementedException, mais cela ne confère pas de raison supplémentaire de changer sur le véhicule.
user949300
@ user949300: le SRP n'est pas un concept "mathématiquement prouvable". Et les décisions de conception de logiciels ne sont pas toujours «en noir et blanc». Le code d'origine n'est pas si mauvais qu'il ne pourrait pas être utilisé dans le code de production, bien sûr, et il peut y avoir des cas où la commodité l'emporte sur la dépendance cyclique, comme l'OP l'a noté lui-même dans sa "mise à jour". Veuillez donc lire ma réponse comme "considérez si l'utilisation d'une classe d'usine séparée ne vous sert pas mieux". Notez également que l'auteur a changé un peu sa question d'origine après avoir donné ma réponse.
Doc Brown du
3

Cela ressemble à un motif d' objet nul pour moi.

Mais cela dépend fortement de la façon dont la Carclasse est implémentée. S'il est implémenté de manière "neutre", alors c'est définitivement un objet nul. Cela signifie que si vous pouvez supprimer toutes les vérifications nulles sur l'interface et mettre l'instance de cette classe au lieu de chaque occurrence de null, le code doit toujours fonctionner correctement.

De plus, s'il s'agit de ce modèle, il n'y a aucun problème à créer un couplage étroit entre l'interface elle-même et la mise en œuvre d'un objet nul. Parce que l'objet nul peut être partout où cette interface est utilisée.

Euphorique
la source
2
Bonjour et merci pour la réponse. Bien que je sois à peu près sûr que dans mon cas, cela n'a pas été utilisé pour implémenter le modèle "Null Object", c'est une explication intéressante.
Jérôme