Conociendo C# Pt. 4: Clases abstractas y herencia

Cerrando la temática de Conociendo C# tenemos el concepto de herencia, declaración de clases abstractas y como envolver todo esto. Cuando empecé a programar en Unity, una de las cosas que me intrigaba mas era; ¿cómo se comportaba la herencia en un sistema de scripts? Honestamente hay que reconocer que lo implementaron de una manera muy intuitiva, por ende, para el programador ya experimentado, la herencia fluye como es de esperarse.

Clases Abstractas

Al definir una clase podemos declarar que esta es abstracta. Una clase abstracta no puede ser instanciada, sirve para hacer de tipo base para otras clases. Sin embargo, puede tener constructores declarados con el mismo propósito. En este ejemplo voy a referirme al Marauders Arena, todas las torretas del juego se comportan de una manera similar (apuntan, disparan y recargan), las diferencias entre ellas pasan por si es una torreta balística donde aplica la mecánica de hitscan, o si dispara un proyectil físico como sería el harpon, o si es un arma de tipo rociador, como es el lanzallamas. Por ende el mejor patrón para aplicar en este caso es tener una clase abstracta padre lo mas genérica posible y los tipos específicos que sean sus hijos.

turret controller base
Declaración de una clase abstracta, que además hereda de MonoBehaviour y permite ser levantada por unity como Script

Herencia

El concepto de herencia es parecido al concepto de implementación de interfaz, excepto que en la herencia juegan variantes. Por ejemplo, todo lo definido en una interfaz va a tener que ser implementado en la clase, pero en la herencia, podemos designar cuales miembros se heredan y cuales no y podemos definir si hay miembros que pueden ser alterados por los hijos. Usando los modificadores de acceso podemos determinar estas cosas, todo lo que sea private va a ser solo accesible desde la clase misma, todo lo que sea protected va a ser accesible desde la clase y sus hijos y todo lo que sea public va a ser accedido desde cualquier lado. Usando el modificador virtual, podemos marcar el miembro (solo métodos y propiedades) para que sus hijos puedan hacerle una reescritura parcial o total usando el modificador override.

ballistic
Definición de un hijo de TurretControllerBase, a su vez, TurretControllerBase hereda de MonoBehaviour, así que BallisticTurretController va a poder ser importado en Unity como script.

Tenemos nuestro padre y a su hijo, ¿cómo funciona la dinámica de reescritura? Consideremos el ejemplo, sabemos que todas las torretas van a disparar, pero al hacerlo se van a comportar diferente, algunas van a instanciar un proyectil, otras van a trazar un rayo y buscar la colisión en el mismo FixedUpdate. Para esto, definimos un método virtual Shoot() en el padre, en este método vamos a implementar toda funcionalidad que sea común para todas las torretas. Por ejemplo, sabemos que cada disparo va a reducir en uno la cantidad de munición, sabemos que hay que reiniciar el timer que controla el ritmo de fuego, sabemos que el barril actual va a disparar la animación adecuada y que tenemos que pasar al siguiente barril en caso que haya más de uno.

virtual Shoot
Virtual Shoot es una función con implementación que va a aplicar para todas las torretas.

Una vez que tenemos esto, podemos ir a los hijos e implementar la reescritura del método Shoot()

override Shoot
En la sobre escritura del método Shoot, tenemos el comportamiento especifico de la torreta de proyectil, y al final llama al base.Shoot() que ejecuta el método del padre. En este caso el llamado va al final porque el código base contiene el reseteo de timers y cambio de barriles.

Siguiendo la misma lógica, también tenemos métodos que existen en el padre que no son virtual porque no necesitan ser sobre escritos, por ejemplo, el método de recargar.

finishReload
El método que se ejecuta una vez que el tiempo de recarga termino es el mismo para todas las torretas, no requiere modificación especifica alguna.

Polimorfismo

Así como con interfaces, es posible hacer polimorfismo entre padres e hijos. Funciona exactamente igual con la diferencia que Unity si expone las variables de clase en el editor, de esta manera nos permite asignar cualquier hijo en un lugar donde espera al padre.

 

public abstract
Podemos definir una variable publica como tipo de clase abstracta con la intención de recibir referencias de un hijo.
editor1
En el editor lo vamos a ver así:

2017-08-24 02_23_41-MaraudersDev - Microsoft Visual Studio

ballistic
Sin embargo, por el polimorfismo, podemos asignar una referencia a cualquier objeto que tenga un script que herede de TurretControllerBase

Por último, ¿porque la clase base es abstract? ¿no podría haber sido instanciable? Sin dudas, pero realmente ¿queremos tener la opción de levantar un script de esa clase genérica? Al definir la clase como abstracta, por más que esta herede de MonoBehaviour, no va a poder ser asignada como script en Unity. Esto nos da la posibilidad de asegurarnos que no haya equivocaciones a la hora de asignar comportamientos concretos.

Con herencia podemos reusar mucho código, multiplicar la escalabilidad, mejorar la mantenibilidad y diseñar patrones de objetos que fácilmente podemos llevar a otros proyectos futuros. Este ejemplo puede ser usado en cualquier otro juego con torretas, sin importar que sean torretas controladas por IA o por el jugador, y sin importar si el input es teclado, mouse o gamepad.

Conclusión

Como para cerrar el tema, me gustaría poner este ejemplo. Digamos que en nuestro juego tenemos sillas, sillones, un muñeco de trapo y la posibilidad de prender fuego las cosas.

Nuestro esquema de clases podría ser algo así:

Clases abstractas: Mueble
Clases instanciables: Silla, Sillon, Muñeco
Interfaces: IFlamable

Muñeco y Mueble implementan la interfaz IFlamable, Silla y Sillón heredan de Mueble. La herencia ya hace que ambas clases implementen el comportamiento de ser inflamable.

Con este diagrama de clases podemos asegurarnos que todo lo que sea inflamable se prenda fuego como queremos, y todo lo que sea un mueble se comporte como mueble sin importar cuantos hijos mas podamos tener.

 

 

Un comentario sobre "Conociendo C# Pt. 4: Clases abstractas y herencia"

Deja un comentario