Después de llevar tiempo corrigiendo varios exámenes y ver que muchos alumnos tiene problemas a la hora de estructurar el código, me pareció  pertinente subir algo de información acerca de una de las técnicas de programación más usadas. Por supuesto, hay muchas y para muchos casos diferentes. Lo más importante no es implementar todas, sino aquellas necesarias para resolver los problemas que enfrentamos a la hora de crear una aplicación/videojuego.

 

112211_1039_principiode11

 

Es un concepto introducido por Robert C. Martin. Son cinco principios útiles para el desarrollo de software. Vamos a desglosarlo en partes, explicando cada una de ellas y tratando de dar ejemplos en cada etapa.

 

 

SRP – Single Responsability Principle (Responsabilidad única):
(que puedas, no significa que debas)

Muchas veces a la hora de programar distintas clases (scripts), siempre nos vemos en la “necesidad” de crear un montón de funciones que no tienen nada que ver con el script solamente porque nos quedan más a mano. El problema de esto es que así llenamos nuestras clases de funcionalidades que no sólo no necesitan realmente, sino que la hacen más tediosa de entender y mucho menos reutilizable. Ni hablar que después hacemos cambios en nuestras aplicaciones/juegos y todo empieza a explotar por todos lados. De lo que se trata esta parte es, como dice su nombre, “responsabilidad única“. O sea que cada script haga una única tarea, y no me refiero con esto a que tenga únicamente una función, sino una única funcionalidad.

Un ejemplo claro de esto es cuando creamos una clase llamada “Character“. Comúnmente acá pondríamos funciones básicas como “Run()”, “Jump()”, “Shoot()”, “Spawn()”, etc. El problema es cuando necesitamos que nuestro character reaccione a las teclas. La problemática acá está en la mentalidad de creer que el character es el que DEBE detectar las teclas que presiona el usuario. Es una problemática porque mientras más cosas haga (funcionalidades tenga), nuestro script, menos reutilizable es. ¿Por qué? Fácil… Si nuestro character además de moverse también detecta las teclas, entonces cuando tengamos que hacer enemigos u otro  player para que juegue, vamos a tener que empezar a agregar muchos condicionales (bloques “if”), para que nuestro character de golpe compruebe si es un enemigo, un segundo luchador, o un NPC que da vueltas por ahí pueda llevar a cabo su cometido. Ni hablar de que muchas veces hasta podríamos llegar a considerar necesaria una locura como sería crear otro script llamado “CharacterEnemy”, que tiene un copy & paste del otro script sólo que cambian dos o tres líneas (y así vamos agregando characters hasta agotar las posibilidades). Lo que pasa acá es que lo que empezó siendo algo que parecía necesario y/o fácil, se convierte en un problema cuando el juego empieza a crecer y vamos notando que necesitamos hacer copias y copias de bloques de código.

Tomando como ejemplo a nuestro script “Character” vamos a imaginarnos que representa a una especie de Frankenstein sin cerebro. Frankenstein tiene una “programación” que le dice “CÓMO” moverse, “CÓMO” interactuar, “CÓMO” sentarse, “CÓMO” lavar el coche, “CÓMO” matar a tu suegra, etc. Sería más o menos así:

 

u7ez1qa1

 

Bueno… Si bien él está programado para poder realizar estas tareas, no las hace porque  no tiene un “cerebro” que le indique cuándo hacerlo. De hecho, es justamente ese cerebro el “encargado” de decirle cuándo hacer cada cosa. Con esto, yo podría crear un montón de copias de este Frankenstein y ponerle cerebros diferentes, lo cuál haría que haga cosas diferentes. Un punto importante de esto es que la programación es LA MISMA, sólo que está copiada en diferentes Frankensteins. Cuando yo creo el script “FrankensteinBrain”, le voy a dar instrucciones a este de cómo interactuar con Frankenstein y manejarlo a su antojo. Como ven, acá tengo DOS scripts y DOS funcionalidades diferentes. Es más, por comodidad, podría hacer un “FrankensteinBrainPlayer” (que detecte la entrada del usuario), y uno llamado “FrankensteinIA” que represente una inteligencia artificial.

Utilizando este método me aseguro que sea reutilizable y que tenga una funcionalidad única. Otra ventaja de esto es que si yo veo en el juego que falla algo, sólo debería preguntarme “¿qué parte está fallando?“. Si la respuesta es “la caminata del personaje”, ¿a qué script voy a ir? Sin duda, al script encargado del movimiento. Con esto también agilizamos mucho el tiempo de respuesta ante la búsqueda de nuevos errores, ya que los tenemos más reconocibles. Concretamente las preguntas serían:

¿Falla? ===================> SÍ

¿Dónde? ==================> AL HACER TAL COSA

¿Dónde hago TAL COSA? =====> EN EL SCRIPT TANTO. 

 

 

OCP – Open-Closed Principle (Abierto-Cerrado):
(abierto para su extensión, cerrado para su modificación)

Siempre que nos pongamos a investigar respecto de esta parte, vamos a ver que va acompañada de la frase de su “dueño”:

Una entidad debe estar abierta a extensiones pero cerrada a modificaciones.
Bertrand Meyer
Bertrand Meyer

Esta frase es casi como “un gran poder, conlleva una gran responsabilidad” del queridísimo Benjamin Parker (tío de Spider-Man), y podríamos decir que tiene básicamente el mismo peso.

Se refiere a que todos los scripts que nosotros hagamos, deberían ser lo suficientemente abiertos como para que podamos generar nuevos comportamientos, ya sea sobreescribiendo funciones, heredando o utilizando interfaces; y lo suficientemente cerrado como para que al hacerlo no tengamos que tocar el código de dicho script.

Un ejemplo de esto podría ser la clase “MonoBehaviour” de Unity3D. Dicha clase está cerrada a toda modificación, pero se puede extender e implementar cada uno de sus métodos (funciones). Todo el proceso de inicialización y demás está programado en la clase, nosotros sólo la heredamos para extender su funcionalidad.

La herencia en sí misma es el ejemplo más común para este tipo de principios. Veamos otro ejemplo:

diagram-flow-classes

 

Cada uno utiliza las funcionalidades programadas en la superclase (clase padre de la cual hereda), sin necesidad de que el código de cada clase deba ser modificado explícitamente por el programador.

Otra de las cosas que se hace es crear una Clase Abstracta, bloqueando así incluso el uso de dicha clase y sus funcionalidades a menos que sea heredada. También se usa el hecho de hacer abstractas sus funciones, obligando a las subclases a escribir los cuerpos de las funciones. Este comportamiento es similar al uso de Interfaces (Programación).

 

 

LSP – Liskov Substitution Principle (Substitución de Liskov):

Principio introducido por Barbara Liskov en 1987 (año en que nací yo, algo tiene que significar :P). Este principio se relaciona directamente con el anterior, y dice:

 

Si una función recibe un objeto como parámetro, de tipo X y en su lugar le pasamos otro de tipo Y, que hereda de X, dicha función debe proceder correctamente. Este principio está muy ligado al anterior.
Barbara Liskov
Barbara Liskov

 
En la gran mayoría de los casos, cuando se aprende a programar, este principio se incorpora casi instantáneamente sin darse cuenta.

Este principio está muy ligado también a la metodología Diseño por Contrato, ya que cumple lo siguiente:

 

  • Las precondiciones no pueden ser reforzadas por un subtipo.
  • Las postcondiciones no pueden ser debilitadas por un subtipo.
  • Las invariantes establecidas por el supertipo deben ser mantenidas por los subtipos.

 

La utilización de este principio es una muy buena práctica de programación, aunque se use abstraído del resto.

 

 

ISP – Interface Segregation Principle (Segregación de Interfaces):

Robert C. Martin dice con este principio que todas las clases (o sistemas), sólo deberían conocer de sus padres (o de las interfaces que implementan), únicamente las funciones que necesitan. Esta es una forma de abstraerse de cada parte de los sistemas que depende y así poder modificarse de una manera más sencilla. Para evitar esto habría que hacer en las superclases no estén dotadas de un sin fin de funciones que las subclases no necesitan. Miren el siguiente ejemplo:

 

flowdiagramisp

 

Si bien Item y PowerUp son objetos agarrables, podemos ver que de todo lo que tiene ObjetoAgarrable no todo en intrínsecamente necesario en ambos. La variable cosasQueDa podría estar sólamente declarada en PowerUp, al igual que la función “Caer()“. La función “ChequearColision()” podría estar solamente creada en el héroe, y en ninguna de esas tres clases. Si lo hacemos de la manera que muestra el diagrama, vamos a forzar a que todo lo que sea un ObjetoAgarrable tenga todas esas cosas, aunque su única funcionalidad sea ser agarrado para pasar de nivel o ser lanzado (como cuando se agarra algo del suelo para tirarlo). O sea, si tuviéramos una clase Hacha, para representar un hacha (you don’t say…), la cual puede ser arrojada, la misma no nos estaría dando nada, ni necesitaríamos una función para hacerla caer.

 

 

DIP – Dependency Inversion Principle (Inversión de Dependencia):

Para este principio Robert C. Martin nos dice que una clase A no debe depender explícitamente de una clase B, sino de una abstracción de B. Este problema se presenta cuando hacemos una clase que contiene una definición de otra dentro de sí. El problema es que si la clase A contiene una instancia de la clase B, y luego tenemos otras clases que hacen algo similar a B, para cambiarlo vamos a tener que tocar el código dentro de la clase A. Hacer esto hace que se pierda versatilidad ya que hay más clases que debemos modificar al momento de agregar funcionalidad. Esto también provoca un efecto de Bola de Nieve, lo que significa que mientras más cosas agreguemos a nuestro proyecto, más cosas vamos a tener que modificar para agregarlas. Veamos el siguiente diagrama de flujo para ver la forma incorrecta y la correcta respectivamente:

 

captura-de-pantalla-2016-10-17-00-49-47

 

La estructura de arriba hace que si luego quiero tener una clase llamada “Escopeta” tenga que modificar también la clase “Heroe“. La solución de abajo hace que si luego quiero dotar a nuestro heroe de más armas, solamente las creo y las hago heredar de “Arma“. Hacerlo de esta manera hace que agregar características nuevas a nuestros proyectos sea mucho más fácil y principalmente versátil. Una práctica muy común dentro de la puesta en práctica de estos principios es el uso de LinkQ. En algún momento más adelante voy a subir un tutorial al respecto. Mientras tanto, si quieren echarle un vistazo, pueden ver este link:

https://msdn.microsoft.com/en-us/library/bb397933.aspx

Eso es todo sobre los principios que forman S.O.L.I.D.

Y con todo esto nada puede malir sal!

Saludos.