web-dev-qa-db-fra.com

Pourquoi utiliser une interface lorsque la classe peut implémenter directement les fonctions?

Duplicata possible:
Pourquoi les interfaces sont-elles utiles?

Comme la plupart des professeurs, ma Java faculté a présenté une interface sans expliquer ni même mentionner son utilisation pratique. Maintenant, j'imagine que les interfaces ont une utilisation très spécifique, mais ne semblent pas trouver la réponse.

Ma question est: une classe peut implémenter directement les fonctions dans une interface. par exemple:

interface IPerson{
    void jump(int); 
}

class Person{
int name;
    void jump(int height){
        //Do something here
    }
}

Quelle différence spécifique

class Person implements IPerson{
    int name;
    void jump(int height){
        //Do something here
    }
}

? faire

44
SoWhat

Rappelez-vous, Java est capricieux sur le type.

Étendons un peu votre exemple et renommons la classe en Jumpable.

interface Jumpable {
    void jump(int);
}

class Person extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
    }
}

class Dog extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //also make him bark when he jumps
    }
}

class Cat extends Mammal implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it stretch its legs as well
    }
}

class FlyingFish extends Fish implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Mantis extends Insect implements Jumpable {
    //other stuff
    void jump(int howHigh) {
        //jump method
        //make it come out of water
    }
}

class Ant extends Insect { //Cannot jump
    //other stuff
}

class Whale extends Mammal { //Cannot jump (hopefully)
    //other stuff
}

Notez que nous avons ici une variété de classes, organisées dans une certaine hiérarchie. Tous ne peuvent pas sauter, et la capacité de saut n'est pas universellement applicable à aucune catégorie - chaque classe parent (Animal, Mammal, Insect, Fish).

Disons que nous voulons organiser une compétition de saut d'obstacles. Sans interfaces, il faudrait faire quelque chose comme ça:

void competition(
    Person[] pCompetitors,
    Dog[] dCompetitors,
    Cat[] cCompetitors,
    FlyingFish[] fCompetitors,
    Mantis[] mCompetitors
) {
    for(int i=0; i<pCompetitors.length; i++) {
        pCompetitors[i].jump((int)Math.Rand() * 10);
    }
    //Do the same for ALL the other arrays.
}

Ici, puisqu'il n'y a pas de "classe enveloppante" contenant toutes les classes pouvant sauter, nous devons les invoquer individuellement. Rappelez-vous, nous ne pouvons pas simplement avoir un tableau Animal[] Ou Mammal[], Car le compilateur ne nous permettra pas d'appeler jump(); et pas tous les Animals/Mammals peuvent jump().

De plus, cela devient impossible à étendre. Disons que vous voulez ajouter une autre classe de saut.

class Bob extends Animal { //Bob is NOT a human. Bob is something....else....
    void jump(int howHigh) {
        //...
    }
}

Maintenant, vous devez modifier competition(.........) pour accepter également un paramètre Bob[]. Vous devez également modifier toutes les instances de competition[] Pour créer leurs propres Bob[] Ou passer des paramètres Bob[] Vides. Et ça devient capricieux.


Si vous avez utilisé des interfaces, votre méthode de compétition devient ainsi:

void competition(Jumpable[] j) {
    for(int i=0; i<j.length; i++) {
        j[i].jump((int)Math.Rand() * 10);
    }
}

Cela peut également être étendu sans tracas.

Fondamentalement, les interfaces permettent au compilateur de connaître le comportement attendu de l'objet - quelles méthodes/données le compilateur peut supposer exister. De cette façon, nous pouvons écrire des programmes généraux.

Vous pouvez également hériter de plusieurs interfaces, vous ne pouvez pas le faire avec extends en Java.

Quelques exemples concrets: Le premier qui me vient à l'esprit concerne les gestionnaires d'événements d'AWT. Ceux-ci nous permettent de passer une méthode en paramètre et d'en faire un gestionnaire d'événements. Cela ne fonctionnera que si le compilateur est certain que la méthode existe, et donc nous utilisons des interfaces.

17
Manishearth

Cela commence avec un chien. En particulier, un carlin.

A pug

Le carlin a différents 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;
    }
}

Labrador Retriever

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"
lab.getName()        -> "Fido"

Two cute dogs in a cage

Disons que je gère un chenil et que je dois garder une trace de tous les chiens que je loge. J'ai besoin de stocker mes carlins et labradors dans des baies distinctes:

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 aussi héberger des caniches, je dois changer ma définition de Kennel pour ajouter un tableau de Poodles. En fait, j'ai besoin d'un tableau séparé pour chaque type de chien.

Aperçu: les carlins et les labradors (et les caniches) sont des types de chiens et ils ont le même ensemble de comportements. Autrement dit, nous pouvons dire (aux fins de cet exemple) que tous les chiens peuvent aboyer, avoir un nom et avoir ou non une queue bouclée. Nous pouvons utiliser une interface pour définir ce que tous les chiens peuvent faire, mais laissons aux types spécifiques de chiens le soin de mettre en œuvre ces comportements particuliers. L'interface dit "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. On peut dire qu'un Pug est un Dog et un Lab est a Dog.

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 toujours instancier Pugs et Labs comme je l'ai fait précédemment, mais maintenant j'ai aussi une nouvelle façon de le faire:

Dog d1 = new Pug("Spot");
Dog d2 = new Lab("Fido");

Cela signifie que d1 n'est pas seulement un Dog, c'est spécifiquement un Pug. Et d2 est également un Dog, en particulier un Lab.

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"
d2.getName()        -> "Fido"

Voici où tout le travail supplémentaire porte ses fruits. La classe Kennel devient beaucoup plus simple. Je n'ai besoin que d'un tableau et d'une méthode addDog. Les deux fonctionneront avec tout objet qui est un chien; c'est-à-dire les 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 instruction afficherait:

 Spot
 Fido

Une interface vous donne la possibilité de spécifier un ensemble de comportements que toutes les classes qui implémentent 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 elles contiendront, mais seulement qu'elles contiendront des objets qui implémentent l'interface.

98
Barry Brown

Avoir une interface IPerson vous permet d'avoir plusieurs implémenteurs (Man, Woman, Employee etc ...), mais toujours les traiter tous via l'interface dans d'autres classes.

Donc, dans une autre classe, vous déclarez simplement:

void myMethod(IPerson person, Integer howHigh)
{
   person.jump(howHigh);
}

Vous n'avez pas besoin d'avoir une méthode distincte pour chaque implémenteur.

29
Oded

Le but d'une interface n'est pas de déplacer ou de réutiliser du code mais plus pour garantir que certaines méthodes qui opèrent sur un large éventail de types peuvent utiliser ces types (qui leur seront transmis comme arguments) de la même manière même si elles peuvent ne pas avoir de classes parentales communes.

L'interface est utilisée pour faire le contrat - la fonctionnalité attendue pour exister dans une certaine classe.

Par exemple:

interface Transmittable {
     public byte[] toBytes();
}

class Person implements Transmittable {
     public byte[] toBytes() {
         return this.name.getBytes()
    }
}

class Animal implements Transmittable {
     public byte[] toBytes() {
            return this.typeOfAnimal.getBytes()
    }
}

class NetworkTransmitter {
     public void transmit(Transmittable object) {
          byte data[] = object.toBytes();
         //do something....
     } 
}

class TestExample {
    public static void main(String args[]) {
           NetworkTransmitter trobj = new NetworkTransmitter();
           trobj.transmit(new Person());
           trobj.transmit(new Animal());
   }
}

Notez que cela ne ressemble pas à l'héritage où l'objet d'une classe hérite (ou remplace) une méthode du même nom du parent. Les classes qui implémentent une interface n'ont pas besoin d'être descendantes de la même classe parente, mais d'autres classes qui veulent s'assurer qu'un contrat existe, peuvent appeler les mêmes méthodes sur les objets de toutes les classes qui implémentent l'interface. L'interface s'assure que ce contrat est disponible.

14
mwallace

Je pense que tous les développeurs peuvent comprendre votre confusion - essayer de se familiariser avec l'utilisation des interfaces n'est pas toujours bien expliqué. J'ai commencé à vraiment comprendre l'utilisation des interfaces lorsque j'ai commencé à travailler en tant que développeur sur des projets du monde réel.

Il y a un excellent article sur BlackWasp, qui explique l'utilisation d'une interface avec un exemple brillant - lisez-le et essayez-le, cela m'a vraiment aidé à mieux le comprendre:

http://www.blackwasp.co.uk/Interfaces.aspx

3
Dal

L'utilisation d'une interface vous permet de convertir un certain nombre de classes différentes en un type commun, quelle que soit la façon dont les classes individuelles peuvent être implémentées.

Voici un exemple rapide et sale:

class Puppet : IMovement { ... }
class Vehicle : IMovement { ... }
class Car : Vehicle { ... }
class Bicycle : Vehicle { ... }

class Person : IMovement { ... }
class Child : Person { ... }
class Adult : Person { ... }

Selon l'exemple, chacune des classes implémente l'interface IMovement, directement ou indirectement. Cependant, la chose importante à noter est que n'importe quelle classe de l'exemple peut être convertie en le même type d'interface commun:

((IMovement)puppet).Move()
((IMovement)car).Move()
((IMovement)child).Move()

Les interfaces vous permettent d'introduire facilement de nouveaux comportements dans vos classes sans altérer leurs interfaces existantes. Ainsi, une interface permet un polymorphisme indépendant de l'héritage. Ceci est très utile lorsque vous essayez de gérer un certain nombre de types d'objets complètement différents de la même manière.

1
S.Robins

Il existe des cas d'utilisation très intéressants d'avoir une interface. En voici un:

Supposons que je crée un système de surveillance du système d'exploitation et que vous me disiez quoi faire après l'occurrence d'un certain événement. Dites quand l'espace disque est> 90% plein ou que l'utilisation du processeur est très élevée ou lorsqu'un utilisateur s'est connecté, etc.

Dans mon code (c'est le système de surveillance du système d'exploitation), je m'attends à ce que vous me fournissiez un objet avec certaines méthodes implémentées. Dites une méthode comme void OnDiskUsageHigh() etc. Dans mon code, j'appellerais simplement votre méthode lorsque l'espace disque diminue.

Il s'agit d'un mécanisme de rappel, et sans l'interface et l'ensemble défini de politiques entre vous et moi, mon code ne pourra pas desservir un ensemble général de clients.

Voici l'interface que vous devez implémenter (c'est-à-dire créer une classe concrète):

interface Callback { 
  void OnDiskUsageHigh();
  void OnCpuUsageHigh();
  ...
} 

Et vous initialisez ma classe OSMonitoring avec un objet dont la classe implémente Callback comme dans

new OSMonitoringTool(new ConcreteCallbackClass());

Vous devriez en savoir plus sur la programmation basée sur les politiques/contrats.

1
Fanatic23

Je pense que votre confusion se résume aux classes et interfaces mal nommées dans l'exemple.

Personnellement, je pense que ce serait moins déroutant si vous nommiez mieux l'interface comme IJumper. Il s'agit de l'interface qui permet aux choses de sauter (en tant que groupe distinct qui peut être plus grand que le groupe Personne).

Si vous vouliez rester avec la même analogie. Je renommerais Person en EarthPerson. Ensuite, vous pouvez avoir des classes alternatives qui implémentent l'interface IPerson telles que MarsPerson, JupitorPerson etc.

Toutes ces personnes différentes pourront sauter, mais la façon dont elles sauteront dépendra de leur évolution. Pourtant, votre code fonctionnera pour les personnes d'autres planètes sans aucune modification, car vous avez fourni une interface générique qui fonctionne avec IPerson plutôt qu'avec une personne d'une planète explicite.

Cela facilite également le test de votre code.

Si par exemple l'objet Person est très cher à créer (il recherche tous les détails d'une personne dans la base de données Earth Wide). Lorsque vous testez, vous ne voulez pas utiliser un véritable objet Personne comme données (prenez un certain temps pour créer) et pouvez changer au fil du temps et casser votre test. Mais vous pouvez créer un objet TestPerson (qui implémente l'interface IPerson) (une maquette d'une personne) et le transmettre à toutes les interfaces qui acceptent une personne et vous assurer qu'elles font la bonne chose.

1
Martin York

En plus des commentaires des autres, les interfaces facilitent les tests car vous avez clairement défini quelles devraient être les parties critiques de l'interface pour toutes les classes de maquette/stub. TDD devrait faire des interfaces partout.

0
anon

Il n'est pas nécessaire de le faire, sauf si vous voulez pouvoir avoir plus d'une classe fournissant ce comportement.

Un bon exemple est l'interface JDBC Connection qui représente une connexion à une base de données SQL, où vous, en tant que programmeur, pouvez envoyer des commandes SQL et récupérer le résultat. Vous ne vous souciez pas de la façon dont le pilote sous-jacent communique avec la base de données, mais vous vous souciez que l'implémentation implémente l'interface de connexion, vous pouvez donc simplement utiliser n'importe quelle connexion que le DriverManager choisit de vous fournir.

0
user1249