( Aux fins de cette question, quand je dis «interface», je veux dire la construction du langageinterface
, et non une «interface» dans l'autre sens du mot, c'est-à-dire les méthodes publiques qu'une classe propose au monde extérieur pour communiquer avec et le manipuler. )
Un couplage lâche peut être obtenu en faisant dépendre un objet d'une abstraction plutôt que d'un type de béton.
Cela permet un couplage lâche pour deux raisons principales: 1- les abstractions sont moins susceptibles de changer que les types en béton, ce qui signifie que le code dépendant est moins susceptible de se casser. 2- différents types de béton peuvent être utilisés lors de l'exécution, car ils s'adaptent tous à l'abstraction. De nouveaux types de béton peuvent également être ajoutés ultérieurement sans avoir à modifier le code dépendant existant.
Par exemple, considérons une classe Car
et deux sous Volvo
- classes et Mazda
.
Si votre code dépend de a Car
, il peut utiliser a Volvo
ou a Mazda
pendant l'exécution. Plus tard, des sous-classes supplémentaires pourraient être ajoutées sans qu'il soit nécessaire de modifier le code dépendant.
En outre, Car
- qui est une abstraction - est moins susceptible de changer que Volvo
ou Mazda
. Les voitures sont généralement les mêmes depuis un certain temps, mais Volvos et Mazdas sont beaucoup plus susceptibles de changer. C'est-à-dire que les abstractions sont plus stables que les types en béton.
Tout cela devait montrer que je comprends ce qu'est le couplage lâche et comment il est réalisé en fonction des abstractions et non des concrétions. (Si j'ai écrit quelque chose d'inexact, veuillez le dire).
Ce que je ne comprends pas, c'est ceci:
Les abstractions peuvent être des superclasses ou des interfaces.
Dans l'affirmative, pourquoi les interfaces sont-elles particulièrement appréciées pour leur capacité à permettre un couplage lâche? Je ne vois pas en quoi c'est différent que d'utiliser une superclasse.
Les seules différences que je vois sont les suivantes: 1- Les interfaces ne sont pas limitées par l'héritage unique, mais cela n'a pas grand-chose à voir avec le sujet du couplage lâche. 2- Les interfaces sont plus «abstraites» car elles n'ont aucune logique d'implémentation. Mais je ne vois toujours pas pourquoi cela fait une si grande différence.
Veuillez m'expliquer pourquoi les interfaces sont réputées pour permettre un couplage lâche, contrairement aux superclasses simples.
la source
interfaces are essential for single-inheritance languages like Java and C# because that's the only way in which you can aggregate different behaviors into a single class
(ce qui me conduit à la comparaison avec C ++, où les interfaces ne sont que des classes avec des fonctions virtuelles pures).Réponses:
Terminologie: je ferai référence à la construction du langage
interface
comme interface , et à l'interface d'un type ou d'un objet comme surface (faute d'un meilleur terme).Correct.
Pas tout à fait correct. Les langages actuels ne prévoient généralement pas qu'une abstraction changera (bien qu'il existe certains modèles de conception pour gérer cela). Séparer les spécificités des choses générales est l' abstraction. Cela se fait généralement par une couche d'abstraction . Cette couche peut être modifiée en quelques autres spécificités sans casser le code qui s'appuie sur cette abstraction - un couplage lâche est obtenu. Exemple non OOP: une
sort
routine peut être modifiée de Quicksort dans la version 1 à Tim Sort dans la version 2. Le code qui ne dépend que du résultat trié (c'est-à-dire s'appuie sur l'sort
abstraction) est donc découplé de l'implémentation de tri réelle.Ce que j'ai appelé la surface ci-dessus est la partie générale d'une abstraction. Il arrive maintenant dans la POO qu'un objet doit parfois prendre en charge plusieurs abstractions. Un exemple pas tout à fait optimal: Java
java.util.LinkedList
prend en charge à la fois l'List
interface qui concerne l'abstraction «collection indexée ordonnée» et prend en charge l'Queue
interface qui (en gros) concerne l'abstraction «FIFO».Comment un objet peut-il prendre en charge plusieurs abstractions?
C ++ n'a pas d'interfaces, mais il a plusieurs héritages, méthodes virtuelles et classes abstraites. Une abstraction peut alors être définie comme une classe abstraite (c'est-à-dire une classe qui ne peut pas être instanciée immédiatement) qui déclare, mais ne définit pas de méthodes virtuelles. Les classes qui implémentent les spécificités d'une abstraction peuvent alors hériter de cette classe abstraite et implémenter les méthodes virtuelles requises.
Le problème ici est que l'héritage multiple peut conduire au problème du diamant , où l'ordre dans lequel les classes sont recherchées pour une implémentation de méthode (MRO: ordre de résolution de méthode) peut conduire à des «contradictions». Il y a deux réponses à cela:
Définissez un ordre sain d'esprit et rejetez les ordres qui ne peuvent pas être linéarisés sensiblement. Le C3 MRO est assez sensible et fonctionne bien. Il a été publié en 1996.
Suivez la voie facile et rejetez l'héritage multiple tout au long.
Java a pris cette dernière option et a choisi l'héritage comportemental unique. Cependant, nous avons toujours besoin de la capacité d'un objet à prendre en charge plusieurs abstractions. Par conséquent, des interfaces doivent être utilisées qui ne prennent pas en charge les définitions de méthode, uniquement les déclarations.
Le résultat est que le MRO est évident (regardez simplement chaque superclasse dans l'ordre), et que notre objet peut avoir plusieurs surfaces pour un nombre quelconque d'abstractions.
Cela s'avère plutôt insatisfaisant, car assez souvent un peu de comportement fait partie de la surface. Considérez une
Comparable
interface:C'est très convivial (une belle API avec de nombreuses méthodes pratiques), mais fastidieux à mettre en œuvre. Nous aimerions que l'interface inclue
cmp
et implémente automatiquement les autres méthodes en fonction de la seule méthode requise. Les mixins , mais plus important encore les Traits [ 1 ], [ 2 ] résolvent ce problème sans tomber dans les pièges de l'héritage multiple.Cela se fait en définissant une composition de traits afin que les traits ne finissent pas réellement par participer au MRO - au lieu de cela, les méthodes définies sont composées dans la classe d'implémentation.
L'
Comparable
interface pourrait être exprimée en Scala commeLorsqu'une classe utilise ensuite ce trait, les autres méthodes sont ajoutées à la définition de classe:
Il en
Inty(4) cmp Inty(6)
serait ainsi-2
etInty(4) lt Inty(6)
seraittrue
.De nombreuses langues prennent en charge certains traits, et toute langue dotée d'un «protocole de métaobjet (MOP)» peut y être ajoutée. La récente mise à jour de Java 8 a ajouté des méthodes par défaut qui sont similaires aux traits (les méthodes dans les interfaces peuvent avoir des implémentations de secours de sorte qu'il est facultatif pour les classes d'implémentation d'implémenter ces méthodes).
Malheureusement, les traits sont une invention assez récente (2002), et sont donc assez rares dans les plus grandes langues dominantes.
la source
Premièrement, le sous-typage et l'abstraction sont deux choses différentes. Le sous-typage signifie simplement que je peux substituer des valeurs d'un type à des valeurs d'un autre type - aucun type ne doit être abstrait.
Plus important encore, les sous-classes dépendent directement des détails d'implémentation de leur superclasse. C'est le type de couplage le plus solide qui soit. En fait, si la classe de base n'est pas conçue en tenant compte de l'héritage, les modifications de la classe de base qui ne changent pas son comportement peuvent toujours casser les sous-classes, et il n'y a aucun moyen de savoir a priori si une rupture se produira. Ceci est connu comme le problème de la classe de base fragile .
L'implémentation d'une interface ne vous couple à rien sauf à l'interface elle-même, qui ne contient aucun comportement.
la source
you want an object named A to depend on an abstraction named B instead of a concrete implementation of that abstraction named C
que vous supposez que les classes ne sont en quelque sorte pas abstraites. Une abstraction est tout ce qui cache les détails de l'implémentation, donc une classe avec des champs privés est tout aussi abstraite qu'une interface avec les mêmes méthodes publiques.Il existe un couplage entre les classes parent et enfant, car l'enfant dépend du parent.
Disons que nous avons une classe A et que la classe B en hérite. Si nous entrons dans la classe A et changeons les choses, la classe B change aussi.
Disons que nous avons une interface I et que la classe B l'implémente. Si nous changeons l'interface I, bien que la classe B ne puisse plus l'implémenter, la classe B reste inchangée.
la source