domingo, 24 de junio de 2007

El principio de las cosas... Parte 1 de 2

Hace unos días un amigo me planteaba su dificultad para comprender IoC, Inversion of Control, mientras conversábamos sobre el tema me di cuenta que el problema no radicaba en el principio en sí, si no en la necesidad de aplicarlo, el porque de su aparición es escena.

Me parecio un buen aporte entonces escribir esta entrada, la idea es mencionar algunos principios de diseño OO, Object Oriented, sobre todos aquellos implícitos en conceptos mas avanzados y que muchas veces damos por sentado que nuestro interlocutor conoce.

La otra realidad que viene de la mano es que difícilmente quien se inicia, y algunos ya iniciados, logren diseños eficientes sin conocer estos principios o su significado concreto.

Principios de diseño:

a - Responsabilidad única.
b - Principio Abierto / cerrado
c - Principio de Sustitución
d - Principio Inversión de dependencia
e - Segregación de interfaz

Veremos ahora que cuestión en el diseño ocupa a cada principio, personalmente me parece más didáctico explicar algunas cosas por el opuesto, mostrar el problema y asi entender la ventaja de aplicar el principio.

a - Responsabilidad única.

Vamos a tomar un pequeño conjunto de clases para nuestro ejemplo, empezaremos con una y veremos los males que nos aquejan.



Nuestra clase Cliente es verdaderamente potente, sabe hacer tantas cosas!!!!, con solo tener una instancia de ella puedo resolver una gran cantidad de problemas.

Veamos que cosas sabe hacer:

1 - Persistirse
2 - Eliminarse
3 - Crear una salida HTML que muestre sus atributos
4 - Hacer reportes referidos a ella misma
5 - Calcular su edad en el mercado

Todo parece muy cómodo, pero analicemos como se comporta esta clase frente al cambio, su capacidad de adaptación.

  • Cambio mi motor de base de datos, de XXSql paso a NNSql.
    Debo cambiar mi clase Cliente.

  • Cambio la forma de crear la salida HTML
    Debo cambiar mi clase Cliente.

  • Agrego un reporte o modifico uno existente
    Debo cambiar mi clase Cliente.

  • Cambio la política para calcular la presencia en el mercado
    Debo cambiar mi clase Cliente.
Esto evidentemente es malo, existen distintos eventos cuya consecuencia es un cambio en la clase Cliente.

Si vemos un poco mas allá de nuestras narices notaremos que además esto implicará cambios en aquellos artefactos que recurren a la clase Cliente para resolver alguna colaboración.

Bueno el problema es que nuestra clase Cliente tiene muchas responsabilidades, incluso algunas que exceden claramente sus responsabilidades naturales, me refiero a las responsabilidades de Cliente en su contexto de negocio.
Se dice entonces que Cliente es poco cohesiva, sabe hacer más de lo que necesita, maneja temas en los cuales no es experto.

Posiblemente esa falta de cohesión nos lleve a otro problema, para mi es una regla que se cumple con rigor matemático, acoplamiento.

Un cambio en Cliente requerirá cambios en quienes consumen la clase Cliente, los cambios se propagan en nuestro diseño, el impacto de un cambio es alto, afecta a varios artefactos.

Pero, volvamos a nuestro principio de OOD, Object Oriented Design, Responsabilidad única.

Este principio ataca el problema descripto, su enunciado dice:

No debe existir más de una razón para cambiar una clase.

En nuestro escenario Cliente debería ser algo parecido a la siguiente figura:
Que sucede entonces con aquellos metodos que ya no existen en Cliente?, bueno será nuestro trabajo buscar quienes tienen la responsabilidad de resolver esas cuestiones. Lo importante aquí es que Cliente atienda sus responsabilidades directas, aquellas en las que es experto.

También es destacable que deberá relacionarse, u otras clases deberán relacionarse con ella, para resolver las cuestiones en las que Cliente no es el experto, pero requieren de su colaboración.

Eso nos lleva a otros principios que ayudarán a manejar las colaboraciones en forma desacoplada.

b - Abierto / cerrado

Volvamos ahora a nuestra nueva clase Cliente, en el punto anterior mejoramos su cohesión eliminando algunos métodos, todos sabemos que alguien deberá encargarse entonces de asumir la responsabilidad de implementar esa funcionalidad.

Una posible solución para lo relacionado con la persistencia podría ser la siguiente:

Muy bien otra vez contamos con unas clases muy bonitas, las responsabilidades ahora parecen estar asignadas correctamente, y de hecho lo están.


Propongamos un cambio en el requerimiento y veamos.

Nuestra clase ClienteDB recurre a un susbsistemas de APIs específicas de XXSql. Ahora nos piden que debemos armar una implementación para que la base de datos sea ZZSql.


Nuevamente un cambio tiene impacto directo un cambio en el sistema de base de datos puede requerir un cambio en Ventas. Por caracter transitivo ventas ha quedado acoplado la API de XXSql.

Problema planteado, que nos dice el principio Abierto/Cerrado?
Los artefactos software deben ser abiertos para su extensión, pero cerrados ante una modificación.

En resumidas cuentas lo que quiere decir esto es que un cambio en el entorno de un artefacto no debería implicar un cambio en el artefacto. Esto no solo es válido para clases, se aplica también a métodos y cualquier otro artefacto software.

La aplicación del principio a nuestro problema podría ser agregar una interface o una clase abstracta que sea bien conocida por Ventas, ClienteDB debería implementar la interface o heredar de la clase abstracta.


En el diagrama se puede apreciar la implementación para una cuestión más terrenal, mockobjects y test unitarios.










c - Sustitución

Vamos a aprovechar la capacidad de extensión que nos brindan los lenguajes OO, estamos frente a nuestra clase Cliente, con algunos cambios ya que los requerimientos del sistema han cambiado con el transcurso del tiempo y ahora un nuevo requerimiento nos exige considerar Clientes presenciales y Clientes virtuales.

Hacemos OOP, Object Oriented Poragramming, contamos con los beneficios de la herencia, heredemos de Cliente.

La funcionalidad nueva esta relacionada con la obtención de la autorización de una transacción comercial.

El experto en autorización requiere que el cliente se identifique, pero el ClienteVirtual implementa el metodo Identificarse() disparando una Exception. Su lógica de negocio no provee un mecanismo de identificación.

El método Obtener entonces deberá tener un tratamiento especial para ClienteVirtual.

Posiblemente el programador haga algo así:

if( typeof... )
//Tratamiento para ClienteVirtual
else
//Tratamiento para Cliente

El problema entonces es que CentroAutorización ya no puede tratar a ClienteVirtual como Cliente, debe conocer las características de la sub clase porque esta altera el comportamiento de la clase Cliente. ( Comportamiento, no implementación de comportamiento).

La solución en este caso es revisar eol diseño, posiblemente ClienteVirtual no este en la línea de herencia de Cliente, tal como pensamos en principio.

No atender estas cuestiones hace que nuestro diseño sea críptico, que los programadores no puedan confiar en la extensión de las clases y deban conocer el comportamiento de de cada una de ellas.

Bueno, para evitar esto debemos respetar el principio de sustitución. El mismo expresa:

Toda clase debe poder ser reemplazada por cualquiera de sus subclases

La aplicación de este principio garantiza de alguna manera el éxito en la aplicación de los principios de Responsabilidad única y Abierto / cerrado.

En los próximos días completaré esta entrega con los principios restantes.

Espero que pese a lo básico este post les resulte de interés.




5 comentarios:

Diego Jancic dijo...

Muy buen post!! Espero el 2do ;)

Daniel Calvin dijo...
Este comentario ha sido eliminado por el autor.
Sebastian Renzi dijo...

Dani, ya esta impreso para ser leido en el viaje de vuelta. Gracias !!

Carlos Marcelo Santos dijo...

!Muy bueno Dani! Espero ansioso la segunda parte.

Unknown dijo...

Muchas gracias! Muy claro. También espero la segunda parte