259 votes

Ai-je vraiment une voiture dans mon garage?

Je suis un débutant en programmation Java, j'essaie de comprendre la POO.

J'ai donc construit cette classe abstraite:

public abstract class Véhicule{....}

et 2 sous-classes:

public class Voiture extends Véhicule{....}
public class Bateau extends Véhicule{....}

Voiture et Bateau ont également des champs et des méthodes uniques qui ne sont pas communs (n'ont pas le même nom, je ne peux donc pas définir de méthode abstraite pour eux dans Véhicule).

Maintenant dans mainClass, j'ai configuré mon nouveau Garage:

Véhicule[] monGarage = new Véhicule[10];
monGarage[0]=new Voiture(2,true);
monGarage[1]=new Bateau(4,600);

J'étais très content du polymorphisme jusqu'à ce que j'essaie d'accéder à l'un des champs qui sont uniques à la Voiture, comme par exemple:

boolean voitureEstAutomatique = monGarage[0].auto;

Le compilateur n'accepte pas cela. J'ai contourné ce problème en utilisant la conversion:

boolean voitureEstAutomatique = ((Voiture)monGarage[0]).auto;

Cela fonctionne... mais cela n'aide pas avec les méthodes, seulement avec les champs. Cela signifie que je ne peux pas faire

((Voiture)monGarage[0]).faireQuelqueChose();

Alors ma question est - qu'est-ce que j'ai vraiment dans mon garage? J'essaie d'avoir l'intuition ainsi que de comprendre ce qui se passe "en coulisses".


pour l'intérêt des futurs lecteurs, un bref résumé des réponses ci-dessous:

  1. Oui, il y a une Voiture dans monGarage[]
  2. Étant donné que Java est un langage à typage statique, le compilateur Java n'autorisera pas l'accès aux méthodes/champs qui ne sont pas de type "Véhicule", si on y accède à travers une structure de données basée sur la super classe Véhicule (comme Véhicule monGarage[])
  3. Quant à la façon de résoudre le problème, il y a 2 approches principales ci-dessous:
    1. Utiliser la conversion de type, ce qui rassurera les préoccupations du compilateur et laissera d'éventuelles erreurs de conception à l'exécution
    2. Le fait que j'ai besoin d'une conversion indique que la conception est défectueuse. Si j'ai besoin d'accéder à des capacités non liées à Véhicule, je ne devrais pas stocker les Voitures et Bateaux dans une structure de données basée sur Véhicule. Soit faire appartenir toutes ces capacités à Véhicule, soit utiliser des structures basées sur des types plus spécifiques (dériver)
  4. Dans de nombreux cas, la composition et/ou les interfaces seraient une meilleure alternative à l'héritage. Probablement le sujet de ma prochaine question...
  5. Et encore de nombreux autres bons aperçus ci-dessous, si on a le temps de parcourir les réponses.

147voto

Jean Logeart Points 12166

Si vous avez besoin de faire la différence entre Car et Boat dans votre garage, vous devriez les stocker dans des structures distinctes.

Par exemple:

public class Garage {
    private List cars;
    private List boats;
}

Ensuite, vous pouvez définir des méthodes spécifiques aux bateaux ou spécifiques aux voitures.

Pourquoi avoir alors la polymorphisme?

Disons que Vehicle est comme suit:

public abstract class Vehicle {
   protected int price;
   public getPrice() { return price; }
   public abstract int getPriceAfterYears(int years);
}

Chaque Vehicle a un prix donc il peut être placé dans la classe abstraite Vehicle.

Cependant, la formule déterminant le prix après n années dépend du véhicule, donc c'est laissé à la classe implémentant de le définir. Par exemple:

public Car extends Vehicle {
    // spécifique à la voiture
    private boolean automatic;
    @Override
    public getPriceAfterYears(int years) {
        // perdre 1000$ chaque année
        return Math.max(0, this.price - (years * 1000));  
    }
}

La classe Boat peut avoir une autre définition pour getPriceAfterYears et des attributs et méthodes spécifiques.

Donc maintenant dans la classe Garage, vous pouvez définir:

// spécifique à la voiture
public int numberOfAutomaticCars() {
    int s = 0;
    for(Car car : cars) {
        if(car.isAutomatic()) {
            s++;
        }
    }
    return s;
}
public List getVehicles() {
    List v = new ArrayList<>(); // init avec la somme
    v.addAll(cars);
    v.addAll(boats);
    return v;
}
// méthode pour tous les véhicules
public getAveragePriceAfterYears(int years) {
    List vehicules = getVehicles();
    int s = 0;
    for(Vehicle v : vehicules) {
        // appeler l'implémentation du type réel!
        s += v.getPriceAfterYears(years);  
    }
    return s / vehicules.size();
}

L'intérêt de la polymorphisme est de pouvoir appeler getPriceAfterYears sur un Vehicle sans se soucier de l'implémentation.

Généralement, la conversion descendante est un signe d'un design défectueux: ne stockez pas vos véhicules tous ensemble si vous avez besoin de différencier leur type réel.

Remarque: bien sûr, le design ici peut être facilement amélioré. C'est juste un exemple pour démontrer les points.

84voto

danizmax Points 1486

Pour répondre à votre question, vous pouvez découvrir ce qui se trouve exactement dans votre garage en faisant ce qui suit :

Vehicle v = myGarage[0];

if (v instanceof Car) {
   // Ce véhicule est une voiture
   ((Car)v).doSomeCarStuff();
} else if(v instanceof Boat){
   // Ce véhicule est un bateau
   ((Boat)v).doSomeBoatStuff();
}

MISE À JOUR : Comme vous pouvez le lire dans les commentaires ci-dessous, cette méthode est correcte pour des solutions simples mais ce n'est pas une bonne pratique, en particulier si vous avez un grand nombre de véhicules dans votre garage. Utilisez-la uniquement si vous savez que le garage restera petit. Si ce n'est pas le cas, recherchez "Éviter instanceof" sur stack overflow, il existe plusieurs façons de le faire.

22voto

Geziefer Points 2406

Si vous opérez sur le type de base, vous ne pouvez accéder qu'aux méthodes et champs publics de celui-ci.

Si vous souhaitez accéder au type étendu, mais disposez d'un champ du type de base où il est stocké (comme dans votre cas), vous devez d'abord le caster, puis vous pouvez y accéder :

Car car = (Car)myGarage[0];
car.doSomeCarStuff();

Ou de manière plus concise sans champ temporaire :

((Car)myGarage[0]).doSomeCarStuff();

Étant donné que vous utilisez des objets Véhicule, vous ne pouvez appeler que les méthodes de la classe de base sur eux sans les caster. Donc, pour votre garage, il peut être conseillé de distinguer les objets dans différents tableaux - ou mieux des listes - un tableau n'est souvent pas une bonne idée, car il est beaucoup moins flexible dans la manipulation qu'une classe basée sur une Collection.

13voto

HamoriZ Points 1074

Vous avez défini que votre garage stockera des véhicules, donc vous vous moquez du type de véhicules que vous avez. Les véhicules ont des caractéristiques communes comme le moteur, la roue, des comportements comme le déplacement. La représentation actuelle de ces caractéristiques pourrait être différente, mais à un niveau abstrait elles sont les mêmes. Vous avez utilisé une classe abstraite ce qui signifie que certaines attributs, comportements sont exactement les mêmes pour les deux véhicules. Si vous voulez exprimer que vos véhicules ont des caractéristiques abstraites communes alors utilisez une interface comme le déplacement pourrait signifier quelque chose de différent pour une voiture et un bateau. Les deux peuvent se rendre du point A au point B, mais de manière différente (sur roues ou sur l'eau - donc l'implémentation sera différente) Donc vous avez des véhicules dans le garage qui se comportent de la même manière et vous ne vous souciez pas de leurs caractéristiques spécifiques.

Pour répondre au commentaire:

Interface signifie un contrat qui décrit comment communiquer avec le monde extérieur. Dans le contrat vous définissez que votre véhicule peut se déplacer, peut être dirigé, mais vous ne décrivez pas comment cela fonctionnera réellement, c'est décrit dans l'implémentation. Avec une classe abstraite, vous pouvez avoir des fonctions où vous partagez une certaine implémentation, mais vous avez également des fonctions dont vous ne savez pas comment elles seront implémentées.

Un exemple d'utilisation de classe abstraite:

    abstract class Vehicle {

    protected abstract void identifierOuJeSuis();
    protected abstract void démarrerMoteur();
    protected abstract void conduireJusquaArriverChezMoi();
    protected abstract void arrêterMoteur();

    public void naviguerVersChezMoi() {
        identifierOuJeSuis();
        démarrerMoteur();
        conduireJusquaArriverChezMoi();
        arrêterMoteur();
    } 
}

Vous utiliserez les mêmes étapes pour chaque véhicule, mais l'implémentation des étapes différera en fonction du type de véhicule. Une voiture peut utiliser un GPS, un bateau peut utiliser un sonar pour identifier sa position.

13voto

Sylvain Leroux Points 9401

Je suis un débutant en programmation Java, essayant de me familiariser avec la POO.

Juste mes 2 cents - je vais essayer de faire court car beaucoup de choses intéressantes ont déjà été dites. Mais en fait, il y a deux questions ici. Une sur la "POO" et une sur comment elle est implémentée en Java.

Tout d'abord, oui, vous avez une voiture dans votre garage. Donc vos hypothèses sont correctes. Mais, Java est un langage statiquement typé. Et le système de types du compilateur ne peut "connaître" le type de vos différents objets que par leur déclaration correspondante. Pas par leur utilisation. Si vous avez un tableau de Véhicule, le compilateur ne sait que cela. Il vérifiera donc que vous ne réalisez que des opérations autorisées sur n'importe quel Véhicule. (En d'autres termes, les méthodes et les attributs visibles dans la déclaration de Véhicule).

Vous pouvez expliquer au compilateur que "vous savez en fait que ce Véhicule est une Voiture", en utilisant une conversion explicite (Voiture). Le compilateur vous croira - même si en Java il y a une vérification à l'exécution, qui peut entraîner une ClassCastException qui empêche d'autres dommages si vous avez menti (d'autres langages comme le C++ ne vérifieront pas à l'exécution - vous devez savoir ce que vous faites)

Enfin, si vous en avez vraiment besoin, vous pouvez vous fier à l'identification du type à l'exécution (c'est-à-dire: instance of) pour vérifier le "vrai" type d'un objet avant de tenter de le convertir. Mais cela est généralement considéré comme une mauvaise pratique en Java.

Comme je l'ai dit, c'est la façon dont Java implémente la POO. Il existe toute une classe famille de langages largement connue sous le nom de "langages dynamiques", qui ne vérifient qu'à l'exécution si une opération est autorisée sur un objet ou non. Avec ces langages, vous n'avez pas besoin de "déplacer" toutes les méthodes communes vers une classe de base (éventuellement abstraite) pour satisfaire le système de types. Cela s'appelle le duck typing.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X