Je suis un novice en programmation Java et en essayant de maîtriser le POO.
J'ai donc construit cette classe abstraite:
public abstract class Vehicle{....}
et 2 sous-classes:
public class Car extends Vehicle{....}
public class Boat extends Vehicle{....}
Car
et Boat
contiennent également des champs et des méthodes uniques qui ne sont pas communs (ils n'ont pas le même nom, je ne peux donc pas définir de méthode abstraite pour eux dans Vehicle).
Maintenant, dans mainClass, j'ai configuré mon nouveau garage:
Vehicle[] myGarage= new Vehicle[10];
myGarage[0]=new Car(2,true);
myGarage[1]=new Boat(4,600);
J'étais très contente du polymorphisme jusqu'à ce que j'essaie d'accéder à l'un des champs propres à Car, tels que:
boolean carIsAutomatic = myGarage[0].auto;
Le compilateur n'accepte pas cela. J'ai travaillé sur ce problème en utilisant le casting:
boolean carIsAutomatic = ((Car)myGarage[0]).auto;
Cela fonctionne ... mais cela n'aide pas avec les méthodes, juste les champs. Ce qui signifie que je ne peux pas faire
(Car)myGarage[0].doSomeCarStuff();
Ma question est donc la suivante: qu'est-ce que j'ai vraiment dans mon garage? J'essaie d'obtenir l'intuition et de comprendre ce qui se passe "dans les coulisses".
pour les futurs lecteurs, un bref résumé des réponses ci-dessous:
Car
dans myGarage[]
Vehicle myGarage[]
)Si vous devez faire la différence entre Car
et Boat
dans votre garage, stockez-les dans des structures distinctes.
Par exemple:
public class Garage {
private List<Car> cars;
private List<Boat> boats;
}
Vous pouvez ensuite définir des méthodes spécifiques aux bateaux ou spécifiques aux voitures.
Disons que Vehicle
est comme:
public abstract class Vehicle {
protected int price;
public getPrice() { return price; }
public abstract int getPriceAfterYears(int years);
}
Chaque Vehicle
a un prix et peut donc être inséré dans la classe abstraite Vehicle
.
Cependant, la formule de détermination du prix après n années dépend du véhicule, il est donc laissé à la classe des exécutants de le définir. Par exemple:
public Car extends Vehicle {
// car specific
private boolean automatic;
@Override
public getPriceAfterYears(int years) {
// losing 1000$ every year
return Math.max(0, this.price - (years * 1000));
}
}
La classe Boat
peut avoir une autre définition de getPriceAfterYears
et des attributs et méthodes spécifiques.
Alors maintenant, dans la classe Garage
, vous pouvez définir:
// car specific
public int numberOfAutomaticCars() {
int s = 0;
for(Car car : cars) {
if(car.isAutomatic()) {
s++;
}
}
return s;
}
public List<Vehicle> getVehicles() {
List<Vehicle> v = new ArrayList<>(); // init with sum
v.addAll(cars);
v.addAll(boats);
return v;
}
// all vehicles method
public getAveragePriceAfterYears(int years) {
List<Vehicle> vehicules = getVehicles();
int s = 0;
for(Vehicle v : vehicules) {
// call the implementation of the actual type!
s += v.getPriceAfterYears(years);
}
return s / vehicules.size();
}
L'intérêt du polymorphisme est de pouvoir appeler getPriceAfterYears
sur un Vehicle
sans se souciant de l'implémentation.
Habituellement, le downcasting est le signe d’une conception défectueuse: ne rangez pas vos véhicules tous ensemble si vous devez différencier leur type réel.
Remarque: bien entendu, le design peut être facilement amélioré. C'est juste un exemple pour démontrer les points.
Pour répondre à votre question, vous pouvez savoir ce qui se trouve exactement dans votre garage en procédant comme suit:
Vehicle v = myGarage[0];
if (v instanceof Car) {
// This vehicle is a car
((Car)v).doSomeCarStuff();
} else if(v instanceof Boat){
// This vehicle is a boat
((Boat)v).doSomeBoatStuff();
}
MISE À JOUR: Comme vous pouvez le lire dans les commentaires ci-dessous, cette méthode convient aux solutions simples, mais ce n’est pas une bonne pratique, en particulier si vous avez un grand nombre de véhicules dans votre garage. Alors utilisez-le uniquement si vous savez que le garage restera petit. Si ce n'est pas le cas, recherchez "Eviter une instance de" en cas de débordement de pile, il existe plusieurs façons de le faire.
Si vous utilisez le type de base, vous ne pouvez accéder qu'aux méthodes publiques et à leurs champs.
Si vous souhaitez accéder au type étendu, mais que vous avez un champ du type de base où il est stocké (comme dans votre cas), vous devez d'abord le convertir, puis vous pouvez y accéder:
Car car = (Car)myGarage[0];
car.doSomeCarStuff();
Ou plus court sans champ temporaire:
((Car)myGarage[0]).doSomeCarStuff();
Puisque vous utilisez des objets Vehicle
, vous ne pouvez appeler des méthodes de la classe de base que sur celles-ci sans transtypage. Donc, pour votre garage, il peut être judicieux de distinguer les objets dans différents tableaux - ou de meilleures listes - un tableau n'est souvent pas une bonne idée, car sa gestion est beaucoup moins souple que celle d'une classe basée sur Collection
.
Vous avez défini que votre garage va stocker les véhicules, de sorte que vous ne vous souciez pas du type de véhicule que vous avez. Les véhicules ont des caractéristiques communes comme le moteur, la roue, un comportement comme le déplacement. La représentation réelle de ces caractéristiques peut être différente, mais les couches abstraites sont identiques. Vous avez utilisé la classe abstraite qui signifie que certains attributs, comportements sont exactement les mêmes par les deux véhicules. Si vous voulez exprimer le fait que vos véhicules ont des caractéristiques abstraites communes, utilisez une interface telle que se déplacer peut vouloir dire différent en voiture ou en bateau. Les deux peuvent aller d'un point A à un point B, mais d'une manière différente (sur roue ou sur l'eau - la mise en œuvre sera donc différente). Vous avez donc des véhicules dans le garage qui se comportent de la même manière et vous ne tenez pas compte des caractéristiques spécifiques d'eux.
Pour répondre au commentaire:
Interface signifie un contrat décrivant comment communiquer avec le monde extérieur. Dans le contrat, vous définissez que votre véhicule peut être déplacé, peut être piloté, mais vous ne décrivez pas son fonctionnement, cela est décrit dans l’implémentation. Par classe abstraite, vous pouvez avoir des fonctions pour lesquelles vous partagez une implémentation, mais vous devez également: fonction que vous ne savez pas comment elle sera mise en œuvre.
Un exemple d'utilisation de la classe abstraite:
abstract class Vehicle {
protected abstract void identifyWhereIAm();
protected abstract void startEngine();
protected abstract void driveUntilIArriveHome();
protected abstract void stopEngine();
public void navigateToHome() {
identifyWhereIAm();
startEngine();
driveUntilIArriveHome();
stopEngine();
}
}
Vous utiliserez les mêmes étapes pour chaque véhicule, mais leur mise en oeuvre différera selon le type de véhicule. La voiture peut utiliser le GPS, le bateau peut utiliser le sonar pour identifier sa position.
Je suis un novice en programmation Java et en essayant de maîtriser le POO.
Juste mes 2 centimes - 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. Un sur "OOP" et un sur sa mise en oeuvre en Java.
Tout d’abord, oui, vous avez une voiture dans votre garage. Donc, vos hypothèses sont justes. 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 Vehicle
, le compilateur le sait seulement. Donc, il vérifiera que vous n’effectuez que des opérations autorisées sur n’importe quel Vehicle
. (En d'autres termes, méthodes et attributs visibles dans la déclaration Vehicle
).
Vous pouvez expliquer au compilateur que "vous savez en fait que cette Vehicle
est une Car
" , en utilisant une distribution explicite (Car)
. le compilateur vous croira - même si en Java il y a un contrôle au moment de l'exécution, ce qui pourrait conduire à un ClassCastException
qui évite des dommages supplémentaires si vous menti (d'autres langages comme C++ ne vérifieront pas au moment de 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 au moment de l’exécution (c.-à-d.: instanceof
) pour vérifier le type "réel" d’un objet avant de tenter de le lancer. Mais ceci est principalement considéré comme une mauvaise pratique en Java.
Comme je l’ai dit, c’est la manière dont Java d’implémenter la POO. Il est tout différent classe famille de langues communément appelées "langages dynamiques" , que ne vérifie qu'au moment de 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 courantes vers une classe de base (éventuellement abstraite) pour satisfaire le système de types. Ceci s'appelle frappe de canard .
Vous avez demandé à votre majordome:
Jeeves, tu te souviens de mon garage sur l'île de Java? Allez vérifier si le premier véhicule garé là est automatique.
et Jeeves paresseux a dit:
mais monsieur, si c'est un véhicule qui ne peut être automatique ou non automatique?
C'est tout.
Ok, ce n’est pas tout, puisque la réalité est plus typée canard que typée statiquement. C'est pourquoi j'ai dit que Jeeves est paresseux.
Votre problème ici se situe à un niveau plus fondamental: vous avez construit Vehicle
de telle manière que Garage
ait besoin d'en savoir plus sur ses objets que l'interface Vehicle
n'en donne. Vous devriez essayer de construire la classe Vehicle
de la perspective Garage
(et en général du point de vue de tout ce qui va utiliser Vehicle
): quel genre de choses doivent-ils faire avec leurs véhicules? Comment puis-je rendre ces choses possibles avec mes méthodes?
Par exemple, à partir de votre exemple:
bool carIsAutomatic = myGarage[0].auto;
Votre garage veut en savoir plus sur le moteur d'un véhicule pour ... des raisons? Quoi qu'il en soit, il n'est pas nécessaire que cela soit simplement exposé par Car
. Vous pouvez toujours exposer une méthode isAutomatic()
non implémentée dans Vehicle
, puis l'implémenter comme return True
dans Boat
et return this.auto
dans Car
.
Il serait même préférable d’avoir une EngineType
enum à trois valeurs (HAS_NO_GEARS
, HAS_GEARS_AUTO_SHIFT
, HAS_GEARS_MANUAL_SHIFT
), ce qui laisserait à votre code le soin de se fonder sur les caractéristiques réelles d’un générique. Vehicle
proprement et avec précision. (Vous aurez besoin de cette distinction pour gérer les motos, de toute façon.)
Votre garage contient des véhicules, donc la vue de contrôle statique du compilateur que vous avez un véhicule et comme .auto est un champ de voiture vous ne pouvez pas y accéder, dynamiquement c'est une voiture afin que la distribution ne crée pas de problème, s'il le sera un bateau et que vous essayez de faire un casting sur Car augmentera une exception à l’exécution.
C’est un bon endroit pour l’application du modèle de conception Visitor
.
La beauté de ce modèle réside dans le fait que vous pouvez appeler du code non lié sur différentes sous-classes d’une superclasse sans avoir à faire des conversions bizarres partout ou à mettre des tonnes de méthodes sans lien dans la superclasse.
Cela fonctionne en créant un objet Visitor
et en permettant à notre classe Vehicle
de accept()
le visiteur.
Vous pouvez également créer de nombreux types de Visitor
et appeler du code non lié à l'aide des mêmes méthodes, mais simplement une implémentation différente de Visitor
, ce qui rend ce modèle de conception très puissant lors de la création de classes propres.
Une démo par exemple:
public class VisitorDemo {
// We'll use this to mark a class visitable.
public static interface Visitable {
void accept(Visitor visitor);
}
// This is the visitor
public static interface Visitor {
void visit(Boat boat);
void visit(Car car);
}
// Abstract
public static abstract class Vehicle implements Visitable {
// NO OTHER RANDOM ABSTRACT METHODS!
}
// Concrete
public static class Car extends Vehicle {
public void doCarStuff() {
System.out.println("Doing car stuff");
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// Concrete
public static class Boat extends Vehicle {
public void doBoatStuff() {
System.out.println("Doing boat stuff");
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// Concrete visitor
public static class StuffVisitor implements Visitor {
@Override
public void visit(Boat boat) {
boat.doBoatStuff();
}
@Override
public void visit(Car car) {
car.doCarStuff();
}
}
public static void main(String[] args) {
// Create our garage
Vehicle[] garage = {
new Boat(),
new Car(),
new Car(),
new Boat(),
new Car()
};
// Create our visitor
Visitor visitor = new StuffVisitor();
// Visit each item in our garage in turn
for (Vehicle v : garage) {
v.accept(visitor);
}
}
}
Comme vous pouvez le constater, StuffVisitor
vous permet d’appeler un code différent sur Boat
ou Car
, selon l’implémentation de visit
appelée. Vous pouvez également créer d'autres implémentations du visiteur pour appeler différents codes avec le même motif .visit()
.
Notez également qu'en utilisant cette méthode, il n'y a pas d'utilisation de instanceof
ni de vérification de classe de hacky. Le seul code dupliqué entre les classes est la méthode void accept(Visitor)
.
Si vous voulez supporter 3 types de sous-classes concrètes par exemple, vous pouvez aussi ajouter cette implémentation dans l'interface Visitor
.
Je ne fais que mettre en commun les idées des autres ici (et je ne suis pas un mec Java, donc c'est pseudo plutôt que réel), mais dans cet exemple artificiel, je résumerais mon approche de vérification de voiture dans une classe dédiée, qui ne connaît que les voitures et ne se soucie que des voitures quand on regarde les garages:
abstract class Vehicle {
public abstract string getDescription() ;
}
class Transmission {
public Transmission(bool isAutomatic) {
this.isAutomatic = isAutomatic;
}
private bool isAutomatic;
public bool getIsAutomatic() { return isAutomatic; }
}
class Car extends Vehicle {
@Override
public string getDescription() {
return "a car";
}
private Transmission transmission;
public Transmission getTransmission() {
return transmission;
}
}
class Boat extends Vehicle {
@Override
public string getDescription() {
return "a boat";
}
}
public enum InspectionBoolean {
FALSE, TRUE, UNSUPPORTED
}
public class CarInspector {
public bool isCar(Vehicle v) {
return (v instanceof Car);
}
public bool isAutomatic(Car car) {
Transmission t = car.getTransmission();
return t.getIsAutomatic();
}
public bool isAutomatic(Vehicle vehicle) {
if (!isCar(vehicle)) throw new UnsupportedVehicleException();
return isAutomatic((Car)vehicle);
}
public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
if (!isCar(garage[bay])) return InspectionBoolean.UNSUPPORTED;
return isAutomatic(garage[bay])
? InspectionBoolean.TRUE
: InspectionBoolean.FALSE;
}
}
Le fait est que vous avez déjà décidé que vous ne vous souciez que des voitures lorsque vous posez des questions sur la transmission de la voiture. Il suffit donc de demander à CarInspector. Grâce au tri-state Enum, vous pouvez maintenant savoir si c'est automatique ou même s'il ne s'agit pas d'une voiture.
Bien entendu, vous aurez besoin de différents VehicleInspectors pour chaque véhicule qui vous tient à cœur. Et vous venez de pousser le problème de VehicleInspector à instancier dans la chaîne.
Au lieu de cela, vous voudrez peut-être examiner les interfaces.
Résumé getTransmission
vers une interface (par exemple, HasTransmission
). De cette façon, vous pouvez vérifier si un véhicule a une transmission ou écrire un TransmissionInspector:
abstract class Vehicle { }
class Transmission {
public Transmission(bool isAutomatic) {
this.isAutomatic = isAutomatic;
}
private bool isAutomatic;
public bool getIsAutomatic() { return isAutomatic; }
}
interface HasTransmission {
Transmission getTransmission();
}
class Car extends Vehicle, HasTransmission {
private Transmission transmission;
@Override
public Transmission getTransmission() {
return transmission;
}
}
class Bus extends Vehicle, HasTransmission {
private Transmission transmission;
@Override
public Transmission getTransmission() {
return transmission;
}
}
class Boat extends Vehicle { }
enum InspectionBoolean {
FALSE, TRUE, UNSUPPORTED
}
class TransmissionInspector {
public bool hasTransmission(Vehicle v) {
return (v instanceof HasTransmission);
}
public bool isAutomatic(HasTransmission h) {
Transmission t = h.getTransmission();
return t.getIsAutomatic();
}
public bool isAutomatic(Vehicle v) {
if (!hasTranmission(v)) throw new UnsupportedVehicleException();
return isAutomatic((HasTransmission)v);
}
public InspectionBoolean isAutomatic(Vehicle[] garage, int bay) {
if (!hasTranmission(garage[bay])) return InspectionBoolean.UNSUPPORTED;
return isAutomatic(garage[bay])
? InspectionBoolean.TRUE
: InspectionBoolean.FALSE;
}
}
Vous dites maintenant que vous ne pouvez parler que de la transmission, quel que soit le véhicule, et que vous pouvez demander à TransmissionInspector. Le bus et la voiture peuvent être inspectés par le TransmissionInspector, mais celui-ci ne peut se renseigner que sur la transmission.
Maintenant, vous pouvez décider que les valeurs booléennes ne vous intéressent pas. À ce stade, vous préférerez peut-être utiliser un type générique pris en charge, qui expose à la fois l'état pris en charge et la valeur:
class Supported<T> {
private bool supported = false;
private T value;
public Supported() { }
public Supported(T value) {
this.isSupported = true;
this.value = value;
}
public bool isSupported() { return supported; }
public T getValue() {
if (!supported) throw new NotSupportedException();
return value;
}
}
Maintenant, votre inspecteur peut être défini comme:
class TransmissionInspector {
public Supported<bool> isAutomatic(Vehicle[] garage, int bay) {
if (!hasTranmission(garage[bay])) return new Supported<bool>();
return new Supported<bool>(isAutomatic(garage[bay]));
}
public Supported<int> getGearCount(Vehicle[] garage, int bay) {
if (!hasTranmission(garage[bay])) return new Supported<int>();
return new Supported<int>(getGearCount(garage[bay]));
}
}
Comme je l'ai déjà dit, je ne suis pas un Java, une partie de la syntaxe ci-dessus est peut-être fausse, mais les concepts doivent être vérifiés. Néanmoins, ne lancez pas ce qui est important ci-dessus sans le tester au préalable.
Créer Champs de niveau de véhicule qui permettront de rendre chaque véhicule plus distinct.
public abstract class Vehicle {
public final boolean isCar;
public final boolean isBoat;
public Vehicle (boolean isCar, boolean isBoat) {
this.isCar = isCar;
this.isBoat = isBoat;
}
}
Définissez les champs de niveau de véhicule de la classe héritante sur la valeur appropriée.
public class Car extends Vehicle {
public Car (...) {
super(true, false);
...
}
}
public class Boat extends Vehicle {
public Boat (...) {
super(false, true);
...
}
}
Mettre en place utiliser les champs de niveau de véhicule pour déchiffrer correctement le type de véhicule.
boolean carIsAutomatic = false;
if (myGarage[0].isCar) {
Car car = (Car) myGarage[0];
car.carMethod();
carIsAutomatic = car.auto;
}
else if (myGarage[0].isBoat) {
Boat boat = (Boat) myGarage[0];
boat.boatMethod();
}
Depuis que vous avez dit à votre compilateur que tout dans votre garage est un véhicule, vous êtes coincé avec les méthodes et les champs de niveau classe de véhicule. Si vous voulez déchiffrer correctement le type de véhicule, vous devez définir certains champs de niveau de classe, par exemple. isCar
et isBoat
pour que le programmeur comprenne mieux le type de véhicule que vous utilisez.
Java est un langage de type sûr, il est donc préférable de toujours vérifier avant de traiter les données transtypées comme vos Boat
s et Car
s.
Si vous êtes sur Java, vous pouvez utiliser les réflexions pour vérifier si une fonction est disponible et l'exécuter également.
La modélisation des objets que vous souhaitez présenter dans un programme (afin de résoudre un problème) est une chose, le codage en est une autre. Dans votre code, je pense qu’il est fondamentalement inapproprié de modéliser un garage en utilisant un tableau. Les tableaux ne doivent pas souvent être considérés comme des objets, bien qu'ils apparaissent , généralement dans le but de se tenir compte d'eux-mêmes intégrité d'un langage et fournissant une certaine familiarité, mais un tableau en tant que type n'est en réalité qu'un élément spécifique à l'ordinateur, à mon humble avis, en particulier en Java, où vous ne pouvez pas étendre tableaux.
Je comprends que modéliser correctement une classe pour représenter un garage ne vous aidera pas à répondre à votre question "voitures dans un garage"; juste un conseil.
Retournez au code. En plus de résoudre le problème de la programmation orientée objet, quelques questions seraient utiles pour créer une scène et ainsi mieux comprendre le problème que vous souhaitez résoudre (en supposant qu'il y en ait un, et pas seulement "obtenir un blocage"):
carIsAutomatic
?carIsAutomatic
, qui ou quoi effectuerait doSomeCarStuff
?Cela peut être un inspecteur ou quelqu'un qui sait seulement conduire des voitures à transmission automatique, etc., mais du point de vue du garage, tout ce qu'il sait, c'est qu'il détient un véhicule. C'est donc (dans ce modèle) la responsabilité de cet inspecteur. ou conducteur pour dire si c'est une voiture ou un bateau; à ce moment, vous voudrez peut-être commencer à créer un autre groupe de classes représentant des types d'acteurs similaires dans la scène. Cela dépend du problème à résoudre. Si vous en avez vraiment besoin, vous pouvez modéliser le garage comme un système super intelligent, de sorte qu'il se comporte comme un distributeur automatique, au lieu d'un garage ordinaire, dont le bouton indique "Voiture" et un autre indique "Bateau", pour que les gens puissent appuyer sur le bouton pour obtenir une voiture ou un bateau comme ils le souhaitent, ce qui rend ce garage super intelligent responsable de la définition de ce qui doit être présenté (une voiture ou un bateau) à ses utilisateurs; pour suivre cette improvisation, le garage peut exiger une certaine comptabilité lorsqu’il accepte un véhicule, que quelqu'un doit fournir des informations, etc., toutes ces responsabilités vont au-delà d’un simple principal classe.
Cela dit, je comprends certainement tous les problèmes rencontrés, ainsi que ceux qui ont précédé, pour coder un programme OO, en particulier lorsque le problème qu’il essaie de résoudre est très simple, mais OO est En effet, un moyen possible de résoudre de nombreux autres problèmes. D'après mon expérience, avec quelques exemples fournissant des exemples d'utilisation, les gens commencent à concevoir des scènes d'interaction entre objets, les catégorisent en classes (ainsi qu'en interfaces en Java), puis utilisent quelque chose comme votre Classe principale vers bootstrap le monde .