J'ai encore une certaine confusion à propos de cette chose. Ce que j'ai trouvé jusqu'à présent, c'est
(Des questions similaires ont déjà été posées ici, mais j'avais d'autres points.)
L'interface est une collection de SEULES méthodes abstraites et de champs finaux.
Il n'y a pas d'héritage multiple en Java.
Les interfaces peuvent être utilisées pour réaliser plusieurs héritages en Java.
Un point fort de l'héritage est que nous pouvons utiliser le code de la classe de base dans la classe dérivée sans l'écrire à nouveau. C'est peut-être la chose la plus importante pour que l'héritage soit là.
À présent..
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code), comment pouvons-nous dire que si nous implémentons une interface, c'est de l'héritage? Nous n'utilisons pas son code.
Q2. Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour obtenir un héritage multiple?
Q3. Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire du code encore et encore dans toutes les classes que nous implémentons.
Alors pourquoi faire des interfaces?
NOTE: J'ai trouvé un cas dans lequel les interfaces sont utiles. Un exemple de cela est comme dans l'interface Runnable, nous avons la méthode public void run () dans laquelle nous définissons la fonctionnalité du thread et il est construit dans le codage que cette méthode sera exécutée en tant que thread séparé. Nous avons donc juste besoin de coder quoi faire dans le fil, Rest est prédéfini. Mais cette chose peut également être réalisée en utilisant des classes abstraites et tout.
Alors quels sont les avantages exacts de l'utilisation des interfaces? Est-ce vraiment l'héritage multiple que nous obtenons en utilisant les interfaces?
Les interfaces sont une collection de champs statiques finaux et de méthodes abstraites (nouvellement Java 8 a ajouté la prise en charge des méthodes statiques dans une interface).
Les interfaces sont établies dans des situations où nous savons qu'une tâche doit être accomplie, mais la façon dont elle doit être effectuée peut varier. En d'autres termes, nous pouvons dire que nous implémentons des interfaces pour que notre classe commence à se comporter d'une manière particulière.
Laissez-moi vous expliquer avec un exemple, nous savons tous ce que sont les animaux. Comme le lion est un animal, le singe est un animal, l'éléphant est un animal, la vache est un animal et ainsi de suite. Maintenant, nous savons que tous les animaux mangent quelque chose et dorment. Mais la façon dont chaque animal peut manger quelque chose ou dormir peut différer. Comme Lion mange en chassant d'autres animaux alors que la vache mange de l'herbe. Mais les deux mangent. Nous pouvons donc avoir un pseudo-code comme celui-ci,
interface Animal {
public void eat();
public void sleep();
}
class Lion implements Animal {
public void eat() {
// Lion's way to eat
}
public void sleep(){
// Lion's way to sleep
}
}
class Monkey implements Animal {
public void eat() {
// Monkey's way to eat
}
public void sleep() {
// Monkey's way to sleep
}
}
Selon le pseudo-code mentionné ci-dessus, tout ce qui est capable de manger ou de dormir sera appelé un animal ou nous pouvons dire que c'est obligatoire pour tous les animaux de manger et de dormir, mais la façon de manger et de dormir dépend de l'animal.
Dans le cas des interfaces, nous héritons uniquement du comportement, pas du code réel comme dans le cas de l'héritage des classes.
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code), comment pouvons-nous dire que si nous implémentons une interface, c'est de l'héritage? Nous n'utilisons pas son code.
L'implémentation d'interfaces est un autre type d'héritage. Il n'est pas similaire à l'héritage de classes car dans cette classe enfant d'héritage, le vrai code à réutiliser à partir de la classe de base.
Q2. Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour obtenir un héritage multiple?
Il est dit qu'une classe peut implémenter plusieurs interfaces. Mais nous devons comprendre que cet héritage est différent de l'héritage des classes.
Q3. Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire du code encore et encore dans toutes les classes que nous implémentons.
L'implémentation d'une interface oblige la classe à remplacer ses toutes les méthodes abstraites.
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code), comment pouvons-nous dire que si nous implémentons une interface, c'est de l'héritage? Nous n'utilisons pas son code.
Nous ne pouvons pas. Les interfaces ne sont pas utilisées pour réaliser l'héritage multiple. Ils le remplacent par une construction plus sûre, bien que légèrement moins puissante. Notez le mot clé implements
plutôt que extends
.
Q2. Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour obtenir un héritage multiple?
Ils ne sont pas. Avec les interfaces, une seule classe peut avoir plusieurs " vues ", différentes API ou capacités. Par exemple. Une classe peut être Runnable
et Callable
en même temps, tandis que les deux méthodes font effectivement la même chose.
Q3. Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire du code encore et encore dans toutes les classes que nous implémentons.
Les interfaces sont une sorte d'héritage multiple sans aucun problème que celui-ci introduit (comme le problème Diamond ).
Il existe peu de cas d'utilisation pour les interfaces:
L'objet a effectivement deux identités: un Tank
est à la fois un Vehicle
et un Weapon
. Vous pouvez utiliser une instance de Tank
où la première ou la seconde est attendue (polymorphisme). Il s'agit rarement d'un cas dans la vie réelle et est en fait un exemple valable où l'héritage multiple serait préférable (ou traits).
Responsabilités simples: une instance de Tank
objet dans un jeu est également Runnable
pour vous permettre de l'exécuter dans un thread et un ActionListener
pour répondre aux événements de la souris.
Interfaces de rappel: si l'objet implémente une interface de rappel donnée, il est informé de son cycle de vie ou d'autres événements.
Interfaces de marqueur: n'ajoutant aucune méthode, mais facilement accessible via instanceof
pour découvrir les capacités ou les souhaits des objets. Serializable
et Cloneable
en sont des exemples.
Ce que vous recherchez est un trait (comme dans Scala), malheureusement indisponible en Java.
BAISER
J'ai cherché pendant des jours, voire des semaines en essayant de comprendre les interfaces et semble lire la même aide générique; Je n'essaie pas de dénigrer les contributions, mais je pense que l'ampoule vient de cliquer, donc je suis chuffé :))
Je préfère Keep It Simple Stupid, donc je vais présenter ma nouvelle vue d'interfaces.
Je suis un codeur occasionnel mais je veux publier ce code que j'ai écrit en VB.NET (le principe est le même pour les autres langues), pour aider les autres à comprendre les interfaces.
Si je me trompe, veuillez en informer les autres dans les commentaires de suivi.
Explication
Trois boutons sur un formulaire, en cliquant sur chacun d'eux enregistre une référence de classe différente à la variable d'interface (_data). Le point entier des différentes références de classe dans une variable d'interface, c'est ce que je ne comprenais pas car il semblait redondant, puis sa puissance devient évidente avec la msgbox, j'ai seulement besoin d'appeler la même méthode pour effectuer la tâche dont j'ai besoin, dans ce case 'GetData ()', qui utilise la méthode de la classe actuellement détenue par la variable de référence d'interface (_data).
Donc, cependant, je souhaite obtenir mes données (à partir d'une base de données, du Web ou d'un fichier texte), cela ne se fait qu'en utilisant même nom de méthode; le code derrière cette implémentation ... je m'en fiche.
Il est alors facile de changer chaque code de classe en utilisant l'interface sans aucune dépendance ... c'est un objectif clé dans OO et l'encapsulation.
Quand utiliser
Classes de code et si vous remarquez le même verbe utilisé pour les méthodes, comme 'GetData ()', alors c'est un bon candidat pour implémenter une interface sur cette classe et utiliser ce nom de méthode comme abstraction/interface.
J'espère sincèrement que cela aidera un collègue noob avec ce principe difficile.
Public Class Form1
Private _data As IData = Nothing
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
_data = New DataText()
MsgBox(_data.GetData())
End Sub
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
_data = New DataDB()
MsgBox(_data.GetData())
End Sub
Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
_data = New DataWeb()
MsgBox(_data.GetData())
End Sub
End Class
Public Interface IData
Function GetData() As String
End Interface
Friend Class DataText : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataText"
End Function
End Class
Friend Class DataDB : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataDB"
End Function
End Class
Friend Class DataWeb : Implements IData
Friend Function GetData() As String Implements IData.GetData
Return "DataWeb"
End Function
End Class
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code), comment pouvons-nous dire que si nous implémentons une interface, c'est de l'héritage? Nous n'utilisons pas son code.
Malheureusement, dans un langage courant, le mot inheritance
est encore fréquemment utilisé lorsqu'une classe implémente une interface, bien que interface implementation
serait un terme préférable - IMO, le terme inheritance
devrait être strictement utilisé avec l'héritage d'une classe concrète ou abstraite. Dans des langages comme C++ et C #, la même syntaxe (c'est-à-dire Subclass : Superclass
et Class : Interface
) est utilisé à la fois pour l'héritage de classe et l'implémentation d'interface, ce qui peut avoir contribué à la propagation de l'utilisation abusive de Word inheritance
avec les interfaces. Java a une syntaxe différente pour extend
ing une classe par opposition à implement
ing une interface, ce qui est une bonne chose.
Q2 Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour réaliser l'héritage multiple?
Vous pouvez obtenir "l'effet" de l'héritage multiple grâce à la composition - en implémentant plusieurs interfaces sur une classe, puis en fournissant des implémentations pour toutes les méthodes, propriétés et événements requis de toutes les interfaces de la classe. Une technique courante pour ce faire avec des classes concrètes consiste à faire des relations "has-a" (composition) avec des classes qui implémentent les interfaces externes en "câblant" l'implémentation à chacune des implémentations de classes internes. (Les langages tels que C++ prennent directement en charge l'héritage concret multiple, mais ce qui crée d'autres problèmes potentiels comme le problème du diamant).
Q3 Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire du code encore et encore dans toutes les classes que nous implémentons.
Les interfaces permettent aux classes existantes (par exemple les frameworks) d'interagir avec vos nouvelles classes sans les avoir jamais vues auparavant, en raison de la capacité de communiquer via une interface connue. Considérez une interface comme un contrat. En implémentant cette interface sur une classe, vous êtes contractuellement tenu de respecter les obligations qui lui sont imposées, et une fois ce contrat mis en œuvre, votre classe devrait pouvoir être utilisée de manière interchangeable avec tout autre code consommant l'interface.
Exemple du monde réel
Un exemple "réel" serait la législation et la convention (interface) entourant une prise électrique murale dans un pays particulier. Chaque appareil électrique branché sur la prise doit répondre aux spécifications (contrat) que les autorités ont définies pour la prise, par ex. le positionnement des fils de ligne, de neutre et de terre, la position et la coloration de l'interrupteur marche/arrêt et la conformité de la tension électrique, de la fréquence et du courant maximum qui seront fournis par le interface
lors de sa commutation sur.
L'avantage de découpler l'interface (c'est-à-dire une prise murale standard) plutôt que de simplement souder des fils ensemble est que vous pouvez brancher (et débrancher) un ventilateur, une bouilloire, un adaptateur double ou un nouvel appareil à inventer l'année prochaine. , même si cet appareil n'existait pas lors de la conception de l'interface. Pourquoi? Parce qu'il sera conforme aux exigences de l'interface.
Pourquoi utiliser des interfaces?
Les interfaces sont idéales pour le couplage lâche des classes, et sont l'un des piliers de l'oncle Bob SOLIDE paradigme, en particulier le Dependency Inversion Principle
et Interface Segregation Principles
.
En termes simples, en s'assurant que les dépendances entre les classes sont couplées uniquement sur les interfaces (abstractions), et non sur d'autres classes concrètes, cela permet de remplacer la dépendance par toute autre implémentation de classe qui répond aux exigences de l'interface.
Lors des tests, des talons et des simulations de dépendances peuvent être utilisés pour tester individuellement chaque classe, et l'interaction de la classe avec la dépendance peut être "espionnée".
C'est une question très ancienne et la version Java-8 a ajouté plus de fonctionnalités et de puissance d'interface.
Une déclaration d'interface peut contenir
Les seules méthodes qui ont des implémentations dans l'interface sont par défaut et statique méthodes.
Utilisations de l'interface:
Strategy_pattern
Jetez un œil à cette question SE associée pour obtenir un exemple de code afin de mieux comprendre les concepts:
Comment aurais-je dû expliquer la différence entre une interface et une classe abstraite?
Revenons à vos requêtes:
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code), comment pouvons-nous dire que si nous implémentons une interface, c'est de l'héritage? Nous n'utilisons pas son code.
Q2. Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour obtenir un héritage multiple?
L'interface peut contenir du code pour les méthodes statiques et par défaut . Ces méthodes par défaut offrent une compatibilité descendante et les méthodes statiques fournissent des fonctions helper/utility.
Vous ne pouvez pas avoir un véritable héritage multiple dans Java et l'interface n'est pas le moyen de l'obtenir. L'interface ne peut contenir que des constantes. Vous ne pouvez donc pas hériter de l'état mais vous pouvez implémenter un comportement.
Vous pouvez remplacer héritage par capacité. L'interface fournit plusieurs capacités pour implémenter des classes.
Q3. Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire du code encore et encore dans toutes les classes que nous implémentons.
Reportez-vous à la section " utilisations de l'interface " dans ma réponse.
L'héritage est lorsqu'une classe dérive d'une autre classe (qui peut être abstraite) ou d'une interface. Le point fort orienté objet (héritage) n'est pas la réutilisation du code (il existe de nombreuses façons de le faire), mais le polymorphisme.
Le polymorphisme est lorsque vous avez du code qui utilise l'interface, dont l'objet d'instance peut appartenir à n'importe quelle classe dérivée de cette interface. Par exemple, je peux avoir une telle méthode: public void Pet (IAnimal animal) et cette méthode obtiendra un objet qui est une instance de Dog ou Cat qui hérite d'IAnimal. ou je peux avoir un tel code: IAnimal animal et ensuite je peux appeler une méthode de cette interface: animal.Eat () que Dog ou Cat peut implémenter d'une manière différente.
Le principal avantage des interfaces est que vous pouvez hériter de certaines d'entre elles, mais si vous avez besoin d'hériter d'une seule, vous pouvez également utiliser une classe abstraite. Voici un article qui explique plus sur les différences entre une classe abstraite et une interface: http://www.codeproject.com/KB/cs/abstractsvsinterfaces.aspx
Vieille question. Je suis surpris que personne n'ait cité les sources canoniques: Java: an Overview par James Gosling, Design Patterns: Elements of Reusable Object-Oriented Software by the Gang of Four or Effective Java par Joshua Bloch (entre autres sources).
Je vais commencer par une citation:
Une interface est simplement une spécification d'un ensemble de méthodes auxquelles un objet répond. Il n'inclut aucune variable d'instance ou implémentation. Les interfaces peuvent être héritées de plusieurs manières (contrairement aux classes) et elles peuvent être utilisées d'une manière plus flexible que la structure d'héritage de classe rigide habituelle. (Gosling, p.8)
Maintenant, prenons vos hypothèses et vos questions une par une (j'ignorerai volontairement les Java 8 fonctionnalités).
Avez-vous vu le mot clé abstract
dans les interfaces Java? Non. Vous ne devez donc pas considérer une interface comme un ensemble de méthodes abstraites. Peut-être que vous êtes trompé par les soi-disant interfaces C++, qui sont des classes avec uniquement des méthodes virtuelles pures. C++, de par sa conception, n'a pas (et n'a pas besoin d'avoir) d'interfaces, car il a un héritage multiple.
Comme expliqué par Gosling, vous devriez plutôt considérer une interface comme "un ensemble de méthodes auxquelles un objet répond". J'aime voir une interface et la documentation associée comme un contrat de service. Il décrit ce que vous pouvez attendre d'un objet qui implémente cette interface. La documentation doit spécifier les pré et post-conditions (par exemple les paramètres ne doivent pas être nuls, la sortie est toujours positive, ...) et les invariants (une méthode qui ne modifie pas l'état interne de l'objet). Ce contrat est le cœur, je pense, de la POO.
Effectivement.
Java omet de nombreuses fonctionnalités du C++ rarement utilisées, mal comprises et déroutantes qui, selon notre expérience, apportent plus de chagrin que d'avantages. Cela consiste principalement en une surcharge d'opérateur (bien qu'elle ait une surcharge de méthode), un héritage multiple et des coercitions automatiques étendues. (Gosling, p.2)
Rien à ajouter.
Non, simlpy car il n'y a pas d'héritage multiple en Java. Voir au dessus.
C'est ce qu'on appelle "l'héritage d'implémentation". Comme vous l'avez écrit, c'est un moyen pratique de réutiliser le code.
Mais il a une contrepartie importante:
les classes parentales définissent souvent au moins une partie de la représentation physique de leurs sous-classes. Parce que l'héritage expose une sous-classe aux détails de l'implémentation de son parent, on dit souvent que "l'héritage rompt l'encapsulation" [Sny86]. L'implémentation d'une sous-classe devient tellement liée à l'implémentation de sa classe parente que tout changement dans l'implémentation du parent forcera la sous-classe à changer. (GOF, 1.6)
(Il y a une citation similaire dans Bloch, point 16.)
En fait, l'héritage sert également un autre objectif:
L'héritage de classe combine l'héritage d'interface et l'héritage d'implémentation. L'héritage d'interface définit une nouvelle interface en termes d'une ou plusieurs interfaces existantes. L'héritage d'implémentation définit une nouvelle implémentation en termes d'une ou plusieurs implémentations existantes. (GOF, Annexe A)
Les deux utilisent le mot clé extends
en Java. Vous pouvez avoir des hiérarchies de classes et des hiérarchies d'interfaces. Les premiers partagent la mise en œuvre, les seconds partagent l'obligation.
L'implémentation d'une interface n'est pas un héritage. C'est la mise en œuvre. Ainsi le mot clé implements
.
Pas d'héritage multiple en Java. Voir au dessus.
La question la plus importante est: pourquoi voudriez-vous avoir un héritage multiple? Je peux penser à deux réponses: 1. donner des types multiples à un objet; 2. pour réutiliser le code.
Dans la POO, un objet peut avoir différents types. Par exemple, en Java, un ArrayList<E>
A les types suivants: Serializable
, Cloneable
, Iterable<E>
, Collection<E>
, List<E>
, RandomAccess
, AbstractList<E>
, AbstractCollection<E>
et Object
(j'espère n'avoir oublié personne). Si un objet a des types différents, différents consommateurs pourront l'utiliser sans être conscient de ses spécificités. J'ai besoin d'un Iterable<E>
Et vous me donnez un ArrayList<E>
? C'est bon. Mais si j'ai besoin maintenant d'un List<E>
Et que vous me donnez un ArrayList<E>
, Ça va aussi. Etc.
Comment tapez-vous un objet dans la POO? Vous avez pris l'interface Runnable
comme exemple, et cet exemple est parfait pour illustrer la réponse à cette question. Je cite le document officiel Java:
De plus, Runnable permet à une classe d'être active sans sous-classer Thread.
Voici le point: l'héritage est un moyen pratique de taper objets. Vous voulez créer un fil? Sous-classons la classe Thread
. Vous voulez qu'un objet ait différents types, utilisons l'héritage multiple. Argh. Il n'existe pas en Java. (En C++, si vous voulez qu'un objet ait différents types, l'héritage multiple est la solution.)
Comment donner alors plusieurs types à un objet? En Java, vous pouvez taper votre objet directement. C'est ce que vous faites lorsque votre classe implements
l'interface Runnable
. Pourquoi utiliser Runnable
si vous êtes fan d'héritage? Peut-être parce que votre classe est déjà une sous-classe d'une autre classe, disons A
. Maintenant, votre classe a deux types: A
et Runnable
.
Avec plusieurs interfaces, vous pouvez attribuer plusieurs types à un objet. Il vous suffit de créer une classe qui implements
plusieurs interfaces. Tant que vous respectez les contrats, c'est ok.
C'est un sujet difficile; J'ai déjà cité le GOF sur la rupture de l'encapsulation. Une autre réponse mentionne le problème du diamant. Vous pourriez également penser au principe de responsabilité unique:
Une classe ne devrait avoir qu'une seule raison de changer. (Robert C. Martin, Développement de logiciels agiles, principes, modèles et pratiques)
Avoir une classe parent peut donner à une classe une raison de changer, en plus de ses propres responsabilités:
L'implémentation de la superclasse peut changer d'une version à l'autre, et si c'est le cas, la sous-classe peut se casser, même si son code n'a pas été touché. En conséquence, une sous-classe doit évoluer en tandem avec sa superclasse (Bloch, point 16).
J'ajouterais un problème plus prosaïque: j'ai toujours un sentiment étrange quand j'essaie de trouver le code source d'une méthode dans une classe et je ne le trouve pas. Ensuite, je me souviens: il doit être défini quelque part dans la classe parente. Ou dans la classe des grands-parents. Ou peut-être même plus. Un bon IDE est un atout précieux dans ce cas, mais il reste, dans mon esprit, quelque chose de magique. Rien de semblable avec les hiérarchies d'interfaces, car le javadoc est la seule chose dont j'ai besoin: un raccourci clavier dans le IDE et je l'obtiens.
Cependant, l'hérédité présente des avantages:
Il est sûr d'utiliser l'héritage dans un package, où les implémentations de sous-classe et de super-classe sont sous le contrôle des mêmes programmeurs. Il est également sûr d'utiliser l'héritage lors de l'extension de classes spécifiquement conçues et documentées pour l'extension (Article 17: Concevoir et documenter pour l'héritage ou bien l'interdire). (Bloch, point 16)
Un exemple de classe "spécialement conçue et documentée pour l'extension" dans Java est AbstractList
.
Mais Bloch et GOF insistent sur ceci: "Privilégiez la composition à l'héritage":
La délégation est un moyen de rendre la composition aussi puissante pour la réutilisation que l'héritage [Lie86, JZ91]. En délégation, deux objets sont impliqués dans le traitement d'une demande: un objet récepteur délègue des opérations à son délégué. Ceci est analogue aux sous-classes qui reportent les requêtes aux classes parentes. (GOF p.32)
Si vous utilisez la composition, vous n'aurez pas à écrire le même code encore et encore. Vous venez de créer une classe qui gère les duplications, et vous passez une instance de cette classe aux classes qui implémentent le interface. C'est un moyen très simple de réutiliser du code. Et cela vous aide à suivre le principe de responsabilité unique et à rendre le code plus testable. Rust et Go n'ont pas d'héritage (ils n'ont pas de classes non plus), mais je ne pense pas que le code soit plus redondant que dans d'autres langages OOP.
De plus, si vous utilisez la composition, vous vous retrouverez naturellement à utiliser des interfaces pour donner à votre code la structure et la flexibilité dont il a besoin (voir les autres réponses sur les cas d'utilisation des interfaces).
Remarque: vous pouvez partager du code avec Java 8 interfaces
Et enfin, une dernière citation:
Pendant la mémorable session de questions/réponses, quelqu'un lui a demandé [James Gosling]: "Si vous pouviez refaire Java, que changeriez-vous?" "Je laisserais de côté les cours" (n'importe où sur le net, je ne sais pas si c'est vrai)
Les deux méthodes fonctionnent (interfaces et héritage multiple).
Réponse rapide et pratique
Les interfaces sont meilleures lorsque vous avez plusieurs années d'expérience dans l'utilisation de l'héritage multiple qui ont des super classes avec uniquement une définition de méthode et aucun code.
Une question complémentaire peut être: "Comment et pourquoi migrer le code des classes abstraites vers les interfaces".
Si vous n'utilisez pas de nombreuses classes abstraites dans votre application ou si vous n'avez pas beaucoup d'expérience avec celle-ci, vous préférerez peut-être ignorer les interfaces.
Ne vous précipitez pas pour utiliser des interfaces.
Réponse longue et ennuyeuse
Les interfaces sont très similaires, voire équivalentes aux classes abstraites.
Si votre code a de nombreuses classes abstraites, il est temps que vous commenciez à penser en termes d'interfaces.
Le code suivant avec des classes abstraites:
/* File name : MyStreamsClasses.Java */
import Java.lang.*;
// Any number of import statements
public abstract class InputStream {
public void ReadObject(Object MyObject);
}
public abstract class OutputStream {
public void WriteObject(Object MyObject);
}
public abstract class InputOutputStream
imnplements InputStream, OutputStream {
public void DoSomethingElse();
}
Peut être remplacé par:
/* File name : MyStreamsInterfaces.Java */
import Java.lang.*;
// Any number of import statements
public interface InputStream {
public void ReadObject(Object MyObject);
}
public interface OutputStream {
public void WriteObject(Object MyObject);
}
public interface InputOutputStream
extends InputStream, OutputStream {
public void DoSomethingElse();
}
À votre santé.
Les interfaces sont créées de manière à ce qu'une classe implémente la fonctionnalité au sein de l'interface et se comporte conformément à cette interface.
Q1. Comme les interfaces n'ont que des méthodes abstraites (pas de code) alors comment pouvons-nous dire que si nous implémentons une interface alors c'est de l'héritage? Nous n'utilisons pas son code.
Ce n'est pas un héritage égal. C'est juste similaire. Laissez-moi expliquer:
VolvoV3 extends VolvoV2, and VolvoV2 extends Volvo (Class)
VolvoV3 extends VolvoV2, and VolvoV2 implements Volvo (Interface)
line1: Volvo v = new VolvoV2();
line2: Volvo v = new VolvoV3();
Si vous ne voyez que la ligne 1 et la ligne 2, vous pouvez en déduire que VolvoV2 et VolvoV3 ont le même type. Vous ne pouvez pas déduire si Volvo est une superclasse ou Volvo est une interface.
Q2. Si la mise en œuvre d'une interface n'est pas un héritage, alors comment les interfaces sont-elles utilisées pour obtenir plusieurs héritages?
Maintenant en utilisant des interfaces:
VolvoXC90 implements XCModel and Volvo (Interface)
VolvoXC95 implements XCModel and Volvo (Interface)
line1: Volvo a = new VolvoXC90();
line2: Volvo a = new VolvoXC95();
line3: XCModel a = new VolvoXC95();
Si vous ne voyez que les lignes 1 et 2, vous pouvez en déduire que VolvoXC90 et VolvoXC95 ont le même type (Volvo). Vous ne pouvez pas déduire que Volvo est une superclasse ou Volvo est une interface.
Si vous ne voyez que les lignes 2 et 3, vous pouvez déduire que Volvo95 implémente deux types, XCModel et Volvo, dans Java vous savez qu'au moins un doit être une interface. Si ce code a été écrit en C++ par exemple, il peut s'agir des deux classes. Par conséquent, de multiples héritages.
Q3. Quoi qu'il en soit, quel est l'avantage d'utiliser les interfaces? Ils n'ont pas de code. Nous devons écrire le code encore et encore dans toutes les classes que nous implémentons.
Imaginez un système où vous utilisez une classe VolvoXC90 dans 200 autres classes.
VolvoXC90 v = new VolvoXC90();
Si vous devez faire évoluer votre système pour lancer VolvoXC95, vous devez modifier 200 autres classes.
Imaginez maintenant un système où vous utilisez une interface Volvo dans 10 000 000 de classes.
// Create VolvoXC90 but now we need to create VolvoXC95
Volvo v = new VolvoFactory().newCurrentVolvoModel();
Maintenant, si vous devez faire évoluer votre système pour créer des modèles VolvoXC95, vous devez modifier une seule classe, l'usine.
C'est une question de bon sens. Si votre système n'est composé que de quelques classes et a peu de mises à jour, utiliser des interfaces partout est contre-productif. Pour les grands systèmes, cela peut vous faire économiser beaucoup de douleur et éviter de risquer d'adopter des interfaces.
Je vous recommande d'en savoir plus sur les principes S.O.L.I.D et de lire le livre Effective Java. Il a de bonnes leçons d'ingénieurs logiciels expérimentés.
Alors. Il y a beaucoup d'excellentes réponses ici expliquant en détail ce qu'est une interface. Pourtant, c'est un exemple de son utilisation, de la manière dont l'un de mes meilleurs collègues me l'a expliqué il y a des années, avec ce que j'ai appris à l'université au cours des deux dernières années.
Une interface est une sorte de "contrat". Il expose certaines méthodes, champs, etc., disponibles. Il ne révèle aucun de ses détails d'implémentation, seulement ce qu'il retourne et quels paramètres il prend. Et c'est là que réside la réponse à la troisième question, et ce que je ressens est l'une des plus grandes forces de la POO moderne:
"Code par ajout, pas par modification" - Magnus Madsen, AAU
C'est ce qu'il a appelé au moins, et il peut l'avoir d'un autre endroit.L'exemple de code ci-dessous est écrit en C #, mais tout ce qui est montré peut être fait à peu près de la même manière en Java.
Ce que nous voyons est une classe appelée SampleApp, qui a un seul champ, IOContext. IOContext est une interface. SampleApp ne se soucie pas de savoir comment il enregistre ses données, il a juste besoin de le faire, dans sa méthode "doSomething ()".
Nous pouvons imaginer qu'au début du processus de développement, l'enregistrement des données peut avoir été plus important que COMMENT elles ont été enregistrées, et donc, le développeur a choisi d'écrire simplement la classe FileContext. Plus tard, cependant, il a dû prendre en charge JSON pour une raison quelconque. Il a donc écrit la classe JSONFileContext, qui hérite de FileContext. Cela signifie qu'il s'agit en fait d'un IOContext, qui a les fonctionnalités de FileContext, sauf le remplacement de FileContexts SaveData et LoadData, il utilise toujours ses méthodes "écriture/lecture".
L'implémentation de la classe JSON a été un peu de travail, comparé à l'écriture de la classe, et à ce qu'elle hérite juste d'IOContext.
Le champ de SampleApp aurait pu être uniquement de type 'FileContext', mais de cette façon, il aurait été limité à n'utiliser que des enfants de cette classe. En faisant l'interface, nous pouvons même faire l'implémentation de SQLiteContext, et écrire dans une base de données, SampleApp ne le saura jamais et ne s'en souciera jamais, et lorsque nous aurons écrit la classe sql lite, nous n'aurons besoin que d'une seule modification de notre code: new JSONFileContext();
devient à la place new SQLiteContext();
Nous avons toujours nos anciennes implémentations et pouvons revenir à celles-ci si le besoin s'en fait sentir. Nous n'avons rien cassé, et toutes les modifications apportées à notre code sont d'une demi-ligne, qui peuvent être modifiées en un clin d'œil.
donc: Code par addition, PAS par modification.
namespace Sample
{
class SampleApp
{
private IOContext context;
public SampleApp()
{
this.context = new JSONFileContext(); //or any of the other implementations
}
public void doSomething()
{
//This app can now use the context, completely agnostic of the actual implementation details.
object data = context.LoadData();
//manipulate data
context.SaveData(data);
}
}
interface IOContext
{
void SaveData(object data);
object LoadData();
}
class FileContext : IOContext
{
public object LoadData()
{
object data = null;
var fileContents = loadFileContents();
//Logic to turn fileContents into a data object
return data;
}
public void SaveData(object data)
{
//logic to create filecontents from 'data'
writeFileContents(string.Empty);
}
protected void writeFileContents(string fileContents)
{
//writes the fileContents to disk
}
protected string loadFileContents()
{
string fileContents = string.Empty;
//loads the fileContents and returns it as a string
return fileContents;
}
}
class JSONFileContext : FileContext
{
public new void SaveData(object data)
{
//logic to create filecontents from 'data'
base.writeFileContents(string.Empty);
}
public new object LoadData()
{
object data = null;
var fileContents = loadFileContents();
//Logic to turn fileContents into a data object
return data;
}
}
class SQLiteContext : IOContext
{
public object LoadData()
{
object data = null;
//logic to read data into the data object
return data;
}
public void SaveData(object data)
{
//logic to save the data object in the database
}
}
}