N’est-ce pas le but d’une interface que plusieurs classes adhèrent à un ensemble de règles et d’implémentations?
N’est-ce pas le but d’une interface que plusieurs classes adhèrent à un ensemble de règles et d’implémentations?
Réponses:
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.
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:
Foo
référer directement à la classe BarImpl
, vous vous engagez fermement à changer Foo
chaque fois que vous changez BarImpl
. En gros, vous les traitez comme une seule unité de code divisée en deux classes.Foo
référence à l' interface Bar
, vous vous engagez plutôt à éviter les changements Foo
lorsque 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 Foo
pour utiliser une interface Bar
, vous pouvez généralement recompiler BarImpl
sans avoir besoin de recompiler Foo
. Dans les grandes applications, cela peut faire gagner beaucoup de temps.
Foo
dépend de l'interface, Bar
elle BarImpl
peut ê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).
If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
Lors 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 BarImpl
implémente Bar. Pour un scénario à plusieurs classes, je comprends le principe d’inversion de dépendance et l’utilité de son interface.
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)
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.
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 = mockImplementation
dans 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:
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.
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 Car
classe. 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
. 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:
Ces propriétés constituent le domaine du IgnitionSwitch
, ou en d’autres termes, ce qu’elle sait et dont elle est responsable.
Le IgnitionSwitch
n'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 IgnitionSwitch
qui 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.
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.
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.
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.
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.
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!
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.
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.
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.