Polimorfismo
Mientras lo uses getType()
o algo así, no estás usando polimorfismo.
Entiendo sentir que necesitas saber qué tipo tienes. Pero cualquier trabajo que desee hacer sabiendo que realmente debe ser empujado a la clase. Entonces solo le dices cuándo hacerlo.
El código de procedimiento obtiene información y luego toma decisiones. El código orientado a objetos le dice a los objetos que hagan cosas.
- Alec Sharp
Este principio se llama decir, no preguntar . Seguirlo le ayuda a no difundir detalles como escribir y crear una lógica que actúe sobre ellos. Hacer eso convierte una clase de adentro hacia afuera. Es mejor mantener ese comportamiento dentro de la clase para que pueda cambiar cuando la clase cambie.
Encapsulación
Puedes decirme que nunca se necesitarán otras formas, pero no te creo y tampoco deberías.
Un buen efecto de seguir la encapsulación es que es fácil agregar nuevos tipos porque sus detalles no se extienden en el código donde aparecen if
y en la switch
lógica. El código para un nuevo tipo debería estar en un solo lugar.
Un sistema de detección de colisión tipo ignorante
Permítame mostrarle cómo diseñaría un sistema de detección de colisión que sea eficaz y que funcione con cualquier forma 2D al no importarme el tipo.
Digamos que se supone que debes dibujar eso. Parece simple Es todo círculos. Es tentador crear una clase circular que entienda las colisiones. El problema es que esto nos envía a una línea de pensamiento que se desmorona cuando necesitamos 1000 círculos.
No deberíamos estar pensando en círculos. Deberíamos estar pensando en píxeles.
¿Qué pasaría si te dijera que el mismo código que usas para dibujar a estos tipos es el que puedes usar para detectar cuándo se tocan o incluso en cuáles está haciendo clic el usuario?
Aquí he dibujado cada círculo con un color único (si tus ojos son lo suficientemente buenos como para ver el contorno negro, simplemente ignóralo). Esto significa que cada píxel en esta imagen oculta se asigna de nuevo a lo que lo dibujó. Un hashmap se encarga de eso muy bien. De hecho, puedes hacer polimorfismo de esta manera.
Esta imagen nunca tiene que mostrarla al usuario. Lo creas con el mismo código que dibujó el primero. Solo con diferentes colores.
Cuando el usuario hace clic en un círculo, sé exactamente qué círculo porque solo un círculo es de ese color.
Cuando dibujo un círculo encima de otro, puedo leer rápidamente cada píxel que estoy a punto de sobrescribir al volcarlos en un conjunto. Cuando termine los puntos de ajuste para cada círculo con el que colisionó y ahora solo tengo que llamar a cada uno una vez para notificarle la colisión.
Un nuevo tipo: rectángulos
Todo esto se hizo con círculos, pero le pregunto: ¿funcionaría de manera diferente con los rectángulos?
Ningún conocimiento circular se ha filtrado en el sistema de detección. No le importa el radio, la circunferencia o el punto central. Se preocupa por los píxeles y el color.
La única parte de este sistema de colisión que debe empujarse hacia las formas individuales es un color único. Aparte de eso, las formas solo pueden pensar en dibujar sus formas. Es lo que son buenos de todos modos.
Ahora, cuando escribes la lógica de colisión, no te importa qué subtipo tengas. Le dices que choque y te dice lo que encontró debajo de la forma que pretende dibujar. No es necesario saber tipo. Y eso significa que puede agregar tantos subtipos como desee sin tener que actualizar el código en otras clases.
Opciones de implementación
Realmente, no necesita ser un color único. Podrían ser referencias de objetos reales y guardar un nivel de indirección. Pero esos no se verían tan bien cuando se dibuja en esta respuesta.
Este es solo un ejemplo de implementación. Ciertamente hay otros. Lo que esto debía mostrar es que cuanto más dejes que estos subtipos de formas se adhieran a su única responsabilidad, mejor funcionará todo el sistema. Es probable que haya soluciones más rápidas y menos intensivas en memoria, pero si me obligan a difundir el conocimiento de los subtipos, no me gustaría usarlas incluso con las ganancias de rendimiento. No los usaría a menos que los necesitara claramente.
Despacho doble
Hasta ahora he ignorado por completo el doble envío . Lo hice porque pude. Mientras la lógica de colisión no le importe qué dos tipos colisionaron, no la necesita. Si no lo necesita, no lo use. Si cree que podría necesitarlo, posponga tratarlo todo el tiempo que pueda. Esta actitud se llama YAGNI .
Si decides que realmente necesitas diferentes tipos de colisiones, pregúntate si n subtipos de formas realmente necesitan n 2 tipos de colisiones. Hasta ahora he trabajado muy duro para que sea fácil agregar otro subtipo de forma. No quiero estropearlo con una implementación de doble despacho que obliga a los círculos a saber que existen cuadrados.
¿Cuántos tipos de colisiones hay de todos modos? Un poco de especulación (algo peligroso) inventa colisiones elásticas (hinchables), inelásticas (pegajosas), enérgicas (explosivas) y destructivas (dañinas). Podría haber más, pero si es menor que n 2, no sobrediseñemos nuestras colisiones.
Esto significa que cuando mi torpedo golpea algo que acepta daño, no tiene que SABER que golpeó una nave espacial. Solo tiene que decirlo: "¡Ja, ja! Recibiste 5 puntos de daño".
Las cosas que causan daño envían mensajes de daño a las cosas que aceptan mensajes de daño. Hecho de esa manera, puede agregar nuevas formas sin decirle a las otras formas sobre la nueva forma. Solo terminas extendiéndote alrededor de nuevos tipos de colisiones.
La nave espacial puede enviar de vuelta al torp "¡Ja, ja! Recibiste 100 puntos de daño". así como "Ahora estás atascado en mi casco". Y el torp puede devolver "Bueno, ya terminé, así que olvídate de mí".
En ningún momento tampoco sabe exactamente qué es cada uno. Simplemente saben cómo comunicarse entre sí a través de una interfaz de colisión.
Ahora claro, el despacho doble te permite controlar las cosas más íntimamente que esto, pero ¿realmente quieres eso ?
Si lo hace, al menos piense en hacer un despacho doble a través de abstracciones de qué tipo de colisiones acepta una forma y no en la implementación real de la forma. Además, el comportamiento de colisión es algo que puede inyectarse como una dependencia y delegar a esa dependencia.
Actuación
El rendimiento siempre es crítico. Pero eso no significa que siempre sea un problema. Prueba de rendimiento. No solo especules. Sacrificar todo lo demás en nombre del rendimiento generalmente no conduce a un código de rendimiento de todos modos.