viernes, 18 de marzo de 2016

Patrones de Diseño - Introducción

Introducción


Los patrones de diseño son una pieza fundamental en el desarrollo del software. Un patrón de diseño en software es un problema que ocurre una y otra vez en nuestro entorno y que puede resolverse repetidamente del mismo modo sin necesidad de implementar una solución diferente cada vez. Es decir, los patrones de diseño son soluciones exitosas y contrastadas a problemas comunes.


Un patrón de diseño tiene 4 elementos esenciales:

Elementos que forman un patrón de diseño


Nombre del patrón: Permite describir en pocas palabras un problema de diseño junto con sus soluciones y consecuencias. Tener un vocabulario de patrones nos permite diseñar con mayor abstracción y tener un lenguaje común para compartir, transmitir y documentar a otros compañeros de profesión. A su vez se pueden identificar fácilmente sus puntos fuertes y sus inconvenientes para decidir cuál es la mejor elección en cada caso.

Problema: Describe cuando se debe aplicar el patrón, explicando el problema y el contexto. En ocasiones el problema incluye una serie de requisitos que deben producirse para que tenga sentido aplicar el patrón.

Solución: Describe los elementos que constituyen, las relaciones, responsabilidades y colaboraciones entre ellos. El patrón proporciona una descripción abstracta de un problema de diseño y como se resuelve a través de una serie de elementos. En el caso de Java y los lenguajes de programación orientados a objetos, estos elementos serán los interfaces, clases y objetos.

Consecuencias: Son las ventajas e inconvenientes de aplicar el patrón. Son fundamentales para evaluar las alternativas de diseño y comprender los costes y beneficios de aplicar cada patrón. Incluyen entre otras el impacto sobre la flexibilidad, extensibilidad y potabilidad de un sistema.

Conceptos fundamentales previos:

Para poder entender bien los patrones de diseño, sus ventajas e inconvenientes es necesario tener claros una serie de conceptos que utilizaremos frecuentemente en el desarrollo orientado a objetos. 


Interfaces de Objetos

Cada operación declarada para un objeto especifica el nombre de un método junto con sus parámetros y tipo de respuesta. Es lo que se conoce cómo signatura o firma de un método.

Al conjunto de todas las firmas de métodos de un objeto se le denomina interfaz del objeto.
Las interfaces son fundamentales en los lenguajes de programación orientada a objetos. 
Las interfaces permiten a dos objetos interactuar entre sí evitando la necesidad de que estos conozcan la implementación concreta de cada uno de ellos.

Por otro lado, para una variable que referencia a un objeto debemos conocer dos tipos:
  • Tipo estático: Se interpreta en tiempo de compilación y es fijo.
  • Tipo dinámico: Se interpreta en tiempo de ejecución y es variable. 
La asociación en tiempo de ejecución entre una variable y su tipo se conoce como enlace dinámico o dynamic binding .

El enlace dinámico nos permite sustituir objetos en tiempo de ejecución por otros que tengan la misma interfaz. Esta capacidad de sustituir unos objetos por otros es lo que se conoce como polimorfismo. Para más detalles consultar enlace-dinamico-en-java

El polimorfismo permite simplificar las definiciones de los clientes, desacoplar objetos y modificar la relaciones entre ellos en tiempo de ejecución. Para más detalles consultar polimorfismo-en-java


Clases de Objetos

La implementación de un objeto queda definida por su clase. Una clase específica los datos, la representación interna del objeto y define las operaciones o métodos que puede realizar.

Las Clases se pueden relacionar entre sí implementando sistemas de Herencia. 
La Herencia de Clases nos permite hacer nuestros sistemas extensibles, mejor desacoplados y más reutilizables. 
Para saber más sobre Herencia podemos consultar la documentación de oracle

Hay una serie de conceptos que es importante entender sobre las relaciones entre clases: 
  • Clase Padre o Superclase: Es la clase que está en la parte más alta de la relación de Herencia.
  • Clase Hija o Subclase: Es una clase que hereda de la clase padre y por tanto incluye las definiciones de datos y métodos que define la clase padre. Estas subclases pueden ampliar o redefinir el comportamiento de las clases padre.
  • Clase Abstracta: Es aquella cuyo propósito es definir una interfaz común para sus subclases. Una clase abstracta delega parte de su implementación en sus subclases. Dado que su implementación no es completa, no se pueden instanciar objetos de este tipo de clases.

Programar para Interfaces

La herencia de clases es un mecanismo para extender las funcionalidad de una aplicación reutilizando la funcionalidad de las clases Padres. Nos permite definir rápidamente un nuevo tipo de objeto basándonos en otros y obtener nuevas implementaciones casi sin esfuerzo.
La herencia también permite definir familias de objetos con interfaces idénticas. Cuando la herencia se usa correctamente todas las clases que derivan de una clase abstracta o interfaz pueden responder a los métodos definidos en la interfaz. Consiguiendo dos cosas:
  • Los clientes no tienen porque conocer los tipos específicos de los objetos que utilizan, ya que basta con que conozcan la interfaz o clase abstracta.
  • Los clientes desconocen las clases que implementan dichos objetos, ya que basta con que conozcan la interfaz o clase abstracta.
Consejos:
No se deben declarar variables como instancias de clases concretas

Reutilización 

Las dos técnicas más comunes para reutilizar funcionalidad en lenguajes orientados a objetos son la herencia de clases y la composición de objetos.
  • La herencia permite definir una implementación en función de otra. A esta modo de reutilización se le denomina “caja blanca” porque con la herencia tenemos visibilidad de la interioridad de las clases padres.
  • La composición de objetos es una alternativa que se consigue ensamblando o componiendo objetos para obtener una funcionalidad más compleja. A este modo de reutilización se le denomina “caja negra” porque los detalles internos de los objetos no son visibles.

Delegación

Con la delegación tendremos dos objetos encargados de tratar una petición. Un primer objeto recibe la solicitud de una petición y este delega en un segundo objeto la funcionalidad.

Ejemplo de Clase Ventana y Clase Rectángulo:
  • Por Herencia: Si aplicamos herencia tendremos una clase Padre Rectángulo y una clase Hija Ventana.
  • Por Composición: Si aplicamos composición tendremos una clase Ventana que está compuesta por una variable de instancia de la clase Rectángulo. Con esta estructura las operaciones serán delegadas en la clase Rectángulo. Es decir, en lugar de hacer que Ventana sea un Rectángulo esta segundo estructura implica que Ventana tiene un Rectángulo.


Diseñar para el cambio

La clave para maximizar la reutilización de funcionalidad reside en anticiparse a los nuevos requisitos y modificaciones sobre los requisitos existentes. Con esto conseguiremos diseñar sistemas capaces de evolucionar.


  • Un diseño que no tenga en cuenta el cambio sufre el riesgo de tener que ser rediseñado en el futuro. 
  • Un rediseño desencadenará una serie de acciones como redefiniciones, re implementaciones, ejecución de pruebas, modificación de clientes y por tanto una serie de costes en tiempo y esfuerzo que podemos evitar haciendo un buen diseño inicial.


Suscríbete al boletín de novedades


En el próximo artículo: 
  • Veremos las diferentes maneras de agrupar y categorizar  los patrones de diseño.
  • Veremos los criterios utilizados para categorizar los patrones.
  • Estudiaremos brevemente la motivación de cada uno de los patrones definidos por GoF (banda de los cuatro)
  • Y recuerda ... Si no quieres perderte las próximas publicaciones. No olvides Suscribirte.




No hay comentarios:

Publicar un comentario

Entradas populares