Pouvez-vous avoir des résumés / classes «vides»?


10

Bien sûr, vous pouvez, je me demande simplement s'il est rationnel de concevoir de cette manière.

Je fais un clone en petits groupes et je faisais du design de classe. Je voulais utiliser l'héritage, même si je n'y suis pas obligé, pour appliquer ce que j'ai appris en C ++. Je pensais à la conception des classes et j'ai trouvé quelque chose comme ça:

GameObject -> classe de base (se compose de membres de données comme les décalages x et y et d'un vecteur de SDL_Surface *

MovableObject: GameObject -> classe abstraite + classe dérivée de GameObject (une méthode void move () = 0;)

NonMovableObject: GameObject -> classe vide ... pas de méthodes ou membres de données autres que constructeur et destructeur (du moins pour l'instant?).

Plus tard, je prévoyais de dériver une classe de NonMovableObject, comme Tileset: NonMovableObject. Je me demandais simplement si des classes abstraites "vides" ou simplement des classes vides étaient souvent utilisées ... Je remarque que la façon dont je fais cela, je crée juste la classe NonMovableObject juste pour la catégorisation.

Je sais que je réfléchis trop juste pour faire un clone en petits groupes, mais je me concentre moins sur le jeu et plus sur l'utilisation de l'héritage et la conception d'une sorte de cadre de jeu.

Réponses:


2

En C ++, vous avez l'héritage multiple, donc il n'y a littéralement aucun avantage à avoir ces classes vides. Si vous souhaitez que Ball hérite de GameObject et MovableObject plus tard (par exemple, vous souhaitez conserver un tableau de GameObjects et appeler une méthode Tick tous les nièmes de seconde pour les faire tous bouger), c'est assez facile à faire.

Mais, dans cette situation, je suggérerais personnellement que vous vous souveniez plutôt de l'axiome « préférez la composition (encapsulation) à l'héritage » et regardez le modèle d'État . Si vous souhaitez que chaque objet ait son état de mouvement ultérieurement, transmettez-le au GameObject. Par exemple: DiagonalBouncingMovementState pour la balle, HoriazontalControlledMovementState pour la raquette, NoMovementState pour une brique.

1) Cela vous facilitera la tâche d'écrire des tests unitaires , si vous le souhaitez (ce que je recommanderais encore) - car vous pouvez tester GameObject et chacun de vos états indépendamment.

2) Supposons que vous ayez des jetons qui tombent des briques, mais que vous souhaitiez ensuite en changer un afin qu'ils se déplacent en diagonale - plutôt que de le déplacer autour d'une hiérarchie complexe d'héritage de classe, vous pouvez simplement changer l'état de mouvement codé en dur qui lui est transmis. .

3) Plus important encore: lorsque vous souhaitez commencer à changer dans le jeu la façon dont la balle et / ou la raquette se déplacent en fonction de ces jetons, vous continuez à changer son état de mouvement (DiagonalMovementState devient DiagonalWithGravityMovementState).

Maintenant, rien de tout cela ne devait suggérer que l'héritage est toujours mauvais, juste pour démontrer pourquoi nous préférons l'encapsulation à l'héritage. Vous pouvez toujours vouloir dériver chaque type d'objet de GameObject et demander à ces classes de comprendre leur état de mouvement initial. Vous voudrez presque certainement dériver chacun de vos états de mouvement d'une classe abstraite MovementState car C ++ n'a pas d'interfaces.

0,02 $


8

En Java, les interfaces "vides" sont utilisées comme marqueurs (par exemple Serializable), car au moment de l'exécution, les objets peuvent être vérifiés s'ils "implémentent" cette interface; mais en C ++, cela me semble assez inutile.

OMI, les fonctionnalités linguistiques devraient être considérées comme des outils, quelque chose qui vous aide à atteindre certains objectifs, et non des obligations. Ce n'est pas parce que vous pouvez utiliser une fonctionnalité que vous devez le faire . Un héritage sans signification n'améliore pas un programme.


3

Vous n'êtes pas trop en train de le penser; vous pensez et c'est bien.

Comme déjà indiqué, n'utilisez pas une fonction de langue simplement parce qu'elle est là. Les compromis sur les fonctionnalités du langage d'apprentissage sont la clé d'une bonne conception.

Il ne faut pas utiliser les classes de base pour la catégorisation. Cela pourrait impliquer la vérification du type. C’est une mauvaise idée.

Pensez à écrire des abstractions pures qui définissent le contrat de vos objets. Le contrat de vos objets est l'interface tournée vers l'extérieur. Son interface publique. Ce qu'il fait.

Remarque: Il est important de comprendre qu'il ne s'agit pas des données dont il dispose x, ymais de ce qu'il fait move().

Dans votre conception, vous avez une classe GameObject. Vous comptez sur lui pour ses données et non pour sa fonctionnalité. Il semble efficace et correct d'utiliser l'héritage pour partager ce qui semble être des données partagées communes, dans votre cas xet y.

Ce n'est pas l'utilisation correcte de l'héritage. Rappelez-vous toujours que l'héritage est la forme de couplage la plus serrée que vous puissiez avoir et vous devez vous efforcer de toujours avoir un couplage lâche.

De par sa nature NonMovableObjectmême, il ne bouge pas. Son xet ydevrait certainement être déclaré const. De par sa nature même, il MoveableObjectdoit bouger. Il ne peut pas déclarer son xet yas const. Ce ne sont donc pas les mêmes données et elles ne peuvent pas être partagées.

Tenez compte de la fonctionnalité ou du contrat de vos objets. Que devraient-ils faire ? Faites le bon choix et les données viendront. Travaillez sur un objet à la fois. S'inquiéter de chacun à son tour. Vous constaterez que vos bons designs sont réutilisables.

Peut-être qu'il n'y en a pas NonMovableObjectdu tout. Qu'en est-il d'une pure abstraction GameObjectBasequi définit une move()méthode? Votre système instancie GameObjectles implémentations move()et le système ne déplace que celles qui doivent se déplacer.

Ce n'est que le début; un goût. Le trou du lapin va beaucoup plus loin. Il n'y a pas de cuillère.


S'il est vrai que ni l'un MovableObjectni l' autre ne NonMovableObjectpeuvent hériter l'un de l'autre, ils ont tous deux un emplacement; en tant que tels, ils devraient tous deux être consommables par du code qui, par exemple, veut diriger le but d'un monstre vers un objet sans se soucier de savoir si cet objet pourrait se déplacer ou non.
supercat

2

Il a des applications en C # comme ammoQ mentionné, mais en C ++ la seule application serait si vous vouliez avoir une liste de pointeurs NonMovableObject.

Mais alors j'imagine que vous détruisez les objets via le pointeur NonMoveableObject, auquel cas vous aurez besoin de destructeurs virtuels, et ce ne sera plus une classe vide. :)


J'ai aussi pensé à ce cas, mais que pourriez-vous faire avec une telle liste d'objets qui ne présentent aucun comportement? Très probablement, vous découvrirez en quelque sorte le vrai type et utiliserez un cast pour accéder aux méthodes et champs disponibles - certainement une odeur de code.
user281377

Oui, c'est un bon point.
tenpn

1

Une situation où vous pourriez voir ceci est où vous voulez qu'une collection fortement tyuped contienne des types autrement hétérogènes. Je ne dis pas que c'est une excellente idée, cela peut indiquer une abstraction faible ou une mauvaise conception, mais cela arrive. Comme vous le mentionnez, c'est une façon de classer les choses et cela peut être utile et parfaitement valable.

Par exemple, vous voulez qu'une collection contienne des chats et des chiens. Dans votre conception, vous décidez qu'ils ont beaucoup en commun, alors ayez une classe de base de Mamal et utilisez ce type dans votre collection (par exemple, List). Tout bon. Ensuite, vous décidez que vous souhaitez ajouter des grenouilles à votre collection, mais ce ne sont pas des mammifères, donc une façon de faire serait de faire de la sous-classe des grenouilles et des mamals des animaux, mais peut-être que vous ne pouvez pas penser à beaucoup de choses que les grenouilles et les mamals ont dans commun donc l'animal reste vide. Vous tapez alors votre collection avec Animal au lieu de Mamal et tout va bien.

Dans la vraie vie, je trouve que même si je crée tôt ou tard une classe de base vide, certaines fonctionnalités s'y retrouvent, mais il y a certainement des moments où elles existent.


Une question à se poser, cependant, est: si les types n'ont rien en commun, pourquoi sont-ils détenus dans la même collection? Il n'y a aucune opération que vous pouvez effectuer sur tous les éléments de la collection, ce qui va à l'encontre du but d'avoir une telle collection. Maintenant, il peut y avoir des opérations artificielles que vous voudrez peut-être ajouter aux deux pour simplifier la logique - par exemple, implémenter le modèle de conception de visiteur - à quel point cela pourrait redevenir utile, mais maintenant elles ont quelque chose en commun ...
Jules
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.