web-dev-qa-db-fra.com

Dois-je utiliser une interface lorsqu'une seule classe la mettra en œuvre?

N'est-ce pas tout l'intérêt d'une interface que plusieurs classes adhèrent à un ensemble de règles et d'implémentations?

255
Lamin Sanneh

Strictement parlant, non, vous ne le faites pas, YAGNI s'applique. Cela dit, le temps que vous passerez à créer l'interface est minime, surtout si vous disposez d'un outil de génération de code pratique qui fait la plupart du travail pour vous. Si vous ne savez pas si vous allez avoir besoin de l'interface de ou non, je dirais qu'il vaut mieux se tromper en faveur de la définition d'une interface.

De plus, l'utilisation d'une interface même pour une seule classe vous fournira une autre implémentation simulée pour les tests unitaires, une qui n'est pas en production. réponse d'Avner Shahar-Kashtan développe sur ce point.

209
yannis

Je répondrais que si vous avez besoin d'une interface ou non pas dépend du nombre de classes qui l'implémenteront. Les interfaces sont un outil pour définir contrats entre plusieurs sous-systèmes de votre application; ce qui compte vraiment, c'est la façon dont votre application est divisée en sous-systèmes. Il devrait y avoir des interfaces en tant que front-end pour les sous-systèmes encapsulés, quel que soit le nombre de classes qui les implémentent.

Voici une règle d'or très utile:

  • Si vous faites en sorte que la classe Foo se réfère directement à la classe BarImpl, vous vous engagez fortement à changer Foo à chaque fois que vous changez BarImpl. Vous les traitez fondamentalement comme une unité de code divisée en deux classes.
  • Si vous faites Foo faire référence à l'interface Bar, vous vous engagez plutôt à évitez les modifications de Foo lorsque vous modifiez BarImpl.

Si vous définissez des interfaces aux points clés de votre application, vous réfléchissez bien aux méthodes qu’elles doivent prendre en charge et celles qu’elles ne devraient pas prendre en charge, et vous commentez clairement les interfaces pour décrire comment une implémentation est censée se comporter (et comment), votre application sera beaucoup plus facile à comprendre car ces interfaces commentées fourniront une sorte de spécification de l'application - une description de la façon dont elle est destiné à se comporter. Cela rend la lecture du code beaucoup plus facile (au lieu de demander "qu'est-ce que ce code est censé faire", vous pouvez demander "comment ce code fait-il ce qu'il est censé faire").

En plus de tout cela (ou à cause de cela), les interfaces favorisent une compilation séparée. Comme les interfaces sont faciles à compiler et ont moins de dépendances que leurs implémentations, cela signifie que si vous écrivez la classe Foo pour utiliser une interface Bar, vous pouvez généralement recompiler BarImpl sans avoir besoin pour recompiler Foo. Dans les grandes applications, cela peut gagner beaucoup de temps.

150
sacundim

Les interfaces sont désignées pour définir un comportement, c'est-à-dire un ensemble de prototypes de fonctions/méthodes. Les types implémentant l'interface implémenteront ce comportement, donc quand vous traitez avec un tel type, vous savez (en partie) quel comportement il a.

Il n'est pas nécessaire de définir une interface si vous savez que le comportement défini par celle-ci ne sera utilisé qu'une seule fois. BAISER (restez simple, stupide)

102
superM

Alors qu'en théorie, vous ne devriez pas avoir une interface juste pour avoir une interface, réponse de Yannis Rizos fait allusion à d'autres complications:

Lorsque vous écrivez des tests unitaires et utilisez des cadres fictifs tels que Moq ou FakeItEasy (pour nommer les deux plus récents que j'ai utilisés), vous créez implicitement une autre classe qui implémente l'interface. La recherche dans le code ou l'analyse statique peut prétendre qu'il n'y a qu'une seule implémentation, mais en fait il y a l'implémentation interne. Chaque fois que vous commencez à écrire des simulations, vous constaterez que l'extraction d'interfaces est logique.

Mais attendez, il y a plus. Il existe d'autres scénarios où il existe des implémentations d'interface implicites. L'utilisation de la pile de communication WCF de .NET, par exemple, génère un proxy pour un service distant, qui, là encore, implémente l'interface.

Dans un environnement de code propre, je suis d'accord avec le reste des réponses ici. Cependant, faites attention à tous les cadres, modèles ou dépendances que vous avez qui pourraient utiliser des interfaces.

62

Non, vous n'en avez pas besoin, et je considère que c'est un anti-modèle pour créer automatiquement des interfaces pour chaque référence de classe.

Il y a un vrai coût à faire Foo/FooImpl pour tout. Le IDE peut créer l'interface/l'implémentation gratuitement, mais lorsque vous naviguez dans le code, vous avez la charge cognitive supplémentaire de F3/F12 sur foo.doSomething() vous amenant à la la signature de l'interface et non l'implémentation réelle que vous souhaitez. De plus, vous avez l'encombrement de deux fichiers au lieu d'un pour tout.

Vous ne devriez donc le faire que lorsque vous en avez réellement besoin pour quelque chose.

Abordons maintenant les contre-arguments:

J'ai besoin d'interfaces pour les frameworks d'injection de dépendances

Les interfaces pour prendre en charge les cadres sont héritées. En Java, les interfaces étaient une exigence pour les proxys dynamiques, pré-CGLIB. Aujourd'hui, vous n'en avez généralement pas besoin. C'est considéré comme un progrès et une aubaine pour la productivité des développeurs que vous n'en avez plus besoin dans EJB3, Spring, etc.

J'ai besoin de simulations pour les tests unitaires

Si vous écrivez vos propres simulacres et avez deux implémentations réelles, alors une interface est appropriée. Nous n'aurions probablement pas cette discussion en premier lieu si votre base de code avait à la fois un FooImpl et un TestFoo.

Mais si vous utilisez un framework de simulation comme Moq, EasyMock ou Mockito, vous pouvez vous moquer des classes et vous n'avez pas besoin d'interfaces. Cela revient à définir foo.method = mockImplementation dans un langage dynamique où les méthodes peuvent être affectées.

Nous avons besoin d'interfaces pour suivre le principe d'inversion de dépendance (DIP)

DIP dit que vous construisez pour dépendre des contrats (interfaces) et non des implémentations. Mais une classe est déjà un contrat et une abstraction. C'est à cela que servent les mots clés publics/privés. À l'université, l'exemple canonique était quelque chose comme une classe Matrix ou Polynomial - les consommateurs ont une API publique pour créer des matrices, les ajouter etc., mais ne sont pas autorisés à se soucier si la matrice est implémentée sous forme clairsemée ou dense. Aucun IMatrix ou MatrixImpl n'était requis pour prouver ce point.

De plus, DIP est souvent sur-appliqué à chaque niveau d'appel de classe/méthode, pas seulement aux limites des modules principaux. Un signe que vous appliquez trop de DIP est que votre interface et votre implémentation changent en étape de verrouillage de sorte que vous devez toucher deux fichiers pour effectuer une modification au lieu d'un. Si DIP est appliqué correctement, cela signifie que votre interface ne devrait pas avoir à changer souvent. En outre, un autre signe est que votre interface n'a qu'un seul consommateur réel (sa propre application). Histoire différente si vous créez une bibliothèque de classes pour la consommation dans de nombreuses applications différentes.

C'est un corollaire du point de vue de l'oncle Bob Martin à propos de la moquerie - vous ne devriez avoir besoin de vous moquer que des principales limites architecturales. Dans une webapp, l'accès HTTP et DB sont les principales limites. Tous les appels de classe/méthode entre les deux ne le sont pas. Il en va de même pour DIP.

Voir également:

38
wrschneider

Il semble que les réponses des deux côtés de la clôture se résument comme suit:

Concevez bien et placez les interfaces là où elles sont nécessaires.

Comme je l'ai noté dans ma réponse à réponse de Yanni , je ne pense pas que vous puissiez jamais avoir une règle stricte et rapide sur les interfaces. La règle doit être, par définition, flexible. Ma règle sur les interfaces est qu'une interface doit être utilisée partout où vous créez une API. Et une API doit être créée partout où vous franchissez la frontière d'un domaine de responsabilité à un autre.

Pour un exemple (horriblement artificiel), disons que vous construisez une classe Car. Dans votre classe, vous aurez certainement besoin d'une couche d'interface utilisateur. Dans cet exemple particulier, il prend la forme d'un IginitionSwitch, SteeringWheel, GearShift, GasPedal et BrakePedal. Puisque cette voiture contient un AutomaticTransmission, vous n'avez pas besoin d'un ClutchPedal. (Et puisque c'est une voiture terrible, il n'y a pas de climatisation, de radio ou de siège. En fait, les planches de plancher manquent aussi - il suffit de s'accrocher à ce volant et d'espérer pour le mieux!)

Alors, laquelle de ces classes a besoin d'une interface? La réponse pourrait être toutes ou aucune d'entre elles, selon votre conception.

Vous pourriez avoir une interface qui ressemble à ceci:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

À ce stade, le comportement de ces classes devient une partie de l'interface/API ICabin. Dans cet exemple, les classes (s'il y en a) sont probablement simples, avec quelques propriétés et une ou deux fonctions. Et ce que vous déclarez implicitement avec votre conception, c'est que ces classes existent uniquement pour prendre en charge n'importe quelle implémentation concrète d'ICabin que vous avez, et qu'elles ne peuvent pas exister seules, ou qu'elles n'ont aucun sens en dehors du contexte ICabin.

C'est la même raison pour laquelle vous ne testez pas les membres privés - ils existent uniquement pour prendre en charge l'API publique, et donc leur comportement doit être testé par tester l'API.

Donc, si votre classe existe uniquement pour prendre en charge une autre classe, et conceptuellement, vous la voyez comme non vraiment ayant son propre domaine, il est bon de sauter l'interface. Mais si votre classe est suffisamment importante pour que vous la considériez suffisamment développée pour avoir son propre domaine, alors allez-y et donnez-lui une interface.


MODIFIER:

Fréquemment (y compris dans cette réponse), vous lirez des choses comme "domaine", "dépendance" (souvent couplées à "injection") qui ne signifient rien pour vous lorsque vous commencez à programmer (ils ne l'ont certainement pas fait rien pour moi). Pour le domaine, cela signifie exactement à quoi cela ressemble:

Le territoire sur lequel la domination ou l'autorité est exercée; les possessions d'un souverain ou d'un Commonwealth, ou similaire. Également utilisé au figuré. [WordNet sense 2] [1913 Webster]

Dans les termes de mon exemple - considérons le IgnitionSwitch. Dans une voiture à espace de viande, le contacteur d'allumage est responsable de:

  1. Authentifier (ne pas identifier) ​​l'utilisateur (il a besoin de la bonne clé)
  2. Fournir du courant au démarreur pour qu'il puisse réellement démarrer la voiture
  3. Fournir du courant au système d'allumage pour qu'il puisse continuer à fonctionner
  4. Couper le courant pour que la voiture s'arrête.
  5. Selon la façon dont vous le voyez, dans la plupart (toutes?) Les voitures plus récentes, il y a un interrupteur qui empêche la clé d'être retirée du contact lorsque la transmission est hors de Park, donc cela pourrait faire partie de son domaine. (En fait, cela signifie que je dois repenser et repenser mon système ...)

Ces propriétés constituent le domaine du IgnitionSwitch, ou en d'autres termes, ce qu'il sait et dont il est responsable.

Le IgnitionSwitch n'est pas responsable du GasPedal. Le commutateur d'allumage ignore complètement la pédale d'accélérateur dans tous les sens. Ils fonctionnent tous les deux complètement indépendants l'un de l'autre (même si une voiture serait assez inutile sans les deux!).

Comme je l'ai indiqué à l'origine, cela dépend de votre conception. Vous pouvez concevoir un IgnitionSwitch ayant deux valeurs: On (True) et Off (False). Ou vous pouvez le concevoir pour authentifier la clé fournie pour lui, et un hôte d'autres actions. C'est le plus difficile pour un développeur de décider où tracer les lignes dans le sable - et honnêtement, la plupart du temps, c'est complètement relatif. Ces lignes dans le sable sont cependant importantes - c'est là que se trouve votre API, et donc où vos interfaces devraient être.

22
Wayne Werner

Non (YAGNI) , sauf si vous envisagez d'écrire des tests pour d'autres classes utilisant cette interface, et ces tests gagneraient à se moquer de l'interface.

8
stijn

De MSDN :

Les interfaces sont mieux adaptées aux situations dans lesquelles vos applications nécessitent de nombreux types d'objets éventuellement non liés pour fournir certaines fonctionnalités.

Les interfaces sont plus flexibles que les classes de base car vous pouvez définir une seule implémentation pouvant implémenter plusieurs interfaces.

Les interfaces sont meilleures dans les situations où vous n'avez pas besoin d'hériter de l'implémentation d'une classe de base.

Les interfaces sont utiles dans les cas où vous ne pouvez pas utiliser l'héritage de classe. Par exemple, les structures ne peuvent pas hériter des classes, mais elles peuvent implémenter des interfaces.

Généralement dans le cas d'une seule classe, il ne sera pas nécessaire d'implémenter une interface, mais vu l'avenir de votre projet, il pourrait être utile de définir formellement le comportement nécessaire des classes.

8
lwm

Depuis que vous avez posé cette question, je suppose que vous avez déjà vu l'intérêt d'avoir une interface cachant plusieurs implémentations. Cela peut se manifester par le principe d'inversion de dépendance.

Cependant, la nécessité d'avoir ou non une interface ne dépend pas du nombre de ses implémentations. Le véritable rôle d'une interface est qu'elle définit un contrat indiquant quel service doit être fourni au lieu de la façon dont il doit être mis en œuvre.

Une fois le contrat défini, deux ou plusieurs équipes peuvent travailler indépendamment. Supposons que vous travaillez sur un module A et cela dépend du module B, le fait de créer une interface sur B permet de continuer votre travail sans vous soucier de l'implémentation de B car tous les détails sont masqués par l'interface. Ainsi, la programmation distribuée devient possible.

Même si le module B n'a qu'une seule implémentation de son interface, l'interface est toujours nécessaire.

En conclusion, une interface cache les détails d'implémentation à ses utilisateurs. La programmation à l'interface permet d'écrire plus de documents car le contrat doit être défini, d'écrire des logiciels plus modulaires, de favoriser les tests unitaires et d'accélérer la vitesse de développement.

5
Hui Wang

Pour répondre à la question: il y a plus que cela.

L'intention est un aspect important d'une interface.

Une interface est "un type abstrait qui ne contient aucune donnée, mais expose des comportements" - Interface (informatique) Donc, s'il s'agit d'un comportement ou d'un ensemble de comportements pris en charge par la classe, une interface est probablement le bon schéma. Si, cependant, le ou les comportements sont intrinsèques au concept incarné par la classe, alors vous ne voulez probablement pas du tout d'interface.

La première question à poser est quelle est la nature de la chose ou du processus que vous essayez de représenter. Continuez ensuite avec les raisons pratiques de mettre en œuvre cette nature d'une manière donnée.

5
Joshua Drake

Toutes les réponses ici sont très bonnes. En effet, la plupart du temps, vous n'avez pas besoin d'implémenter une interface différente. Mais il y a des cas où vous pouvez vouloir le faire quand même. Voici un cas où je le fais:

La classe implémente une autre interface que je ne veux pas exposer
Se produit souvent avec la classe d'adaptateur qui relie le code tiers.

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

La classe utilise une technologie spécifique qui ne devrait pas fuir
Surtout lors de l'interaction avec des bibliothèques externes. Même s'il n'y a qu'une seule implémentation, j'utilise une interface pour m'assurer de ne pas introduire de couplage inutile avec la bibliothèque externe.

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}

Communication entre couches
Même si une seule classe d'interface utilisateur implémente l'une des classes de domaine, elle permet une meilleure séparation entre ces couches et, surtout, elle évite la dépendance cyclique.

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

Seul un sous-ensemble de la méthode devrait être disponible pour la plupart des objets
Se produit généralement lorsque j'ai une méthode de configuration sur la classe concrète

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

Donc, à la fin, j'utilise l'interface pour la même raison que j'utilise un champ privé: les autres objets ne devraient pas avoir accès à des choses auxquelles ils ne devraient pas accéder. Si j'ai un cas comme celui-là, j'introduis une interface même si seulement une classe l'implémente.

4

Les interfaces sont vraiment importantes, mais essayez d'en contrôler le nombre.

Ayant décidé de créer des interfaces pour à peu près tout, il est facile de se retrouver avec du code "spaghetti haché". Je m'en remets à la plus grande sagesse d'Ayende Rahien qui a posté quelques mots très sages sur le sujet:

http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application

Ceci est son premier post d'une série entière alors continuez à lire!

3
Lord Spa

Il n'y a aucune vraie raison de faire quoi que ce soit. Les interfaces doivent vous aider et non le programme de sortie. Donc, même si l'Interface est implémentée par un million de classes, il n'y a aucune règle qui dit que vous devez en créer une. Vous en créez un de sorte que lorsque vous, ou toute autre personne qui utilise votre code, souhaitez changer quelque chose, il se répercute sur toutes les implémentations. La création d'une interface vous aidera dans tous les cas futurs où vous voudrez peut-être créer une autre classe qui l'implémente.

2
John Demetriou

Une raison pour laquelle vous voudrez peut-être toujours introduire une interface dans ce cas est de suivre le Dependency Inversion Principle . Autrement dit, le module qui utilise la classe dépendra d'une abstraction de celle-ci (c'est-à-dire de l'interface) au lieu de dépendre d'une implémentation concrète. Il dissocie les composants de haut niveau des composants de bas niveau.

2
dodgy_coder

Il n'est pas toujours nécessaire de définir une interface pour une classe.

Les objets simples comme les objets de valeur n'ont pas plusieurs implémentations. Ils n'ont pas besoin d'être moqués non plus. L'implémentation peut être testée seule et lorsque d'autres classes sont testées qui en dépendent, l'objet valeur réelle peut être utilisé.

N'oubliez pas que la création d'une interface a un coût. Il doit être mis à jour le long de l'implémentation, il a besoin d'un fichier supplémentaire et certains IDE auront du mal à zoomer dans l'implémentation, pas dans l'interface.

Je définirais donc des interfaces uniquement pour les classes de niveau supérieur, où vous voulez une abstraction de l'implémentation.

Notez qu'avec une classe, vous obtenez une interface gratuitement. Outre l'implémentation, une classe définit une interface à partir de l'ensemble des méthodes publiques. Cette interface est implémentée par toutes les classes dérivées. Ce n'est pas à proprement parler une interface, mais elle peut être utilisée exactement de la même manière. Je ne pense donc pas qu'il soit nécessaire de recréer une interface qui existe déjà sous le nom de la classe.

1
Florian F