Dois-je utiliser une interface lorsqu'une seule classe l'implémentera?

243

N’est-ce pas le but d’une interface que plusieurs classes adhèrent à un ensemble de règles et d’implémentations?

Lamin Sanneh
la source
16
Ou pour le rendre plus facile à unittest.
11
Autoriser plusieurs classes à implémenter des interfaces et faire en sorte que votre code dépende de ces interfaces est ESSENTIEL à l'isolation pour les tests unitaires. Si vous effectuez des tests unitaires, une autre classe implémentera cette interface.
StuperUser
2
Reddit discussion sur cette question.
Yannis
6
Les champs et méthodes publics constituent une "interface" à part entière. Si l'absence de polymorphisme est intentionnellement planifiée, il n'y a aucune raison d'utiliser une interface. Les tests unitaires mentionnés par d'autres sont une utilisation planifiée du polymorphisme.
mike30

Réponses:

205

A strictement parler, non, YAGNI s'applique. Cela dit, le temps que vous passerez à créer l'interface est minime, en particulier si vous disposez d'un outil de génération de code pratique qui effectue la majeure partie du travail à votre place. Si vous ne savez pas si vous allez avoir besoin de l'interface ou non, je dirais qu'il est préférable de privilégier 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 fictive pour les tests unitaires, qui n'est pas en production. La réponse d'Avner Shahar-Kashtan s'étend sur ce point.

Yannis
la source
84
Un test +1 signifie de toute façon que vous avez presque toujours deux implémentations
jk.
24
@ YannisRizos En désaccord avec votre dernier point à cause de Yagni. Activer une interface à partir de méthodes publiques de classes est trivial après le fait, tout comme le remplacement de CFoo par IFoo dans les classes consommatrices. Il est inutile de l'écrire avant le besoin.
Dan Neely
5
Je ne suis toujours pas sûr de suivre votre raisonnement. Étant donné que les outils de génération de code permettent de l'ajouter après coup encore moins cher, je vois encore moins de raisons de créer l'interface avant que vous n'en ayez explicitement besoin.
Dan Neely
6
Je pense qu'une interface manquante n'est pas un bon exemple pour YAGNI mais pour une "fenêtre brisée" et une documentation manquante. Les utilisateurs de la classe sont pratiquement obligés de coder contre l'implémentation, au lieu de l'abstraction comme ils le devraient.
Fabio Fracassi
10
Pourquoi voudriez-vous jamais polluer votre base de code avec un objet cruel sans signification juste pour satisfaire un framework de test? Je veux dire sérieusement, je suis juste un type autodidacte du côté du client JavaScript qui essaie de résoudre WTF a tort avec les implémentations OOD des développeurs C # et Java que je ne cesse de rencontrer dans ma quête pour devenir un généraliste complet, mais pourquoi ne pas t-ils taper l'IDE hors de vos mains et ne vous laissez pas le récupérer jusqu'à ce que vous appreniez à écrire du code propre et lisible quand ils vous attrapent faire ce genre de chose à l'université? C'est juste obscène.
Erik Reppen
144

Je répondrais que si vous avez besoin d'une interface ou non ne dépend pas du nombre de classes qui vont l'implémenter. Les interfaces sont un outil permettant de définir des contrats entre plusieurs sous-systèmes de votre application. Donc, 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 comme interface frontale pour les sous-systèmes encapsulés, quel que soit le nombre de classes qui les implémentent.

Voici une règle de base très utile:

  • Si vous faites classe se Fooréférer directement à la classe BarImpl, vous vous engagez fermement à changer Foochaque fois que vous changez BarImpl. En gros, vous les traitez comme une seule unité de code divisée en deux classes.
  • Si vous faites Fooréférence à l' interface Bar , vous vous engagez plutôt à éviter les changements Foolorsque vous changez 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 qu’elles ne doivent pas, et vous les commentez clairement pour décrire comment une implémentation est censée se comporter (et comment), votre demande sera beaucoup plus facile à comprendre , car ces interfaces commentaires fourniront une sorte de cahier des charges de l'application une description de la façon dont il est destiné à se comporter. Cela facilite beaucoup la lecture du code (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 en classe Foopour utiliser une interface Bar, vous pouvez généralement recompiler BarImplsans avoir besoin de recompiler Foo. Dans les grandes applications, cela peut faire gagner beaucoup de temps.

sacundim
la source
14
Je voterais cela beaucoup plus d'une fois, si je le pouvais. OMI la meilleure réponse à cette question.
Fabio Fracassi
4
Si la classe n'effectue qu'un seul rôle (c'est-à-dire qu'une seule interface est définie pour celui-ci), pourquoi ne pas le faire via des méthodes public / privé?
Matthew Finlay
4
1. organisation du code; le fait de disposer de l'interface dans son propre fichier comportant uniquement des signatures et des commentaires de documentation permet de garder le code propre. 2. Les frameworks qui vous obligent à exposer les méthodes que vous préférez garder privées (par exemple, les conteneurs qui injectent des dépendances via des setters publics). 3. Compilation séparée, comme mentionné déjà; si la classe Foodépend de l'interface, Barelle BarImplpeut être modifiée sans avoir à recompiler Foo. 4. Contrôle d'accès plus fin que les offres publiques / privées (exposer la même classe à deux clients via des interfaces différentes).
sacundim
3
Et dernier point: 5. Les clients (idéalement) ne devraient pas se préoccuper du nombre de classes de mon module, du nombre de classes ou même de leur capacité à en dire. Tout ce qu’ils devraient voir, c’est un ensemble de types et quelques usines ou façades pour lancer le mouvement. C'est-à-dire que je considère souvent qu'il est utile d'encapsuler même les classes de votre bibliothèque.
sacundim
4
@sacundim If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImplLors du changement de BarImpl, quels sont les changements qui peuvent être évités dans Foo en utilisant interface Bar? Puisque tant que les signatures et les fonctionnalités d'une méthode ne changent pas dans BarImpl, Foo ne nécessitera pas de changement, même sans interface (tout le but de l'interface). Je parle du scénario où seulement une classe BarImplimplémente Bar. Pour un scénario à plusieurs classes, je comprends le principe d’inversion de dépendance et l’utilité de son interface.
Shishir Gupta
101

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. Ainsi, lorsque 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. KISS (restez simple, stupide)

SuperM
la source
Pas toujours, vous pouvez utiliser l'interface dans une classe si cette classe est une classe générique.
John Isaiah Carmona
De plus, vous pouvez introduire une interface lorsque vous en avez enfin besoin facilement dans l'EDI moderne avec un refactoring "extraire l'interface".
Hans-Peter Störr
Considérons une classe d’utilité où il n’existe que des méthodes statiques publiques et un constructeur privé - parfaitement acceptable et préconisée par des auteurs tels que Joshua Bloch et al.
Darrell Teague
63

Bien qu'en théorie, vous ne devriez pas avoir une interface juste pour avoir une interface, la réponse de Yannis Rizos laisse entrevoir d' autres complications:

Lorsque vous écrivez des tests unitaires et utilisez des frameworks factices 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 réalité il y a l'implémentation interne. Chaque fois que vous commencez à écrire des simulacres, vous constaterez que l'extraction d'interfaces est logique.

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

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

Avner Shahar-Kashtan
la source
2
+1: Je ne suis pas sûr que YAGNI s'applique ici, car le fait de disposer d'une interface et d'utiliser des frameworks (par exemple, JMock, etc.) utilisant des interfaces peut réellement vous faire gagner du temps.
Déco
4
@Deco: les bons frameworks moqueurs (parmi lesquels JMock) n'ont même pas besoin d'interfaces.
Michael Borgwardt
12
Créer une interface uniquement à cause d'une limitation de votre cadre moqueur me semble une terrible raison. Utiliser, par exemple, EasyMock pour se moquer d’une classe est aussi simple qu’une interface.
Alb
8
Je dirais que c'est l'inverse. Utiliser des objets fantaisie dans le test signifie créer des implémentations alternatives à vos interfaces, par définition. Cela est vrai que vous créiez vos propres classes FakeImplementation ou que vous laissiez des frameworks moqueurs faire le gros du travail pour vous. Il se peut que certains frameworks, comme EasyMock, utilisent divers hacks et astuces de bas niveau pour simuler des classes de béton - et plus de puissance pour eux! - mais conceptuellement, les objets fantaisie sont des implémentations alternatives à un contrat.
Avner Shahar-Kashtan
1
Vous ne lancez pas les tests en production. Pourquoi une maquette pour une classe qui n'a pas besoin d'une interface a-t-elle besoin d'une interface?
Erik Reppen
33

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

Fabriquer Foo / FooImpl pour tout est très coûteux. L'EDI peut créer l'interface / implémentation gratuitement, mais lorsque vous naviguez dans le code, vous disposez de la charge cognitive supplémentaire de F3 / F12 pour foo.doSomething()vous amener à la signature d'interface et non de l'implémentation réelle souhaitée. De plus, vous avez le fouillis 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 infrastructures d'injection de dépendance

Les interfaces pour supporter les frameworks sont héritées. En Java, les interfaces étaient un préalable pour les mandataires dynamiques, pré-CGLIB. Aujourd'hui, vous n'en avez généralement pas besoin. C'est considéré comme un progrès et un avantage pour la productivité des développeurs que vous n'en ayez plus besoin dans EJB3, Spring, etc.

J'ai besoin de simulacres pour les tests unitaires

Si vous écrivez vous-même et que vous avez deux implémentations réelles, 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 moqueur comme Moq, EasyMock ou Mockito, vous pouvez vous moquer des classes sans avoir besoin d'interfaces. Cela revient à définir foo.method = mockImplementationdans un langage dynamique où des méthodes peuvent être assignées.

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

DIP indique que vous construisez pour dépendre de contrats (interfaces) et non de mises en œuvre. 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 ressemblait à une classe Matrix ou Polynomial. Les consommateurs disposent d'une API publique pour créer, ajouter des matrices, etc. Aucune matrice ni MatrixImpl n’était requise pour prouver ce point.

De plus, le 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 le DIP, c'est que votre interface et votre implémentation changent en phase de verrouillage, de sorte que vous devez toucher deux fichiers pour effectuer un changement 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 construisez une bibliothèque de classes pour une utilisation dans de nombreuses applications différentes.

Ceci est un corollaire à l'observation de l'oncle Bob Martin au sujet de la raillerie - vous devriez seulement avoir besoin de vous moquer des limites architecturales majeures. Dans une application Web, l’accès HTTP et DB est la limite principale. Tous les appels de classe / méthode entre les deux ne le sont pas. Même chose pour DIP.

Voir également:

Wrschneider
la source
4
Les classes moqueuses plutôt que les interfaces (du moins en Java et en C #) doivent être considérées comme obsolètes, car il n'existe aucun moyen d'empêcher le constructeur de la superclasse de s'exécuter, ce qui peut entraîner l'interaction involontaire de votre objet fictif avec l'environnement. Se moquer d'une interface est plus sûr et plus facile car vous n'avez pas à penser au code constructeur.
Jules
4
Je n'ai pas eu de problèmes avec les cours moqueurs, mais j'ai été frustré par la navigation IDE qui ne fonctionnait pas comme prévu. Résoudre un problème réel bat un problème hypothétique.
wrschneider
1
@Jules Vous pouvez vous moquer d'une classe concrète incluant son constructeur en Java.
Assylias
1
@assylias comment empêcher le constructeur de s'exécuter?
Jules
2
@Jules Cela dépend de votre framework moqueur - avec jmockit par exemple, vous pouvez simplement écrire new Mockup<YourClass>() {}et la classe entière, y compris ses constructeurs, est fausse, qu'il s'agisse d'une interface, d'une classe abstraite ou d'une classe concrète. Vous pouvez également "remplacer" le comportement du constructeur si vous le souhaitez. Je suppose qu'il existe des moyens équivalents à Mockito ou à Powermock.
Assylias
22

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

Bien concevoir et mettre des interfaces là où des interfaces sont nécessaires.

Comme je l'ai indiqué dans ma réponse à la réponse de Yanni , je ne pense pas que l'on puisse jamais avoir une règle absolue 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 passez d'un domaine de responsabilité à un autre.

Pour un exemple (horriblement artificiel), disons que vous construisez une Carclasse. 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, GasPedalet BrakePedal. Depuis cette voiture contient un AutomaticTransmission, vous n'avez pas besoin d'un ClutchPedal. (Et comme c'est une voiture épouvantable, il n'y a pas de climatisation, de radio ou de siège. En fait, les marchepieds manquent aussi - il vous suffit de vous accrocher à ce volant et d'espérer le meilleur!)

Alors, laquelle de ces classes nécessite une interface? La réponse peut être la totalité ou aucune des réponses - en fonction de votre conception.

Vous pouvez 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 partie intégrante de l'interface / API ICabin. Dans cet exemple, les classes (le cas échéant) sont probablement simples, avec quelques propriétés et une ou deux fonctions. Et ce que vous dites implicitement dans votre conception, c'est que ces classes existent uniquement pour prendre en charge toute implémentation concrète d'ICabin, et qu'elles ne peuvent exister par elles-mêmes ou qu'elles n'ont pas de sens en dehors du contexte ICabin.

C'est la même raison pour laquelle vous ne testez pas les membres privés de test unitaire: ils n'existent que pour prendre en charge l'API publique. Par conséquent, leur comportement doit être testé en testant l'API.

Donc, si votre classe existe uniquement pour prendre en charge une autre classe et que, théoriquement, vous la considérez comme n'ayant pas vraiment son propre domaine, il est alors bon de sauter l'interface. Mais si votre classe est suffisamment importante pour que vous la considériez suffisamment grande pour avoir son propre domaine, allez-y et donnez-lui une interface.


MODIFIER:

Fréquemment (y compris dans cette réponse), vous allez lire des choses comme 'domaine', 'dépendance' (souvent associées à 'injection') qui ne vous disent rien du tout lorsque vous commencez à programmer (ce n'est certainement pas le cas signifie rien pour moi). Pour domain, cela signifie exactement ce que cela ressemble à:

Le territoire sur lequel le pouvoir ou l'autorité est exercé; les biens d'un souverain ou d'un Commonwealth, ou similaires. Aussi utilisé au sens figuré. [WordNet sens 2] [1913 Webster]

En termes de mon exemple - considérons le IgnitionSwitch. Dans un wagon de voiture, le commutateur d'allumage est responsable de:

  1. Authentifier (ne pas identifier) ​​l'utilisateur (ils ont besoin de la clé correcte)
  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 la voyez, dans la plupart (toutes?) Des voitures plus récentes, il existe un interrupteur qui empêche la clé de sortir de la clé de contact lorsque la transmission est hors de Park. Elle pourrait donc 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’elle sait et dont elle est responsable.

Le IgnitionSwitchn'est pas responsable du GasPedal. Le commutateur d'allumage ignore complètement la pédale d'accélérateur. Ils fonctionnent tous les deux de manière totalement indépendante (une voiture n’aurait aucune valeur sans eux deux!).

Comme je l'ai dit à l'origine, cela dépend de votre conception. Vous pouvez concevoir un IgnitionSwitchqui a deux valeurs: On ( True) et Off ( False). Vous pouvez également le concevoir pour authentifier la clé fournie et une foule d’autres actions. C’est la partie la plus difficile d’être un développeur, c’est de décider où tracer les lignes dans le sable - et honnêtement, c’est tout à fait relatif. Ces lignes dans le sable sont toutefois importantes: c'est là que se trouve votre API et donc vos interfaces.

Wayne Werner
la source
Pourriez-vous préciser davantage ce que vous entendez par "avoir son propre domaine"?
Lamin Sanneh
1
@ LaminSanneh, élaboré. Est ce que ça aide?
Wayne Werner
8

Non (YAGNI) , sauf si vous envisagez d'écrire des tests pour d'autres classes utilisant cette interface, qui auraient intérêt à se moquer de l'interface.

Stijn
la source
8

De MSDN :

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

Les interfaces sont plus souples 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 dans lesquelles vous n'avez pas besoin d'hériter de la mise en œuvre 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 classe unique, il ne sera pas nécessaire de mettre en œuvre une interface, mais compte tenu de l’avenir de votre projet, il peut être utile de définir formellement le comportement nécessaire des classes.

lwm
la source
5

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

L'intention est l'un des aspects importants 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 modèle correct. Si, toutefois, le (s) comportement (s) est (sont) intrinsèque (s) au concept incarné par la classe, vous ne souhaiterez probablement pas du tout une interface.

La première question à poser est quelle est la nature de la chose ou du processus que vous essayez de représenter. Viennent ensuite les raisons pratiques pour appliquer cette nature d’une manière donnée.

Joshua Drake
la source
5

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

Cependant, la nécessité d'avoir une interface ou non ne dépend pas du nombre de ses implémentations. Le véritable rôle d’une interface est de définir un contrat précisant quel service doit être fourni et non pas comment il doit être mis en œuvre.

Une fois le contrat défini, deux équipes ou plus peuvent travailler indépendamment. Supposons que vous travaillez sur un module A et que cela dépend du module B, le fait de créer une interface sur B permet de continuer votre travail sans vous soucier de la mise en œuvre 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, celle-ci est toujours nécessaire.

En conclusion, une interface masque les détails de la mise en œuvre à ses utilisateurs. La programmation pour l’interface permet d’écrire plus de documents car il faut définir un contrat, écrire un logiciel plus modulaire, promouvoir les tests unitaires et accélérer la vitesse de développement.

Hui Wang
la source
2
Chaque classe peut avoir une interface publique (les méthodes publiques) et une interface privée (les détails d'implémentation). Je pourrais soutenir que l'interface publique de la classe est le contrat. Vous n'avez pas besoin d'un élément supplémentaire pour respecter ce contrat.
Fuhrmanator
4

Toutes les réponses ici sont très bonnes. En effet, la plupart du temps, vous n'avez pas besoin de mettre en œuvre une interface différente. Mais il y a des cas où vous voudrez peut -être le faire quand même. Voici quelques cas où je le fais:

La classe implémente une autre interface que je ne souhaite pas exposer Il
arrive souvent avec une classe d'adaptateur qui ponte 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,
principalement lorsqu’elle interagit avec des bibliothèques externes. Même s'il n'y a qu'une seule implémentation, j'utilise une interface pour 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 une classe de domaine, elle permet une meilleure séparation entre ces couches et évite surtout une 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();
}

Seulement un sous-ensemble de la méthode devrait être disponible pour la plupart des objets.
Surtout 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 au final, j'utilise une interface pour la même raison que j'utilise un champ privé: un autre objet ne devrait pas avoir accès à des choses auxquelles il ne devrait pas avoir accès. Si j'ai un cas comme ça, j'introduis une interface même si une seule classe l'implémente.

Laurent Bourgault-Roy
la source
2

Les interfaces sont vraiment importantes, mais essayez de contrôler leur nombre.

Après avoir créé 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 publié des propos 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 complète alors continuez à lire!

Lord Spa
la source
Le code 'spaghetti haché' est également appelé code de ravioli c2.com/cgi/wiki?RavioliCode
CurtainDog
Pour moi, ça sonne plus comme un code de lasagne ou un code de baklava - un code avec trop de couches. ;-)
dodgy_coder
2

Une des raisons pour lesquelles vous pouvez toujours souhaiter introduire une interface dans ce cas est de suivre le principe d'inversion de dépendance . En d’autres termes, le module qui utilise la classe dépendra de son abstraction (c’est-à-dire de l’interface) au lieu de dépendre d’une implémentation concrète. Il sépare les composants de haut niveau des composants de bas niveau.

dodgy_coder
la source
2

Il n'y a pas de vraie raison de faire quoi que ce soit. Les interfaces sont destinées à vous aider et non au programme de sortie. Ainsi, même si l'interface est implémentée par un million de classes, aucune règle ne vous oblige à en créer une. Vous en créez un pour que, lorsque vous-même ou quiconque utilise votre code, vous souhaitez modifier 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émentera.

John Demetriou
la source
1

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

Les objets simples tels que 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 dépendantes d'entre elles sont testées, l'objet de 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 tout au long de la mise en œuvre, il nécessite un fichier supplémentaire et certains IDE auront des difficultés à zoomer sur la mise en œuvre, pas sur l'interface.

Je définirais donc les interfaces uniquement pour les classes de niveau supérieur, pour lesquelles vous souhaitez une abstraction de la mise en œuvre.

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 il peut être utilisé exactement de la même manière. Donc, je ne pense pas qu'il soit nécessaire de recréer une interface qui existe déjà sous le nom de la classe.

Florian F
la source