J'ai beaucoup lu sur les interfaces et l'héritage de classe en Java, et je sais comment faire les deux et je pense avoir une bonne idée des deux. Mais il semble que personne ne compare vraiment les deux côte à côte et explique quand et pourquoi vous souhaitez utiliser l'un ou l'autre. Je n'ai pas trouvé beaucoup de fois où l'implémentation d'une interface serait un meilleur système que l'extension d'une superclasse.
Alors, quand implémentez-vous une interface et quand étendez-vous une superclasse?
Utilisez une interface si vous souhaitez définir un contrat . C'est à dire. X doit prendre Y et retourner Z. Peu importe comment le code fait ça. Une classe peut implémenter des interfaces multiple.
Utilisez une classe abstraite si vous souhaitez définir le comportement par défaut en non méthodes abstraites afin que les utilisateurs finaux puissent le réutiliser sans le réécrire encore et encore. Une classe peut s'étendre de une seule autre classe. Une classe abstraite avec seulement les méthodes abstraites peuvent être aussi bien définies qu'une interface. Une classe abstraite sans aucune méthode abstraite est reconnaissable comme le modèle Méthode de modèle (voir cette réponse pour quelques exemples du monde réel).
Une classe abstraite à son tour peut parfaitement implémenter une interface chaque fois que vous voulez donner à l'utilisateur final la liberté de définir le comportement par défaut.
Vous devez choisir une interface si tout ce que vous voulez est de définir un contrat c'est-à-dire des signatures de méthode que vous souhaitez que les classes héritées implémentent. Une interface ne peut avoir aucune implémentation. Les classes héritées sont libres de choisir leur propre implémentation.
Parfois, vous voulez définir implémentation partielle dans un type de base et laisser le reste aux classes héritées. Si tel est le cas, choisissez une classe abstraite. Une classe abstraite peut définir des implémentations de méthodes et des variables tout en laissant certaines méthodes abstraites. Les classes d'extension peuvent choisir comment implémenter les méthodes abstraites tout en ayant l'implémentation partielle fournie par la superclasse.
Un extrême des classes abstraites est une pure classe abstraite - une classe qui n'a que des méthodes abstraites et rien d'autre. S'il s'agit de pure classe abstraite vs une interface, aller avec l'interface. Java n'autorise que l'héritage d'implémentation unique alors qu'il autorise l'héritage d'interfaces multiples, ce qui signifie qu'une classe peut implémenter plusieurs interfaces mais ne peut étendre qu'une seule classe. Ainsi, le choix d'une classe abstraite pure sur l'interface signifie que la sous-classe ne sera pas autorisé à étendre une autre classe lors de l'implémentation des méthodes abstraites.
Utilisez une interface pour définir le comportement. Classes (et sous-classes) utilisateur (abstraites) à fournir implémentation. Ils ne s'excluent pas mutuellement; ils peuvent tous travailler ensemble.
Par exemple, supposons que vous définissez un objet d'accès aux données. Vous voulez que votre DAO puisse charger des données. Mettez donc une méthode de chargement sur l'interface. Cela signifie que tout ce qui veut s'appeler DAO doit implémenter load. Supposons maintenant que vous devez charger A et B. Vous pouvez créer une classe abstraite générique paramétrée (génériques) pour fournir un aperçu du fonctionnement de la charge. Vous sous-classe ensuite cette classe abstraite pour fournir les implémentations concrètes pour A et B.
La raison principale de l'utilisation de classes abstraites et d'interfaces est différente.
Une classe abstraite doit être utilisée lorsque vous avez des classes qui ont des implémentations identiques pour un tas de méthodes, mais varient en quelques-unes.
Cela peut être un mauvais exemple, mais l'utilisation la plus évidente des classes abstraites dans le cadre Java est dans les classes Java.io. OutputStream
n'est qu'un flux d'octets. Où ce flux va dépendre entièrement de la sous-classe de OutputStream
que vous utilisez ... FileOutputStream
, PipedOutputStream
, le flux de sortie créé à partir d'un Java.net.Socket
' s getOutputStream
méthode ...
Remarque: Java.io utilise également le modèle Decorator pour encapsuler les flux dans d'autres flux/lecteurs/écrivains.
Une interface doit être utilisée lorsque vous voulez simplement garantir qu'une classe implémente un ensemble de méthodes, mais peu vous importe comment.
L'utilisation d'interfaces la plus évidente se situe dans le cadre Collections.
Je me fiche de savoir comment un List
ajoute/supprime des éléments, tant que je peux appeler add(something)
et get(0)
pour mettre et obtenir des éléments. Il peut utiliser un tableau (ArrayList
, CopyOnWriteArrayList
), une liste chaînée (LinkedList
), etc ...
L'autre avantage de l'utilisation des interfaces est qu'une classe peut en implémenter plusieurs. LinkedList
est une implémentation des deux List
etDeque
.
Personne?
http://mindprod.com/jgloss/interfacevsabstract.html
EDIT: je devrais fournir plus qu'un lien
Voici une situation. Pour construire sur l'exemple de voiture ci-dessous, considérez ceci
interface Drivable {
void drive(float miles);
}
abstract class Car implements Drivable {
float gallonsOfGas;
float odometer;
final float mpg;
protected Car(float mpg) { gallonsOfGas = 0; odometer = 0; this.mpg = mpg; }
public void addGas(float gallons) { gallonsOfGas += gallons; }
public void drive(float miles) {
if(miles/mpg > gallonsOfGas) throw new NotEnoughGasException();
gallonsOfGas -= miles/mpg;
odometer += miles;
}
}
class LeakyCar extends Car { // still implements Drivable because of Car
public addGas(float gallons) { super.addGas(gallons * .8); } // leaky tank
}
class ElectricCar extends Car {
float electricMiles;
public void drive(float miles) { // can we drive the whole way electric?
if(electricMiles > miles) {
electricMiles -= miles;
odometer += miles;
return; // early return here
}
if(electricMiles > 0) { // exhaust electric miles first
if((miles-electricMiles)/mpg > gallonsOfGas)
throw new NotEnoughGasException();
miles -= electricMiles;
odometer += electricMiles;
electricMiles = 0;
}
// finish driving
super.drive(miles);
}
}
Je pense que les interfaces fonctionnent mieux lorsque vous les utilisez pour exprimer que l'objet a une certaine propriété ou un certain comportement, qui s'étend sur plusieurs arbres d'héritage et n'est clairement défini que pour chaque classe.
Par exemple, pensez à comparable. Si vous vouliez créer une classe Comparable à étendre à d'autres classes, elle devrait être très élevée dans l'arbre d'héritage, possible juste après Object, et la propriété qu'elle exprime est que deux objets de ce type peuvent être comparés, mais il y a aucun moyen de définir cela en général (vous ne pouvez pas avoir une implémentation de compareTo directement dans la classe Comparable, c'est différent pour chaque classe qui l'implémente).
Les classes fonctionnent mieux quand elles définissent quelque chose de clair, vous savez quelles propriétés et quels comportements elles ont, et ont des implémentations réelles pour les méthodes, que vous voulez transmettre aux enfants.
Les classes fonctionnent donc lorsque vous devez définir un objet concret comme un humain ou une voiture, et les interfaces fonctionnent mieux lorsque vous avez besoin d'un comportement plus abstrait qui est trop général pour appartenir à un arbre d'héritage, comme la capacité à être comparé (comparable) ou à être exécuté (Runnable).
Une méthode pour choisir entre un interface
et un base class
est la considération de la propriété du code. Si vous contrôlez tout le code, une classe de base est une option viable. Si, d'un autre côté, de nombreuses sociétés différentes souhaitent produire des composants remplaçables, c'est-à-dire définir un contrat alors une interface est votre seul choix.
Vous pouvez penser à étendre à partir d'une super classe si la classe dérivée est du même type. Je veux dire que lorsqu'une classe étend une classe abstraite, elles doivent toutes les deux être du même type, la seule différence étant que la super classe a un plus comportement général et la sous-classe a un comportement plus spécifique. Une interface est un concept totalement différent. Lorsqu'une classe implémente une interface, c'est soit pour exposer une API (contrat), soit pour obtenir un certain comportement. Pour donner un exemple, je dirais que Car est une classe abstraite. Vous pouvez étendre de nombreuses classes comme Ford, Chevy, etc., qui sont chacune de type voiture. Mais alors, si vous avez besoin d'un certain comportement spécifique, comme par exemple que vous avez besoin d'un GPS dans une voiture, la classe concrète, par exemple Ford, devrait implémenter l'interface GPS.
J'ai trouvé quelques articles, en particulier certains qui décrivent pourquoi vous ne devriez pas utiliser l'héritage d'implémentation (c'est-à-dire les superclasses):
Je suppose que je vais donner l'exemple de la voiture classique.
Lorsque vous avez une interface de voiture, vous pouvez créer une Ford, une Chevy et une Oldsmobile. En d'autres termes, vous créez différents types de voitures à partir d'une interface de voiture.
Lorsque vous avez une classe de voiture, vous pouvez ensuite étendre la classe de voiture pour faire un camion ou un bus. En d'autres termes, vous ajoutez de nouveaux attributs aux sous-classes tout en conservant les attributs de la base ou de la super classe.
Si vous souhaitez uniquement hériter des signatures de méthode (nom, arguments, type de retour) dans les sous-classes, utilisez une interface, mais si vous souhaitez également hériter du code d'implémentation, utilisez une superclasse.