25 mayo 2010

Inyección de Dependencias

En los comienzos de la programación, los programas eran lineales. El flujo de ejecución era simple y predecible, ejecutándose línea tras línea.
Aparecieron dos conceptos para estructurar el código: la modularidad y la reutilización de los componentes: se crean bibliotecas de componentes reutilizables. El flujo se complica, saltando de componente a componente, y aparece un nuevo problema: la dependencia (acoplamiento) entre las clases.
El problema de la dependencia se empieza a considerar lo suficientemente importante como para definir nuevos conceptos en el diseño.

Inyección de Dependencias (en inglés Dependency Injection, DI).

Originalmete, Inyección de Dependencias era referido por el nombre de Inversión de Control pero en un articulo escrito a inicios del 2004, Martin Fowler pregunto que aspectos de control son invertidos. Martin Fowler concluyo que la adquisición de dependecias era lo que se invertía. Basado en este hecho el definió que la frase "Inyección de Dependencias" sería un mejor termino utilizado de aquí en adelante.

Cualquier aplicación no trivial esta compuesta de dos o mas clases que colaboran para lograr un objetivo de la lógica del negocio. Tradicionalmente cada objeto es responsable de obtener sus propias referencias de los objetos que colaboran con él. Esto puedo llevarnos a acoplamientos rígidos y código difícil de probar.

Cuando aplicamos Inyección de Dependencias a los objetos le son proporcionadas sus dependencias en el tiempo de su creación por una entidad externa que coordina cada objeto en el sistema. En otras palabras, las dependencias son inyectadas en los objetos. En este sentido la Inyección de Dependencias significa una inversión de responsabilidad con respecto de como el objeto obtiene las referencias  de los objetos con los que colabora.

El principal beneficio de la Inyección de Dependencias es el bajo acoplamiento. Si un objeto solo conoce sus dependencias por una interface (no sus implementaciones o como están instanciadas) entonces las dependencias pueden intercambiarse con una implementación diferente sin que los objetos dependientes conozca sus diferencias.

Por ejemplo sin una clase Banco solo conoce a su clase dependiente Cliente a través de su interface entonces la implementación actual de Cliente no tiene importancia para la clase Banco. Cliente podría ser un Pojo, un Webservice, un Ejb, o un objeto Mock para una prueba unitaria. como comentamos la clase Banco no le interesa saber de que tipo es la implementación de la clase Cliente.


En cierto modo es una implementación del Principio de Hollywood, una metodología de diseño de software, cuyo nombre proviene de las típicas respuestas que se les dan a los actores amateurs en las audiciones que tienen lugar en la meca del cine: no nos llames; nosotros te llamaremos.

24 mayo 2010

Sustitución de Liskov

Principio de Sustitución de Liskov (The Liskov Substitution Principle, LSP)

Clases derivadas deben ser sustituibles por sus clases bases. Este principio trata acerca de aplicar la herencia correctamente para lograr un buen diseño. Cuando se hereda de una clase base, debemos ser capaces de sustituir las subclases por la clase base sin que suceda ningún tipo de problema o errores, de otro modo podemos asegurar que la herencia se esta aplicando incorrectamente.

Otras opciones que tenemos para relacionar clases son la Delegación, Composición y Agregación.

Delegación.
Es cuando transferimos la responsabilidad o tarea de hacer algo a otra clase o método. Si necesitamos usar la responsabilidad de una clase, pero no queremos cambiar esa responsabilidad podemos considerar la delegación en lugar de la herencia.

La Delegación nos permite que nuestra aplicación sea débilmente acoplada, es decir que un objeto de cara al exterior expresa cierto comportamiento pero en realidad delega la responsabilidad de implementar dicho comportamiento a un objeto asociado en una relación inversa de responsabilidad.

Composición.
Se utiliza para expresar que un par de objetos tienen una relación de dependencia para llevar a cabo su función, de modo que uno de los objetos involucrados está compuesto por el otro. De manera práctica, es posible reconocer asociatividad entre dos objetos A y B si la proposición "A tiene un B" es verdadera. Por ejemplo: "una computadora tiene un disco duro" es verdadero, por tanto un objeto computadora tiene una relación de composición con al menos un objeto disco duro.

La composición es más poderosa cuando se usa un comportamiento definido en una interface, entonces se escoge de una variedad de implementaciones de esa interface permitiéndonos cambiar el comportamiento a tiempo de ejecución.

Una característica importante de la composición es que el objeto compuesto del comportamiento de otros objetos maneja el ciclo de vida de sus objetos compuestos, es decir, cuando el objeto compuesto es destruido también son destruidos todos los objetos que lo componen, los objetos que componen al objeto compuesto no existen fuera de este objeto.

Agregación.
Que sucede cuando queremos todos los beneficios de la composición, flexibilidad al cambiar el comportamiento a tiempo de ejecución, pero necesitamos que los objetos existan fuera del objeto principal, la respuesta es utilizar la agregación.
La agregación es cuando una clase es usada como parte de otra clase, pero puede existir si la clase que la contiene deja de existir.

Veamos un ejemplo de Delegación, Composición y Agregación:


  • Un Almacén posee Clientes y Cuentas (los rombos van en el objeto que posee las referencias).
  • Cuando se destruye el Objeto Almacén también son destruidos los objetos Cuenta asociados, en cambio no son afectados los objetos Cliente asociados.
  • La composición se destaca por un rombo relleno.
  • La agregación se destaca por un rombo transparente.  
  • La flecha en este tipo de relación indica la navegabilidad del objeto referenciado. Cuando no existe este tipo de particularidad la flecha se elimina. 
  • La clase Cliente delega a la clase Banco el pago.
Debemos tener mucho cuidado en nuestros diseños al querer utilizar la herencia y decidir si la Delegación, Composición y Agregación son mejores alternativas que la herencia, ya que nuestros software será mas flexible, fácil de mantener, extender y reusar.

18 mayo 2010

Responsabilidad Única

Principio de Responsabilidad Única (The Single Responsibility Principle, SRP)

"Cada objeto en el sistema deben tener una simple responsabilidad, y todos los servicios de los objetos deben cumplir con esa simple responsabilidad"

Una clase debería tener solo una razón para cambiar, este principio tiene que ver con responsabilidades, cada objeto en un diseño tiene que enfocarse en solo una responsabilidad y si esa responsabilidad cambia, sabremos donde exactamente tenemos que hacer el cambio en nuestro código.

Este principio será implementado correctamente cuando cada uno de nuestras clases tengan solo una razón para cambiar, en aplicaciones bien diseñadas una clase tiene solo una responsabilidad, hace esta responsabilidad correctamente y  no permite que otras clases compartan ese comportamiento.

Cohesión es otro termino para el principio SRP, si nuestra aplicación es altamente Cohesiva esto significa que estamos aplicando correctamente este principio.

Una forma para probar este principio es escribir en un papel algo parecido a la siguiente imagen, en la primera línea se escribe el nombre de la clase, en las siguientes líneas escribiremos el nombre de la clase y los métodos de las clase, ocuparemos tantas lineas como métodos tengamos en la clase, luego leeremos cada línea y analizaremos si la frase tiene sentido y si la clase tiene la responsabilidad del método descrito.


Veamos un ejemplo con la siguiente clase:



Veamos el resultado de aplicar el análisis en base al documento que se definió.



Aclarando que el método obtenerAceite() se refiere solo a una lectura de aceite ó con una posibilidad de asignar esta responsabilidad a otra clase.

No repetir

Principio No te repitas (Don't Repeat Yourself, DRY).

"Evitar código repetido abstrayendo las funcionalidades que son comunes y colocarlas en un solo lugar"

Este principio es en realidad colocar los requerimientos repetidos en un solo lugar, abstrayendo el código duplicado es un buen inicio para usar este principio, cuando se desea evitar código duplicado en realidad se esta asegurando que solo se implementa cada requerimiento de la aplicación una sola vez.

Cuando el principio DRY se aplica de forma eficiente los cambios en cualquier parte del proceso requieren cambios en un único lugar. Por el contrario, si algunas partes del proceso están repetidas por varios sitios, los cambios pueden provocar fallos con mayor facilidad si todos los sitios en los que aparece no se encuentran en un solo lugar

17 mayo 2010

Abierto-Cerrado

Principio Abierto-Cerrado (The Open-Closed Principle, OCP).

"Clases deberían estar abiertas para extensión, pero cerradas para modificación"

La meta es permitir que nuestras clases sean fácilmente extendibles para incorporar nuevo comportamiento sin modificar el código existente, esto lo logramos cuando nuestros diseños son capaces de adaptarse al cambio y son lo suficientemente flexibles para añadir nueva funcionalidad sin permitir modificar el código existente.

Una vez que se tiene una clase que trabaja correctamente y esta siendo usada, en realidad no se desea hacer cambios a esta clase, pero hay que recordar que el Cambio es una gran constante en el desarrollo de software, con el principio OCP permitimos el cambio a través de la extensión, en lugar que modificar nuestro código existente. Subclases pueden agregar y extender el comportamiento de nuestra clase base, sin modificar el comportamiento que actualmente esta funcionando de nuestra clase base, no siempre este principio aplica solo a la herencia, si tu tienes varios métodos privados en una clase, estos estarán cerrados para la modificación, no permitiendo que otro código los altere, sin embargo se pueden declarar métodos públicos que invoquen esos métodos privados en diferentes formas, así estaremos extendiendo el comportamiento de esos métodos privados sin cambiar estos, este sería otro ejemplo de este principio.

Mientras este principio pareciese ser una contradicción hay técnicas para permitir que nuestro código sea extendido sin modificarlo directamente, hay que tener mucho cuidado al escoger las áreas del código que necesita ser extendido, aplicar el principio Abierto-Cerrado en cualquier lugar puede ser innecesario y puede provocar código complejo y difícil de entender.

14 mayo 2010

Las Clases tratan acerca del comportamiento

Principio: Las Clases tratan acerca del comportamiento y funcionalidad (Classes are about behavior and functionality)

La razón por la que usualmente creamos una subclase es porque el comportamiento de la subclase es diferente que la superclase, veamos un ejemplo donde no se aplica correctamente este principio:


En este ejemplo, ¿el comportamiento de la clase Guitarra es diferente de la clase Instrumento? ¿Es diferente la funcionalidad de un Mandolin ó de un Banjo?, la respuesta para ambas preguntas en No, todos los instrumentos tienen el mismo comportamiento.

Un argumento por el que podemos defender este diseño es que todos los instrumentos tienen diferentes propiedades o que la clase Instrumento representa una abstracción y no un objeto real, estos podrían ser buenas razones, pero provocaría un trabajo extra y haría nuestro software inflexible y difícil de cambiar.

Por lo cual aplicando el principio "Clases tratan acerca de comportamiento y funcionalidad" el resultado de nuestro diseño sería el siguiente:



La clase Instrumento ya no es Abstracta y añadimos un nueva propiedad para saber que tipo de instrumento es.

Si tuvieramos que escribir una aplicación que representara como esos instrumentos van a ser tocados, entonces si tendríamos que definir subclases para manejar ese comportamiento diferente como tocarInstrumento(), iniciarSonido(), etc.

La Clase debe tener solo una razón para cambiar

Principio: La Cada clase debe tener solo una razón para cambiar (Each class in your application should have only one reason to change)

Como sabemos una constante en el desarrollo de software es el CAMBIO, el software que  es bien diseñado es fácil hacer cambios.

La forma mas fácil  de hacer que nuestro software sea fácil de cambiar es asegurarnos que Cada clase tiene solo una razón para cambiar. En otras palabras, estaremos reduciendo las posibilidades de que una clase tenga que cambiar reduciendo el número de responsabilidades que una clase tenga.

Veamos un ejemplo:

La clase Automovil tiene VARIAS responsabilidades que podrían causar que tenga que cambiar si un mecánico cambia la forma como checa el aceite, o si un conductor maneja el carro de forma diferente, o si el lavado de carros es actualizado, este código necesitaría cambiar.

Cuando observemos que una clase tiene mas de una razón para cambiar, es probable que tenga responsabilidades de más. Si descomponemos la funcionalidad en diferentes clases, donde cada clase tiene solo pocas responsabilidades y por lo tanto solo una razón para cambiar.
 
Ahora la clase Automovil es mucho mas simple  y mucho mas flexible al cambio, las clases Conductor y LavadoDeCoches solo tienen una responsabilidad, así que las posibilidades de que cambien son mas reducidas.

Un termino muy relacionado con este principio es la Cohesión. La cohesión esta relacionada en medir cuanto una clase hace solo una cosa bien y no intenta tomar responsabilidades de más.

Un diseño con una medida de alta Cohesión es débilmente acoplada, entre mayor sea la medida de Cohesión, mas débilmente  es el acoplamiento entre nuestras clases, por lo tanto siempre debemos de asegurarnos que nuestras clases sean cohesivas es decir, cada una de nuestras clases deben tomar solo una responsabilidad correctamente. Siempre debemos esforzarnos por mantener una alta Cohesión en el ciclo de vida de nuestros diseños.

Programar una Interface vs implementación

Principio: Programar una Interface en lugar de una implementación (Code to an interface rather than to an implementation).

Para entender claramente este principio veamos primero que es Polimorfismo. El polimorfismo esta muy relacionado con la herencia, cuando una clase hereda de otra entonces el polimorfismo permite a una subclase sustituir a la superclase.

Lo que permite el polimorfismo  es retardar la decisión sobre el tipo del objeto hasta el momento en que vaya a ser utilizado el método. En este sentido, el polimorfismo está asociado a lo que se denomina vinculación tardía o vinculación en tiempo de ejecución.

La idea es que los objetos de distintas clases que hereden de una clase en común puedan ser tratados de la misma manera, y se le apliquen los mismos mensajes (tengan la misma interfaz), aunque las implementaciones particulares sean diferentes.

Veamos un ejemplo para entender de una manera mas clara.

 
Imaginemos que tenemos una interface (o una clase abstracta) llamada Animal que tiene definido un método llamado hacerSonido(), Animal tiene dos implementaciones concretas Perro y Gato, sin aplicar Polimorfismo tenemos que Programar una Implementación:

Perro p = new Perro();
p.hacerSonido();

Declarando la variable p como de Tipo Perro (Una concreta implementación de Animal) nos obliga a codificar una concreta implementación.

Pero con ayuda del polimorfismo podemos Programar una Interface:

Animal a = new Perro();
a.hacerSonido();

Sabemos que el método hacerSonido lo ejecutara un objeto de tipo Perro  pero ahora podemos utilizar la referencia a para aplicar el polimorfismo.

Si añadimos a nuestro código anterior las siguientes líneas:

Animal a = new Perro();
a.hacerSonido();
a = new Gato();
a.hacerSonido();

Ahora el método hacerSonido lo ejecutara un objeto de tipo Gato.


Aún mejor, en lugar de definir la instancia del subtipo new Perro() ó new Gato() como en el código anterior, asignamos la implementación concreta del objeto en tiempo de ejecución:

Animal a = getAnimal();
a.hacerSonido();

Ahora no sabemos de que tipo es Animal , ya que lo único que nos preocupa es saber que responderá de forma correcta al método hacerSonido().

Programar una interface en lugar que una implementación logra que nuestra aplicación sea más fácil de extender y nuestro código funcione con todas las implementaciones de esa interface, aún con ellas que aún no han sido creadas.

Encapsular lo que varía

Principio: Encapsular lo que varía (Encapsulate what varies)

Veamos primero que es la Encapsulación. La encapsulación permite ocultar la funcionalidad interna de las partes de tu aplicación, es decir, ocultar la implementación de una parte de tu aplicación de tal forma que sea fácil de usar y cambiar. Esto hace que esa parte de tu aplicación actué como una caja negra para proveer servicios a sus usuarios.

La encapsulación permite a una clase que sus datos no puedan ser cambiados en una forma directa o usar esos datos de manera incorrecta, es decir proteger la información de tu código para evitar que se use incorrectamente.

La encapsulación también te ayuda a separar el comportamiento de ciertas partes de tu aplicación, tú podrías colocar cierto comportamiento en un código dentro de un método y colocar ese método en una clase, entonces tu habrás separado ese comportamiento del resto de tu aplicación y la aplicación tiene que usar esa nueva clase y método para tener acceso a ese comportamiento, es el mismo principio que se usan con los datos solo que ahora se separa partes de la aplicación para protegerlos de usarlos incorrectamente.

En resumen la Encapsulación es cuando tu separas o ocultas una parte de tu código del resto de tu código, la mas simple forma de encapsulación es cuando defines las variables de tu clase como privadas y solo expones esos datos a través de métodos en la clase.


También se pueden encapsular grupos de datos o aún comportamiento de tu aplicación, para tener control de como se debe tener acceso a ellos.


El principio Encapsular lo que varía, se refiere a que siempre que se tiene un comportamiento en la aplicación que se considera que pueda cambiar, se debe apartar ese comportamiento de las partes de la aplicación que permanecen sin cambio.

Veamos un ejemplo:
Aquí tenemos una simple clase llamada Pato que tiene 3 métodos; nadar, volar y mostrar, los métodos nadar y mostrar permanecen constantes para todos los objetos Pato, pero el método volar no es una constante ya que algunos objetos Pato no vuelan mientras otros vuelan, entonces aquí es una buena oportunidad para aplicar el principio Encapsular lo que varía
Hemos encapsulado lo que varía, el comportamiento volar

Análisis

El Análisis te ayuda a asegurar que tu sistema funcione en un contexto del mundo real, previniendo problemas antes de que surgan.

El análisis y los casos de uso nos permiten mostrar a los clientes, directores y otros desarrolladores como tu sistema trabajara en un contexto del mundo real, el análisis nos permite darnos cuenta si necesitamos hacer cambios en nuestros casos de uso y en ocasiones esos cambios significaran desarrollar otros casos de uso.

El análisis Textual es la detección de los sustantivos y verbos en nuestros casos de uso para la definición de las clases y métodos de nuestro sistema.

Los sustantivos en nuestros casos de uso son generalmente las clases que necesitaremos escribir y el enfoque en nuestro sistema. Solo necesitamos las clases que son parte de nuestro sistema que tenemos que representar, aún así debemos prestar atención a los sustantivos de nuestros casos de uso aunque no formen parte de las clases de nuestro sistema. Tenemos que pensar como las clases que forman parte de nuestro sistema  pueden soportar el comportamiento de nuestros casos de uso.

Un buen caso de uso explica claramente y exactamente que debe hacer el sistema, en un lenguaje que es fácilmente entendible, cuando un buen caso de uso este completo, el análisis textual es una rápida y fácil forma de detectar las clases y métodos en nuestro sistema.

Un diagrama de clases es un tipo de diagrama estático que describe la estructura de un sistema mostrando sus clases, atributos y las relaciones entre ellos. Los diagramas de clases son utilizados durante el proceso de análisis y diseño de los sistemas, donde se crea el diseño conceptual de la información que se manejará en el sistema, y los componentes que se encargaran del funcionamiento y la relación entre uno y otro, dicho de otra forma, una imagen dice que más que mil palabras.

Los diagramas de clases nos ayuda a comunicar nuestro diseño a los diversos miembros del desarrollo del sistema, es una gran manera de modelar las clases que necesitamos crear, pero no proveen de todas las respuesta que necesitaremos al programar el sistema, por ejemplo no nos indicaran como programar nuestros métodos, sin embargo es una herramienta útil para detectar problemas antes de programar o comunicar nuestras ideas.

Cambio de Requerimientos.

No importa donde trabajes, que tan bien este diseñada tu aplicación, o que lenguaje utilizas para programar, siempre hay una constante que estará presente en tus desarrollos y es el CAMBIO.

Los requerimientos cambian todo el tiempo, algunas veces a la mitad de un proyecto o algunas veces cuando tu pienses que todo esta concluido.

Los requerimientos siempre cambian y crecen aunque hayas hecho buenos casos de uso.

Los cambios pueden provocar que en un caso de uso existan varios Escenarios pero siempre compartirán el mismo objetivo, un escenario es el flujo completo desde el primer paso hasta el último en un caso de uso.

Algunas veces un cambio de requerimiento revela problemas en tu diseño del cual no te habías dado cuenta, cambio es una constante y tu sistema debería siempre mejorar cuando se apliquen los cambios, un buena practica cuando se aplican los cambios es siempre utilizar principios de diseño orientado a objetos.

Requerimientos

La primera prioridad en el desarrollo de un sistema es que haga exactamente lo que el cliente necesita.

El requerimiento es un punto especifico que tu sistema tiene que hacer para hacer su trabajo correctamente.

Para estar seguro que se tienen buenos requerimientos, se deberían de desarrollar casos de uso para el sistema.

Los casos de uso detallan exactamente lo que el sistema debería de hacer y define un solo objetivo, pudiendo tener diversos flujos para alcanzar ese objetivo, debe ser redactado en un lenguaje entendible para el usuario.

Realizar correctos requerimientos asegura que tu sistema funcione como tu cliente lo quiere, así como la definición de todos los pasos en tus casos de uso para tu sistema, en ocasiones los casos de uso te permitirán encontrar cosas que tu cliente olvido decirte y requerimientos incompletos.

Aunque algunas veces el cliente no sepa que realmente él quiere, se deben formular preguntas de lo que realmente el cliente esta solicitando antes de determinar exactamente lo que debe hacer el sistema. Entonces uno puede pensar mas allá de lo que el cliente pide para anticipar las necesidades y problemas.

La mejor forma de obtener buenos requerimientos es entender completamente como el sistema tiene que funcionar, conocer perfectamente las necesidades del cliente.

El sistema debe funcionar en el mundo real, entonces hay que planear y probar para cuando las cosas suceden incorrectamente.

Cuando las funcionalidades en el sistema suceden incorrectamente, el sistema debe tener flujos alternos para lograr los objetivos del sistema.

En un sistema se tienen que probar el flujo ideal así como los flujos alternos.

Inicio

La gente enfrenta problemas, por lo tanto construimos software para resolver sus problemas.

Un buen software no solo resuelve problemas inmediatos, se desarrolla buen software para ser mantenido y modificado para prepararlo para los cambios inevitables que el cliente va a requerir.

El primer principio en el Desarrollo de Software es que debe satisfacer al cliente, el software debe hacer lo que el cliente necesita, un buen software debe ser bien diseñado, codificado, fácil de mantener, reusar y extender.

A continuación se mencionan los tres pasos ordenados por importancia para construir un buen Software:

1. El software debe hacer lo que el cliente necesita.
2. Aplicar principios de diseño de Orientación a Objetos para añadir flexibilidad.
3. Esforzarse por un diseño mantenible y reusable.

Un software bien diseñado es fácil de cambiar y extender, para ello debemos usar principios de diseño para que nuestro sistema sea mantenible, flexible y extensible, si un diseño no cumple con estas propiedades hay que cambiarlo, nunca debemos permitir un mal diseño en nuestros sistemas.

Un principio de diseño es una técnica que puede ser aplicada a un diseño o código para hacer nuestro código mas mantenible, flexible y extendible.

Una buena práctica es hacer desarrollos iterativos e incrementales, repetir los pasos de un proceso de desarrollo una y otra vez añadiendo un poco de complejidad cada vez, es decir, en cada iteración se hace análisis, diseño, implementación y pruebas, entonces se lo mostraremos al cliente y continuaremos refinando el desarrollo de nuestro software.