Polymorphisme
Tant que vous utilisez getType()
ou quelque chose du genre, vous n'utilisez pas le polymorphisme.
Je comprends que vous ayez besoin de savoir quel type vous avez. Mais tout travail que vous voudriez faire en sachant que cela devrait vraiment être poussé dans la classe. Ensuite, vous lui dites simplement quand le faire.
Le code procédural obtient des informations puis prend des décisions. Le code orienté objet dit aux objets de faire des choses.
- Alec Sharp
Ce principe s'appelle dire, ne demandez pas . Le suivre vous aide à ne pas diffuser de détails comme le type et à créer une logique qui agit sur eux. Faire cela transforme une classe à l'envers. Il est préférable de conserver ce comportement dans la classe afin qu'il puisse changer lorsque la classe change.
Encapsulation
Vous pouvez me dire qu'aucune autre forme ne sera jamais nécessaire, mais je ne vous crois pas et vous non plus.
Un bon effet de l'encapsulation suivante est qu'il est facile d'ajouter de nouveaux types car leurs détails ne sont pas répartis dans le code où ils apparaissent if
et dans la switch
logique. Le code d'un nouveau type doit tous être au même endroit.
Un système de détection de collision de type ignorant
Permettez-moi de vous montrer comment je concevrais un système de détection de collision performant et fonctionnant avec n'importe quelle forme 2D sans se soucier du type.
Dis que tu étais censé dessiner ça. Semble simple. Ce sont tous des cercles. Il est tentant de créer une classe de cercle qui comprend les collisions. Le problème est que cela nous renvoie à une ligne de pensée qui s'effondre lorsque nous avons besoin de 1000 cercles.
Nous ne devrions pas penser aux cercles. Nous devrions penser aux pixels.
Et si je vous disais que le même code que vous utilisez pour dessiner ces gars-là est ce que vous pouvez utiliser pour détecter quand ils se touchent ou même ceux sur lesquels l'utilisateur clique.
Ici, j'ai dessiné chaque cercle avec une couleur unique (si vos yeux sont assez bons pour voir le contour noir, ignorez simplement cela). Cela signifie que chaque pixel de cette image cachée correspond à ce qui l'a attiré. Un hashmap s'en occupe bien. Vous pouvez réellement faire du polymorphisme de cette façon.
Cette image que vous n'avez jamais à montrer à l'utilisateur. Vous le créez avec le même code qui a dessiné le premier. Juste avec des couleurs différentes.
Lorsque l'utilisateur clique sur un cercle, je sais exactement quel cercle car un seul cercle est de cette couleur.
Lorsque je dessine un cercle au-dessus d'un autre, je peux rapidement lire chaque pixel que je suis sur le point d'écraser en les déversant dans un ensemble. Lorsque j'ai terminé les points de consigne pour chaque cercle avec lequel il est entré en collision, je n'ai plus qu'à les appeler une fois pour l'avertir de la collision.
Un nouveau type: les rectangles
Tout cela a été fait avec des cercles mais je vous demande: cela fonctionnerait-il différemment avec des rectangles?
Aucune connaissance du cercle ne s'est infiltrée dans le système de détection. Peu importe le rayon, la circonférence ou le point central. Il se soucie des pixels et des couleurs.
La seule partie de ce système de collision qui doit être enfoncée dans les formes individuelles est une couleur unique. En dehors de cela, les formes peuvent simplement penser à dessiner leurs formes. C'est ce qu'ils sont bons de toute façon.
Maintenant, lorsque vous écrivez la logique de collision, vous ne vous souciez pas du sous-type que vous avez. Vous lui dites d'entrer en collision et il vous indique ce qu'il a trouvé sous la forme qu'il prétend dessiner. Pas besoin de connaître le type. Et cela signifie que vous pouvez ajouter autant de sous-types que vous le souhaitez sans avoir à mettre à jour le code dans d'autres classes.
Choix d'implémentation
Vraiment, il n'a pas besoin d'être d'une couleur unique. Il pourrait s'agir de références d'objets réelles et enregistrer un niveau d'indirection. Mais ceux-ci ne seraient pas aussi beaux lorsqu'ils sont dessinés dans cette réponse.
Ceci n'est qu'un exemple d'implémentation. Il y en a certainement d'autres. Ce que cela était censé montrer, c'est que plus vous laissez ces sous-types de formes s'en tenir à leur seule responsabilité, mieux tout le système fonctionne. Il existe probablement des solutions plus rapides et moins gourmandes en mémoire, mais si elles m'obligent à diffuser les connaissances sur les sous-types, je serais réticent à les utiliser même avec les gains de performances. Je ne les utiliserais que si j'en avais clairement besoin.
Double expédition
Jusqu'à présent, j'ai complètement ignoré la double expédition . Je l'ai fait parce que je le pouvais. Tant que la logique de collision ne se soucie pas des deux types en collision, vous n'en avez pas besoin. Si vous n'en avez pas besoin, ne l'utilisez pas. Si vous pensez que vous pourriez en avoir besoin, remettez-le à plus tard. Cette attitude s'appelle YAGNI .
Si vous décidez que vous avez vraiment besoin de différents types de collisions, demandez-vous si n sous-types de forme ont vraiment besoin de n 2 types de collisions. Jusqu'à présent, j'ai travaillé très dur pour faciliter l'ajout d'un autre sous-type de forme. Je ne veux pas le gâcher avec une implémentation à double répartition qui oblige les cercles à savoir que des carrés existent.
Combien de types de collisions y a-t-il de toute façon? Un peu de spéculation (une chose dangereuse) invente des collisions élastiques (rebondissantes), inélastiques (collantes), énergétiques (explosives) et destructrices (damageuses). Il pourrait y en avoir plus mais si celui-ci est inférieur à n 2, il ne faut pas trop concevoir nos collisions.
Cela signifie que lorsque ma torpille frappe quelque chose qui accepte des dégâts, elle n'a pas à SAVOIR qu'elle a touché un vaisseau spatial. Il suffit de lui dire: "Ha ha! Vous avez subi 5 points de dégâts."
Les objets qui infligent des dégâts envoient des messages de dégâts aux objets qui acceptent les messages de dégâts. De cette façon, vous pouvez ajouter de nouvelles formes sans en parler aux autres formes. Vous finissez par vous propager autour de nouveaux types de collisions.
Le vaisseau spatial peut renvoyer à la torp "Ha ha! Vous avez pris 100 points de dégâts." ainsi que "Vous êtes maintenant collé à ma coque". Et le torp peut renvoyer "Eh bien, j'ai fini pour ainsi m'oublier".
À aucun moment ne sait exactement ce que chacun est. Ils savent juste se parler via une interface de collision.
Maintenant, bien sûr, la double répartition vous permet de contrôler les choses plus intimement que cela, mais voulez-vous vraiment cela ?
Si vous le faites, pensez au moins à faire une double répartition par le biais d'abstractions des types de collisions qu'une forme accepte et non sur la mise en œuvre réelle de la forme. En outre, le comportement de collision est quelque chose que vous pouvez injecter en tant que dépendance et déléguer à cette dépendance.
Performance
La performance est toujours critique. Mais cela ne signifie pas que c'est toujours un problème. Testez les performances. Ne vous contentez pas de spéculer. Sacrifier tout le reste au nom de la performance ne conduit généralement pas à un code de perforation de toute façon.