web-dev-qa-db-fra.com

Y at-il plus à une interface que d'avoir les bonnes méthodes

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?

159
Click Upvote

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.

143
morgancodes

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.

120
Michael Borgwardt

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 { }
118
peevesy

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.

46
Apocalisp

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:

  1. Déclare une variable locale appelée anAnimal (notez que nous ne spécifions pas le type de la variable - toutes les variables sont des références à un objet, ni plus ni moins.)
  2. Crée une nouvelle instance de la classe nommée "Pig"
  3. Assigne cette nouvelle instance de Pig à la variable anAnimal.
  4. Envoie le message makeNoise au cochon.
  5. Répète le tout en utilisant une vache, mais en l'attribuant à la même variable exacte que le 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:

  1. 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).

  2. Les interfaces nous offrent de nombreux avantages du "vrai" polymorphisme, sans pour autant sacrifier la vérification du type de compilateur.

33
Jared

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 ArrayLists, LinkedLists, ... 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.

9
Johannes Weiss

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é.

6
Ickster

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.

6
IAdapter

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.

6

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).

3
Christopher Smith

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)

3
Brian Agnew

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é.

3
Kip

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

  1. signatures de méthode
  2. méthodes par défaut
  3. méthodes statiques
  4. définitions constantes.

Les seules méthodes qui ont des implémentations sont les méthodes par défaut et statiques.

Utilisations de l'interface :

  1. Pour définir un contrat
  2. Pour relier des classes non liées à des capacités (par exemple, les classes implémentant l'interface Serializable peuvent avoir ou non une relation entre elles, à l'exception de cette interface.
  3. Pour fournir une implémentation interchangeable par exemple, un modèle de stratégie
  4. Les méthodes par défaut vous permettent d'ajouter de nouvelles fonctionnalités aux interfaces de vos bibliothèques et d'assurer la compatibilité binaire avec le code écrit pour les anciennes versions de ces interfaces.
  5. Organisez des méthodes d'assistance dans vos bibliothèques avec des méthodes statiques (vous pouvez conserver les méthodes statiques spécifiques à une interface dans la même interface plutôt que dans une classe séparée)

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.

3
Ravindra babu

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.

2
eljenso

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).

1
Todd R

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.

1
mandel

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...");    
    }
}
0
Vladimir Georgiev