Alors disons que j'ai cette interface:
public interface IBox
{
public void setSize(int size);
public int getSize();
public int getArea();
//...and so on
}
Et j'ai une classe qui l'implémente:
public class Rectangle implements IBox
{
private int size;
//Methods here
}
Si je voulais utiliser l'interface IBox, je ne peux pas en créer une instance, de la manière suivante:
public static void main(String args[])
{
Ibox myBox=new Ibox();
}
droite? Je devrais donc faire ceci:
public static void main(String args[])
{
Rectangle myBox=new Rectangle();
}
Si cela est vrai, le seul but des interfaces est de s'assurer que la classe qui implémente une interface a les bonnes méthodes, comme décrit par une interface? Ou existe-t-il une autre utilisation des interfaces?
Les interfaces sont un moyen de rendre votre code plus flexible. Ce que vous faites est ceci:
Ibox myBox=new Rectangle();
Puis, plus tard, si vous décidez d’utiliser un type de boîte différent (il existe peut-être une autre bibliothèque, avec un meilleur type de boîte), vous remplacez votre code par:
Ibox myBox=new OtherKindOfBox();
Une fois que vous vous y serez habitué, vous constaterez que c'est une excellente façon de travailler (même essentielle).
Une autre raison est, par exemple, si vous souhaitez créer une liste de boîtes et effectuer une opération sur chacune d’elles, mais que vous souhaitez que la liste contienne différents types de boîtes. Sur chaque case, vous pouvez faire:
myBox.close()
(en supposant que IBox utilise une méthode close ()) même si la classe réelle de myBox change en fonction de la zone dans laquelle vous vous trouvez dans l'itération.
Ce qui rend les interfaces utiles, c’est et non le fait que "vous pouvez changer d’avis et utiliser une mise en œuvre différente plus tard et ne devoir changer que le seul endroit où l’objet est créé". C'est un non-problème.
Le vrai point est déjà dans le nom: ils définissent une interface que tout le monde peut implémenter pour utiliser tout le code qui fonctionne sur cette interface. Le meilleur exemple est Java.util.Collections
, Qui fournit toutes sortes de méthodes utiles qui fonctionnent exclusivement sur les interfaces, telles que sort()
ou reverse()
pour List
. Le point ici est que ce code peut maintenant être utilisé pour trier ou inverser toute classe qui implémente les interfaces List
- pas seulement ArrayList
et LinkedList
, mais aussi des classes que vous écrivez vous-même, qui peuvent être implémentées de manière inimaginable pour ceux qui ont écrit Java.util.Collections
.
De la même manière, vous pouvez écrire du code qui fonctionne sur des interfaces connues, ou des interfaces que vous définissez, et d'autres personnes peuvent utiliser votre code sans devoir vous demander de prendre en charge leurs classes.
Une autre utilisation courante des interfaces concerne les rappels. Par exemple, Java.swing.table.TableCellRenderer , qui vous permet d’influencer la manière dont une table Swing affiche les données d’une certaine colonne. Vous implémentez cette interface, passez une instance au JTable
et, à un moment donné, lors du rendu de la table, votre code sera appelé pour faire son travail.
L’une des nombreuses utilisations que j’ai lues est sa difficulté sans interfaces à héritage multiple en Java:
class Animal
{
void walk() { }
....
.... //other methods and finally
void chew() { } //concentrate on this
}
Maintenant, imaginons un cas où:
class Reptile extends Animal
{
//reptile specific code here
} //not a problem here
mais,
class Bird extends Animal
{
...... //other Bird specific code
} //now Birds cannot chew so this would a problem in the sense Bird classes can also call chew() method which is unwanted
Un meilleur design serait:
class Animal
{
void walk() { }
....
.... //other methods
}
Animal n'a pas la méthode chew () et est placé dans une interface en tant que:
interface Chewable {
void chew();
}
et demandez à la classe Reptile de mettre cela en œuvre et non aux oiseaux (car ceux-ci ne peuvent pas mâcher):
class Reptile extends Animal implements Chewable { }
et en cas d'oiseaux simplement:
class Bird extends Animal { }
Le but des interfaces est polymorphisme, a.k.a. substitution de type. Par exemple, étant donné la méthode suivante:
public void scale(IBox b, int i) {
b.setSize(b.getSize() * i);
}
Lorsque vous appelez la méthode scale
, vous pouvez fournir toute valeur d'un type implémentant l'interface IBox
. En d'autres termes, si Rectangle
et Square
implémentent tous les deux IBox
, vous pouvez fournir un Rectangle
ou un Square
partout où un IBox
est attendu.
Les interfaces permettent aux langages à typage statique de prendre en charge le polymorphisme. Un puriste orienté objet insisterait pour que la langue fournisse l'héritage, l'encapsulation, la modularité et le polymorphisme afin de constituer une langue orientée objet complète. Dans les langues à typage dynamique (ou typées à la manière de canard) (comme Smalltalk,), le polymorphisme est trivial; cependant, dans les langages statiquement typés (comme Java ou C #,) le polymorphisme est loin d'être trivial (en fait, il semble en apparence qu'il soit en contradiction avec la notion de typage fort.)
Permettez-moi de démontrer:
Dans un langage typé dynamiquement (ou typé canard) (comme Smalltalk), toutes les variables sont des références à des objets (rien de moins, rien de plus.) Donc, en Smalltalk, je peux le faire:
|anAnimal|
anAnimal := Pig new.
anAnimal makeNoise.
anAnimal := Cow new.
anAnimal makeNoise.
Ce code:
makeNoise
au cochon.Le même code Java) ressemblerait à ceci (supposant que Canard et Vache sont des sous-classes d’Animal:
Animal anAnimal = new Pig();
duck.makeNoise();
anAnimal = new Cow();
cow.makeNoise();
C'est bien beau jusqu'à ce que nous introduisions la classe Vegetable. Les légumes ont le même comportement que Animal, mais pas tous. Par exemple, Animal et Vegetable peuvent tous deux être en mesure de pousser, mais il est clair que les légumes ne font pas de bruit et que les animaux ne peuvent pas être récoltés.
En Smalltalk, nous pouvons écrire ceci:
|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.
aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.
Cela fonctionne parfaitement en Smalltalk car il est typé canard (s'il marche comme un canard et charlatan comme un canard - c'est un canard). Dans ce cas, lorsqu'un message est envoyé à un objet, une recherche est effectuée sur la liste des méthodes du destinataire, et si une méthode correspondante est trouvée, elle est appelée. Si ce n'est pas le cas, une sorte d'exception NoSuchMethodError est renvoyée - mais tout cela est fait à l'exécution.
Mais en Java, langage typé statiquement, quel type pouvons-nous assigner à notre variable? Le maïs doit hériter de Vegetable, pour pouvoir pousser, mais ne peut pas hériter d’Animal, car il ne fait pas de bruit. La vache doit hériter d'Animal pour prendre en charge le bruit, mais ne peut pas hériter de Vegetable car elle ne devrait pas mettre en œuvre la récolte. Il semble que nous ayons besoin d'héritage multiple - possibilité d'hériter de plusieurs classes. Mais cela s’avère être une fonctionnalité de langage assez difficile à cause de tous les cas Edge qui apparaissent (que se passe-t-il lorsque plusieurs super-classes parallèles implémentent la même méthode?, Etc.)
Viennent les interfaces ...
Si nous fabriquons des classes d’animaux et de légumes, avec chacune d’elles mettant en œuvre Cultivable, nous pouvons déclarer que notre vache est un animal et que notre maïs est un légume. Nous pouvons également déclarer que les animaux et les légumes sont cultivables. Cela nous permet d'écrire ceci pour tout faire pousser:
List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());
for(Growable g : list) {
g.grow();
}
Et cela nous permet de faire des bruits d'animaux:
List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
a.makeNoise();
}
L'avantage du langage de type canard est que vous obtenez vraiment un polymorphisme de Nice: tout ce qu'une classe doit faire pour fournir un comportement est de fournir la méthode. Tant que tout le monde joue à Nice et envoie uniquement des messages correspondant à des méthodes définies, tout va bien. L'inconvénient est que le type d'erreur ci-dessous n'est détecté qu'au moment de l'exécution:
|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.
Les langages à typage statique fournissent une bien meilleure "programmation par contrat", car ils détecteront les deux types d'erreur ci-dessous lors de la compilation:
// Compiler error: Corn cannot be cast to Animal.
Animal farmObject = new Corn();
farmObject makeNoise();
-
// Compiler error: Animal doesn't have the harvest message.
Animal farmObject = new Cow();
farmObject.harvest();
Alors .... pour résumer:
L'implémentation d'interface vous permet de spécifier le type de choses que les objets peuvent faire (interaction) et l'héritage de classe vous permet de spécifier comment les choses doivent être effectuées (implémentation).
Les interfaces nous offrent de nombreux avantages du "vrai" polymorphisme, sans pour autant sacrifier la vérification du type de compilateur.
Normalement, les interfaces définissent l'interface que vous devez utiliser (comme son nom l'indique ;-)). Échantillon
public void foo(List l) {
... do something
}
Maintenant, votre fonction foo
accepte ArrayList
s, LinkedList
s, ... pas un seul type.
La chose la plus importante dans Java est que vous pouvez implémenter plusieurs interfaces mais que vous ne pouvez étendre qu'une classe!!
class Test extends Foo implements Comparable, Serializable, Formattable {
...
}
class Test extends Foo, Bar, Buz {
...
}
Votre code ci-dessus pourrait également être: IBox myBox = new Rectangle();
. L’important est maintenant que myBox contienne UNIQUEMENT les méthodes/champs d’IBox et non les autres méthodes (éventuellement existantes) de Rectangle
.
Je pense que vous comprenez tout ce que font les interfaces, mais vous n’imaginez pas encore les situations dans lesquelles une interface est utile.
Si vous instanciez, utilisez et relâchez un objet dans une étendue restreinte (par exemple, dans le cadre d'un appel de méthode), une interface n'ajoute rien. Comme vous l'avez noté, la classe concrète est connue.
Les interfaces sont utiles lorsqu'un objet doit être créé à un endroit et renvoyé à un appelant qui peut ne pas se soucier des détails de la mise en œuvre. Changeons votre exemple IBox en Shape. Nous pouvons maintenant avoir des implémentations de Shape telles que Rectangle, Cercle, Triangle, etc. Les implémentations des méthodes getArea () et getSize () seront complètement différentes pour chaque classe concrète.
Vous pouvez maintenant utiliser une fabrique avec une variété de méthodes createShape (params) qui renverront une forme appropriée en fonction des paramètres transmis. Il est évident que l'usine saura quel type de forme est créé, mais l'appelant n'aura pas se soucier de savoir si c'est un cercle, un carré, etc.
Imaginez maintenant que vous devez effectuer diverses opérations sur vos formes. Vous devrez peut-être les trier par zone, les définir toutes à une nouvelle taille, puis les afficher dans une interface utilisateur. Les formes sont toutes créées par l’usine et peuvent ensuite être transmises très facilement aux classes Sorter, Sizer et Display. Si vous avez besoin d’ajouter une classe d’hexagones dans le futur, vous n’avez pas à changer autre chose que l’usine. Sans l'interface, l'ajout d'une autre forme devient un processus très compliqué.
vous pourriez faire
Ibox myBox = new Rectangle();
de cette façon, vous utilisez cet objet comme Ibox et vous ne vous inquiétez pas que ce soit vraiment Rectangle
.
POURQUOI INTERFACE ??????
Cela commence par un chien. En particulier, un carlin.
Le carlin a divers comportements:
public class Pug {
private String name;
public Pug(String n) { name = n; }
public String getName() { return name; }
public String bark() { return "Arf!"; }
public boolean hasCurlyTail() { return true; } }
Et vous avez un Labrador, qui a également un ensemble de comportements.
public class Lab {
private String name;
public Lab(String n) { name = n; }
public String getName() { return name; }
public String bark() { return "Woof!"; }
public boolean hasCurlyTail() { return false; } }
Nous pouvons faire des carlins et des laboratoires:
Pug pug = new Pug("Spot");
Lab lab = new Lab("Fido");
Et nous pouvons invoquer leurs comportements:
pug.bark() -> "Arf!"
lab.bark() -> "Woof!"
pug.hasCurlyTail() -> true
lab.hasCurlyTail() -> false
pug.getName() -> "Spot"
Disons que je gère un chenil et que je dois garder une trace de tous les chiens que je loge. I besoin de stocker mes carlins et labradors dans des tableaux séparés:
public class Kennel {
Pug[] pugs = new Pug[10];
Lab[] labs = new Lab[10];
public void addPug(Pug p) { ... }
public void addLab(Lab l) { ... }
public void printDogs() { // Display names of all the dogs } }
Mais ce n'est clairement pas optimal. Si je veux héberger quelques caniches, je dois aussi changer ma définition de chenil pour ajouter un tableau de caniches. En fait, j'ai besoin d'un tableau séparé pour chaque type de chien.
Insight: les carlins et les labradors (et caniches) sont des types de chiens et ils ont le même ensemble de comportements. C'est-à-dire que nous pouvons dire (aux fins de cet exemple) que tous les chiens peuvent aboyer, avoir un nom et peuvent ou non avoir une queue frisée. Nous pouvons utiliser une interface pour définir ce que tous les chiens peuvent faire, mais laissez le soin à certains types de chiens de mettre en œuvre ces comportements particuliers. L'interface indique "voici les choses que tous les chiens peuvent faire" mais ne dit pas comment chaque comportement est fait.
public interface Dog
{
public String bark();
public String getName();
public boolean hasCurlyTail(); }
Ensuite, je modifie légèrement les classes Pug et Lab pour implémenter les comportements Dog. Nous pouvons dire qu'un Carlin est un chien et un laboratoire est un chien.
public class Pug implements Dog {
// the rest is the same as before }
public class Lab implements Dog {
// the rest is the same as before
}
Je peux encore instancier comme d'habitude Pugs and Labs, mais maintenant, j'ai aussi une nouvelle façon de le faire:
Dog d1 = new Pug("Spot");
Dog d2 = new Lab("Fido");
Cela dit que d1 n'est pas seulement un chien, c'est spécifiquement un roquet. Et d2 est aussi un chien, plus précisément un laboratoire. Nous pouvons invoquer les comportements et ils fonctionnent comme avant:
d1.bark() -> "Arf!"
d2.bark() -> "Woof!"
d1.hasCurlyTail() -> true
d2.hasCurlyTail() -> false
d1.getName() -> "Spot"
Voici où tout le travail supplémentaire porte ses fruits. La classe de chenil devient beaucoup plus simple. Je n'ai besoin que d'un tableau et d'une méthode addDog. Les deux vont travailler avec n'importe quel objet qui est un chien; c'est-à-dire des objets qui implémentent l'interface Dog.
public class Kennel {
Dog[] dogs = new Dog[20];
public void addDog(Dog d) { ... }
public void printDogs() {
// Display names of all the dogs } }
Voici comment l'utiliser:
Kennel k = new Kennel();
Dog d1 = new Pug("Spot");
Dog d2 = new Lab("Fido");
k.addDog(d1);
k.addDog(d2);
k.printDogs();
La dernière déclaration afficherait: Spot Fido
Une interface vous permet de spécifier un ensemble de comportements que toutes les classes implémentant l'interface partageront en commun. Par conséquent, nous pouvons définir des variables et des collections (telles que des tableaux) qui n'ont pas besoin de savoir à l'avance quel type d'objet spécifique ils vont contenir, mais seulement qu'ils vont contenir des objets qui implémentent l'interface.
C'est la raison pour laquelle Modèles d'usine et d'autres modèles de création sont si populaires en Java. Vous avez raison de dire que sans eux Java ne fournit pas un mécanisme prêt à l'emploi permettant d'abstraction facile de l'instanciation. Néanmoins, vous obtenez une abstraction partout où vous don 't crée un objet dans votre méthode, qui devrait représenter la majeure partie de votre code.
En passant, j'encourage généralement les gens à ne pas suivre le mécanisme "IRealname" pour nommer les interfaces. C’est un système Windows/COM qui met un pied dans la tombe de la notation hongroise et n’est vraiment pas nécessaire (Java est déjà très typé et l’intérêt des interfaces est d’avoir des interfaces aussi différentes que possible des types de classe).
N'oubliez pas qu'à une date ultérieure, vous pourrez prendre une classe existante et la faire implémenter IBox
. Elle deviendra alors disponible pour tout votre code sensible à la boîte.
Cela devient un peu plus clair si les interfaces sont nommées --able. par exemple.
public interface Saveable {
....
public interface Printable {
....
etc. (les schémas de nommage ne fonctionnent pas toujours, par exemple, je ne suis pas sûr que Boxable
soit approprié ici)
Un excellent exemple de la manière dont les interfaces sont utilisées est dans la structure Collections. Si vous écrivez une fonction qui prend un List
, le fait que l'utilisateur passe un Vector
ou un ArrayList
ou un HashList
ou peu importe. Et vous pouvez également transmettre ce List
à toute fonction nécessitant une interface Collection
ou Iterable
.
Cela rend possible des fonctions comme Collections.sort(List list)
, quelle que soit la façon dont le List
est implémenté.
le seul but des interfaces est de s'assurer que la classe qui implémente une interface a les méthodes correctes décrites dans une interface? Ou existe-t-il une autre utilisation des interfaces?
Je mets à jour la réponse avec de nouvelles fonctionnalités d'interface introduites avec la version Java 8 .
De la page de documentation Oracle sur résumé de l'interface :
Une déclaration d'interface peut contenir
Les seules méthodes qui ont des implémentations sont les méthodes par défaut et statiques.
Utilisations de l'interface :
Serializable
peuvent avoir ou non une relation entre elles, à l'exception de cette interface.Quelques questions SE liées concernant la différence entre classe abstraite et interface et cas d'utilisation avec des exemples concrets:
Quelle est la différence entre une classe d'interface et une classe abstraite?
Comment aurais-je dû expliquer la différence entre une classe Interface et une classe abstraite?
Regardez documentation page pour comprendre les nouvelles fonctionnalités ajoutées à Java 8: méthodes par défaut et méthodes statiques.
Le but des interfaces est abstraction, ou découplage de la mise en oeuvre.
Si vous introduisez une abstraction dans votre programme, vous ne vous souciez pas des implémentations possibles. Vous êtes intéressé par ce que il peut faire et non comment , et vous utilisez un interface
pour exprimer cela en Java.
Si vous avez CardboardBox et HtmlBox (qui implémentent tous deux IBox), vous pouvez les transmettre à toute méthode qui accepte une IBox. Même si elles sont à la fois très différentes et pas complètement interchangeables, les méthodes qui ne se soucient pas de "ouvrir" ou de "redimensionner" peuvent toujours utiliser vos classes (peut-être parce qu'elles se soucient du nombre de pixels nécessaires pour afficher quelque chose sur un écran).
Interfaces où une fétature a été ajoutée à Java pour autoriser l'héritage multiple. Les développeurs de Java ont/ont réalisé/cependant que le fait d'avoir plusieurs héritages était une fonctionnalité "dangereuse", c'est-à-dire pourquoi est venu l'idée d'une interface.
l'héritage multiple est dangereux car vous pourriez avoir une classe comme celle-ci:
class Box{
public int getSize(){
return 0;
}
public int getArea(){
return 1;
}
}
class Triangle{
public int getSize(){
return 1;
}
public int getArea(){
return 0;
}
}
class FunckyFigure extends Box, Triable{
// we do not implement the methods we will used the inherited ones
}
Quelle serait la méthode à appeler quand on utilise
FunckyFigure.GetArea();
Tous les problèmes sont résolus avec les interfaces, car vous savez que vous pouvez étendre les interfaces et qu’elles n’auront pas de méthodes de classification ... bien sûr, le compilateur est agréable et vous indique si vous n’avez pas implémenté de méthodes, mais j’aime penser que c’est un effet secondaire d'une idée plus intéressante.
Voici ma compréhension de l'avantage de l'interface. Corrigez-moi si je me trompe. Imaginez que nous développons un système d'exploitation et qu'une autre équipe développe les pilotes de certains périphériques. Nous avons donc développé une interface StorageDevice. Nous en avons deux implémentations (FDD et HDD) fournies par une autre équipe de développeurs.
Ensuite, nous avons une classe OperatingSystem qui peut appeler des méthodes d'interface telles que saveData simplement en transmettant une instance de classe implémentée par l'interface StorageDevice.
L'avantage ici est que nous ne nous soucions pas de la mise en œuvre de l'interface. L'autre équipe fera le travail en implémentant l'interface StorageDevice.
package mypack;
interface StorageDevice {
void saveData (String data);
}
class FDD implements StorageDevice {
public void saveData (String data) {
System.out.println("Save to floppy drive! Data: "+data);
}
}
class HDD implements StorageDevice {
public void saveData (String data) {
System.out.println("Save to hard disk drive! Data: "+data);
}
}
class OperatingSystem {
public String name;
StorageDevice[] devices;
public OperatingSystem(String name, StorageDevice[] devices) {
this.name = name;
this.devices = devices.clone();
System.out.println("Running OS " + this.name);
System.out.println("List with storage devices available:");
for (StorageDevice s: devices) {
System.out.println(s);
}
}
public void saveSomeDataToStorageDevice (StorageDevice storage, String data) {
storage.saveData(data);
}
}
public class Main {
public static void main(String[] args) {
StorageDevice fdd0 = new FDD();
StorageDevice hdd0 = new HDD();
StorageDevice[] devs = {fdd0, hdd0};
OperatingSystem os = new OperatingSystem("Linux", devs);
os.saveSomeDataToStorageDevice(fdd0, "blah, blah, blah...");
}
}