1

C'est sûrement une question idiote, mais je n'ai jamais su trouver une réponse convenable, alors je me jette à l'eau.
Comment exprimer, avec des objets (héritage, etc.) la situation suivante (qu'il m'est arrivé maintes fois de rencontrer) :
Soit une entité, Véhicule, avec tout un tas de choses qui lui sont propres (couleur, poids, etc.).
Soit une autre entité, Voiture, qui correspond à un Véhicule doté de quelques propriétés supplémentaires (genre le type de carburant).
Et encore, une autre entité, Vélo, qui correspond aussi à un Véhicule doté de propriétés supplémentaires (genre le nombre de plateaux et de pignons). Enfin, une entité Parking, qui peut contenir des Véhicules, qu'ils soient des Voitures ou des Vélos.


L'idée courante, je pense serait de définir une classe Véhicule, puis de la spécialiser en Voiture et en Vélo (via de l'héritage).
Cependant, le parking contenant des Véhicules, comment peut-il accéder aux propriétés spécifiques d'un Vélo ou d'une Voiture ?
En faisant de l'introspection ? N'y a-t-il pas un moyen plus élégant ? Je déteste écrire du code du genre :
if vehicule is Voiture:
  voiture = (Voiture)vehicule
  ...
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

2

OK pour la spécialisation de véhicule en vélo/voiture.

2e point > non non l'introspection c'est un truc compliqué qui utilise la réflexion, c'est vachement plus compliqué que ce que tu veux.

la liaison entre parking et véhicule se fait pas une simple aggrégation. t'auras une méthode addVehicule() etc... dans parking.

note aux autres: je me trompe pas?

3

squalyl (./2) :
la liaison entre parking et véhicule se fait pas une simple aggrégation. t'auras une méthode addVehicule() etc... dans parking.

Oui bien sûr.
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

4

Les actions spécifique pourront se faire par redéfinition de fonctions:
Par exemple tu peux imposer à tout véhicule de savoir se garer, par une fonction se_garer. Chaque type de véhicule aura son se_garer spécifique et puis ensuite dans ton parking tu pourras faire :
vehicule v = ...;
v.se_garer();
s'il s'agit d'une voiture ça appellera la version dédiée aux voitures, si c'est un vélo celle du vélo, etc
Le choix de la fonction à appeler se fait de façon dynamique (en C++ tu auras à déclarer la fonction se_garer comme virtuelle pour ça).
avatar
Combien de tas de bois une marmotte pourrait couper si une marmotte pouvait couper du bois ?

5

Je ne vois pas de solution générale au problème.
Tu peux peut être sortir le code "if vehicle is Voiture" de Parking (s'il n'a rien à voir avec un parking) et le mettre dans une méthode de Voiture qui implémente/surcharge une méthode de la classe Vehicle (en gros c'est ce que dis Twindruff).
Sinon, tu peux maintenir dans Parking des listes de Velo et de Voiture séparés.

6

Jyaif (./5) :
Sinon, tu peux maintenir dans Parking des listes de Velo et de Voiture séparés.
ouais mais dans ce cas pour rajouter un type de véhicule ça devient un peu compliqué.
avatar
Combien de tas de bois une marmotte pourrait couper si une marmotte pouvait couper du bois ?

7

Tu perds surtout la moitié de l'intérêt de l'héritage, car tu réutilise du code dans tes classes dérivées de Vehicule, mais tu le duplique quand même dans parking.
Enfin d'une manière générale je comprends pas trop le problème... D'un coté tu suppose que le parking peut contenir un vehicule (au sens objet, donc n'importe quoi en réalité), et d'un autre tu suppose que c'est forcément une voiture ou un velo, alors que ça se trouve c'est simplement une soucoupe volante. (hmm ok j'ai rien dit grin)
Personellement je vois une contradition entre els deux, enfin je suis peut-être le seul. tongue
Mais par exemple pour quelles raisons aurais-tu besoin d'accéder aux propriétés spécifiques des véhicules dans le parking ?
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

8

Peu importe s'il y a une soucoupe volante ou pas.

Bon, en fait l'exemple là est bidon, j'avais essayé de trouver un cas simple pour décrire la situation, parce que ça m'est arrivé souvent d'avoir ce problème, et je me souviens qu'à l'IUT on nous indiquait de faire de la réflexion dans Parking pour pouvoir spécialiser les traitements selon le type de véhicule (quand celui-ci ne pouvait pas être exprimé avec une fonction dans Véhicule à surcharger), et je trouvais ça complètement dommage.

Alors voilà mon vrai problème :
Je souhaite modéliser le fait qu'une classe regroupe des attributs. Ceux-ci peuvent être des données, mais ils peuvent aussi être exprimés par un couple de fonctions get/set, voire un triplet add/remove/get pour les listes.
Je pense donc utiliser une classe Classe, contenant une liste d'Attribut, pour ensuite effectuer un traitement sur chaque attribut.
Comment modéliser le fait que l'accès à un attribut peut se faire par une donnée membre (auquel cas j'ai besoin de noter l'identificateur de la donnée en question) ou bien par des fonctions (auquel cas je dois connaître le nom de toutes les opérations d'accès et mise à jour) ?
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

9

La méthode la plus simple est de toujours utiliser des getters et setters pour tes attributs, même s'ils sont triviaux, ça permet aussi de changer l'implémentation plus tard.
avatar
Mes news pour calculatrices TI: Ti-Gen
Mes projets PC pour calculatrices TI: TIGCC, CalcForge (CalcForgeLP, Emu-TIGCC)
Mes chans IRC: #tigcc et #inspired sur irc.freequest.net (UTF-8)

Liberté, Égalité, Fraternité

10

pour moi la vraie question est toujous "pourquoi tu dois accéder à ces données qui ne sont pas déclarées dans la structure de la classe de base que tu connais ?"
Je pense que ça marche mieux avec un exemple concret ou on ne peut pas déformer les choses, puisque de toutes façons la solution dépend entièrement du problème (les parties concrètes du problème pas les abstractions ^^)
De la façon dont je vois le problème "générique", quoi qu'il arrive tu en reviens a faire un cast à un moment ou un autre mais ça n'a pas forcément la même signification.
Je vois deux solutions a ton problème (de manière générique donc ce n'est pas applicable tel quel partout évidemment) soit avec de l'héritage multiple (interfaces), et il faudra quand même caster des trucs, soit en redécoupant le problème (ça revient un peu à ce que disait Twindruff aussi), et là il faudra peut-être quand même caster des trucs mais tu peux peut-être l'éviter.
La solution change si tu es dans un contexte où ta classe n'a que N classes dérivées que tu connais, ou si tu es dans le contexte ou ta classe a une infinité de classes dérivées et donc que tu ne les connais pas toutes.
Mais pour moi le point important de la résolution reste quand même le "pourquoi je dois faire ça ?" (désolé grin)
avatar
Le scénario de notre univers a été rédigée par un bataillon de singes savants. Tout s'explique enfin.
T'as un problème ? Tu veux un bonbon ?
[CrystalMPQ] C# MPQ Library/Tools - [CrystalBoy] C# GB Emulator - [Monoxide] C# OSX library - M68k Opcodes

11

Si t'as un traitement général que tu veux spécialiser selon la classe (comme dans l'exemple du parking), tu peux utiliser Visitor. À la base, c'est plutôt fait pour mettre chaque traitement dans une classe séparée (et ainsi de pouvoir avoir un tas de traitement différent sans trop polluer la classe d'origine), mais en l'occurrence, ça fonctionne grâce au double dispatch, et donc tu peux t'éviter des suites de "if vehicule is SoucoupeVolante", en codant autant de fonctions à ton visiteur. Pas besoin de vérification de type et pas besoin de downcasting, donc tu trouveras peut-être ça plus élégant.
Après forcément, il y a des points négatifs, ça sera plus chiant si tu veux rajouter des classes à ta hiérarchie, et ça casse un peu l'encapsulation parce que tes classes doivent fournir une interface suffisamment riche pour pouvoir faire tout le traitement depuis le visiteur, mais c'était déjà le cas si tu y accèdes depuis le parking...
avatar
;)

12

Je me suis posé un peu près les mêmes questions que Sasume.
Imaginons un jeu d'echec avec une collection de pièces pouvant être soit blanches ou noires. On a une classe CPiece avec pour spécialisation les classes CKing, CQueen, CBishop... Comment faire en sorte dans notre classe CBoard de créer un plateau sans révéler les types de pièces ? Ma première idée était de confier la création du plateau à la classe CPiece mais je trouve pas çà très propre.
C'est vrai l'abstration est poussé à l'extrème mais l'idée est de ne jamais employé directement CQueen... bref en changeant une classe passer d'un jeu d'echec à un jeu de dames... sans toucher aux classes au dessus. ^^
avatar
la Nature nous montre seulement la queue du lion. Mais je suis certain que le lion a qui elle appartient pense qu'il ne peut pas se révéler en une fois en raison de son immense taille.

- Fondateur de Ti-Gen -: http://www.tigen.org

- Membre du Groupe Orage Studio -: http://oragestudio.free.fr/

- Mon site perso -: http://tisofts.free.fr

Projets TI68K en cours:
GFA-Basic = http://www.tigen.org/gfabasic
Arkanoid.
PolySnd 3.0.

13

Si tu veux que ton plateau ne soit qu'un conteneur de pièces et qu'il sache rien d'autre sur le jeu, ça peut se faire, mais faut pas non plus repousser ça sur la classe pièce parce qu'elle a rien demandé et son nom a rien à voir en plus. happy
Je pense que tu peux mettre ça dans une toute nouvelle hiérarchie de classe qui va te fabriquer les plateaux, une classe abstraite (ou interface) et une classe pour chaque type de jeu. Selon la classe que tu choisis pour ton instance, tu peux fabriquer ce que tu veux.
interface BoardMaker {
  Board makeBoard();
}

class ChessMaker implements BoardMaker {
  Board makeBoard() {
    Board board = new Board(8, 8);
    board.add(1, 1, new Rook(Piece.White)); // Tour blanche
    board.add(1, 2, new Knight(Piece.White));
    // bla
  }
}

class CheckersMaker implements BoardMaker {
  Board makeBoard() {
    Board board = new Board(10, 10);
    board.add(1, 1, new CheckersPieces(Piece.White);
    board.add(1, 3, new CheckersPieces(Piece.White);
    // bla
  }
}

main() {
  BoardMaker bm;
  if (gameType == chess) bm = new ChessMaker();
  else if (gameType == checkers) bm = new CheckersMaker();

  Board board = bm.makeBoard();
  // bla
}
Non ?
avatar
;)

14

./10 Bah j'ai expliqué mon cas concret en ./8
Mais je crois que j'ai trouvé une solution smile
En fait, c'est clair que la bonne question est "pourquoi je dois faire ça ?"
avatar
« Quand le dernier arbre sera abattu, la dernière rivière empoisonnée, le dernier poisson capturé, alors vous découvrirez que l'argent ne se mange pas. »

15

BiHi> Oui je vois moi aussi j'aurais fait ça mais dans ton code tu révèles la collection de pièces dans ta classe ChessMaker ainsi, si cette collection change il faudra aussi changer ChessMaker.
Bon il est vrai que le niveau d'abstraction dans un jeu d'echecs est ici poussé à l'extrême et que donc il n'est vraiment pas utile de se prendre la tête. Bref de façon plus générale est-ce qu'il est possible en C++ par un moyen ou un autre de faire en sorte que l'ajout d'une classe entraine une modification du programme. Bref qu'à partir d'une classe abstraite on puisse connaître la liste des classes concrètes qui en hérites ?
avatar
la Nature nous montre seulement la queue du lion. Mais je suis certain que le lion a qui elle appartient pense qu'il ne peut pas se révéler en une fois en raison de son immense taille.

- Fondateur de Ti-Gen -: http://www.tigen.org

- Membre du Groupe Orage Studio -: http://oragestudio.free.fr/

- Mon site perso -: http://tisofts.free.fr

Projets TI68K en cours:
GFA-Basic = http://www.tigen.org/gfabasic
Arkanoid.
PolySnd 3.0.

16

Il faut comprendre que l'héritage signifie "est un".
Le principe de liskov, autrement appelé principe de substitution est claire, on utilise l'héritage pour utiliser les objets sans différenciation.
Prenons l'exemple d'un carré: un carré est un rectangle.
Notion d'héritage simple carré dérive de rectangle...
Pourtant on se rend vite compte que cet héritage rompt les contrats (cf B. Meyer: prog par contrat) de la classe rectangle (a savoir qu'un setw, le h est invariant).
De ce fait on a pas le principe de substitution pour le test unitaire du rectangle:
setw(2);
setw(3);
TEST_EQ(area() == 6); -> error

La classe qui construit l'echequier doit connaître le type de chaque pièce, elle donne ensuite une liste des pièces castées en classe pièce.