Supposons que j'ai la situation suivante:
public abstract class Vehicle {
public void turnOn() { ... }
}
public interface Flier {
public void fly();
}
Existe-t-il un moyen de garantir que toute classe qui implémente Flier
doit également étendre Vehicle
? Je ne veux pas faire de Flier
une classe abstraite parce que je veux pouvoir mélanger quelques autres interfaces de la même manière.
Par exemple:
// I also want to guarantee any class that implements Car must also implement Vehicle
public interface Car {
public void honk();
}
// I want the compiler to either give me an error saying
// MySpecialMachine must extend Vehicle, or implicitly make
// it a subclass of Vehicle. Either way, I want it to be
// impossible to implement Car or Flier without also being
// a subclass of Vehicle.
public class MySpecialMachine implements Car, Flier {
public void honk() { ... }
public void fly() { ... }
}
Les interfaces Java ne peuvent pas étendre les classes, ce qui est logique car les classes contiennent des détails d'implémentation qui ne peuvent pas être spécifiés dans une interface.
La manière appropriée de résoudre ce problème consiste à séparer complètement l'interface de l'implémentation en transformant également Vehicle
en interface. Le Car
e.t.c. peut étendre l'interface Vehicle
pour forcer le programmeur à implémenter les méthodes correspondantes. Si vous souhaitez partager du code entre toutes les instances Vehicle
, vous pouvez utiliser une classe (éventuellement abstraite) comme parent pour toutes les classes qui ont besoin d'implémenter cette interface.
Vous pouvez réorganiser vos classes et interfaces comme ceci:
public interface IVehicle {
public void turnOn();
}
public abstract class Vehicle implements IVehicle {
public void turnOn() { ... }
}
public interface Flier extends IVehicle {
public void fly();
}
De cette façon, toutes les implémentations de Flier
sont garanties d'implémenter le protocole d'un véhicule, à savoir IVehicle
.
C'est une exigence étrange, mais vous pouvez accomplir quelque chose du genre avec Generics:
<T extends MyInterface & MyAbstractClass>
Cette question montre que vous n'avez pas saisi l'essence de interface
et class
. Oublier la syntaxe concrète Java en ce moment, tout ce que vous devez d'abord comprendre, c'est que: l'interface est un ensemble de protocoles, qui devrait être indépendant de l'implémentation. Cela n'a aucun sens de laisser une interface étendre un (qui est orientée vers l'implémentation).
Revenons à votre question concrète, si vous voulez garantir qu'un Flier
est toujours une sorte de Vehicle
, changez simplement ce dernier en interface
et laissez ancien l'étendre (Il est logique d'étendre un protocole à l'autre). Après cela, vous pouvez créer n'importe quelle classe (abstraite ou concrète) qui implémente Vehicle
ou Flier
.
Si vous avez le contrôle sur les classes Vehicle
, extrayez simplement Vehicle
en tant qu'interface, puis fournissez une implémentation de base.
Si vous n'avez aucun contrôle sur la classe Vehicle
, par exemple parce qu'elle fait partie d'un framework que vous utilisez ou d'une bibliothèque tierce, ce n'est pas possible de le faire en Java.
La chose la plus proche que vous pouvez faire est d'utiliser la notation générique à caractères génériques multiples.
<T extends Vehicle & Car>
Mais vous ne pouvez pas vraiment l'appliquer directement à Car à moins que vous ne fassiez quelque chose comme ceci:
public interface Car<T extends Vehicle & Car>() {
T self();
}
Ce qui est bizarre et n'impose pas la méthode self pour retourner réellement soi, c'est juste un indice/suggestion fort.
Vous implémenteriez un Car
comme ceci:
public class CitroenC3 extends Vehicle implements Car<CitroenC3> {
@Override
public CitroenC3 self() {
return this;
}
}
on peut utiliser un Car<?>
comme ceci:
Car<?> car = obtainCarInSomeWay();
Vehicle v = car.self();
Car c = car.self();
ils doivent être tous les deux une syntaxe valide.
Ce que le compilateur applique ici, c'est que ce que vous spécifiez dans Car<WHICH>
Comme QUI doit à la fois étendre Vehicle
et implémenter Car
. Et en ajoutant self()
vous dites au programmeur que l'objet T
est censé être l'objet lui-même, forçant ainsi l'instance générique à correspondre à la classe s'il veut se conformer à la spécification.
dans Java 8 vous pouvez même définir une implémentation par défaut pour la méthode self.
Je souhaite également qu'il y ait une meilleure façon de gérer quelque chose comme ça.
Now: Chaque fois que vous souhaitez implémenter à partir de "Flier", vous devez étendre depuis Vehicle ! (car seul le véhicule peut implémenter implementMe).
C'est délicat mais fonctionne très bien.