Patrones de diseño
Definición
1. Situación respecto a la noción de objeto
Un objeto no construye una aplicación. Lo importante, tanto como los mecanismos que permiten trabajar con los objetos, son aquellos que permiten gestionar la manera en que estos interactúan ante una problemática para dar respuesta a una funcionalidad.
Para ello se utiliza, de manera consciente o no, patrones de diseño que se corresponden, cada uno, con una forma de hacer interactuar los objetos entre sí. Existen varios tipos de patrones, destinados a gestionar problemáticas que pueden parecer similares pero que tienen, cada una de ellas, un contexto de aplicación o un destino particular. Son tan numerosos que haría falta un libro entero dedicado al tema para presentarlos todos, lo cual escapa del interés de esta obra.
La mayoría de los patrones se conciben para responder a una problemática concreta. Son la síntesis de experiencias importantes y significativas y se describen con precisión utilizando distintos objetos, cuyos roles e interacciones se describen a su vez.
Su conocimiento permite estandarizar el diseño de aplicaciones, implementar herramientas para reproducir las buenas prácticas y mejorar, proyecto tras proyecto, los procesos de construcción de aplicaciones utilizando soluciones normalizadas.
No obstante, la mayoría de los libros de referencia sobre el tema están...
Patrones de creación
1. Singleton
Un singleton es, en matemáticas, un conjunto que contiene un único elemento. En informática se trata de una clase que posee una única instancia.
>>> class Singleton:
... instance = None
... def __new__(cls):
... if cls.instance is None:
... cls.instance = object.__new__(cls)
... return cls.instance
...
>>> object() is object(), Singleton() is Singleton()
(False, True)
Cabe destacar que, la mayoría de las veces, los lenguajes de programación utilizan el singleton para paliar el hecho de que su modelo de objetos no es lo suficientemente flexible como para gestionar lo que Python hace por sí mismo mediante sus métodos de clase.
De este modo, utilizar un singleton en Python es algo muy extraño.
2. Fábrica
Presentación de la problemática
Cuando se dispone de una clase madre abstracta y varias clases hijas, la clase madre abstracta permite recoger y aprovechar comportamientos comunes a todas las clases hijas. En los lenguajes estáticamente tipados, una funcionalidad que resulta útil es la posibilidad de trabajar potencialmente con objetos declarados como que son del tipo de la clase madre, de manera que su comportamiento pueda homogeneizarse.
Dicho de otro modo, se evita tener que duplicar código tantas veces como clases hijas existan.
Para ello, se utiliza una fábrica, que recibe como parámetro los datos necesarios para realizar la construcción del objeto, determinando a partir de ciertos criterios la clase hija que debe instanciar, crear dicha instancia, y devolverla. Esta instancia tendrá el tipo de su clase madre (véase la noción de polimorfismo).
Solución
Python es un lenguaje de tipado dinámico. No dispone de restricciones que le obliguen a trabajar con un tipo de datos particular -a menos que el propio desarrollador lo haya especificado en su código- y utiliza la noción de «Duck Typing».
En este sentido, las problemáticas puramente técnicas vinculadas a la solución de la fábrica no le aportan ninguna ventaja, puesto que, para el lenguaje, son inexistentes.
No obstante...
Patrones de estructuración
1. Adaptador
Presentación de la problemática
Para disponer de procesamientos genéricos, cuando se diseña una arquitectura, resulta ideal trabajar con una solución que permita disponer de una interfaz común y crear objetos que vayan a proveer, a continuación, la misma interfaz.
Pocas veces se trabaja únicamente con objetos que hemos diseñado nosotros mismos, pues lo habitual es trabajar con librerías de terceros, o con objetos diseñados previamente y que se han adaptado a una problemática diferente a la nuestra.
En cualquier caso, no es posible recuperar estos objetos y meterlos en un molde que satisfaga inmediatamente nuestras necesidades.
En este caso, la solución más difundida consiste en crear adaptadores, que adaptarán el comportamiento de los objetos a una interfaz única.
Solución
He aquí un ejemplo de clases que están perfectamente adaptadas a un uso que queremos recuperar, pero utilizándolas de manera genérica:
>>> class Perro:
... def ladrar(self):
... print('Guau')
...
>>> class Gato:
... def maullar(self):
... print('Miau')
...
>>> class Caballo:
... def relinchar(self):
... print('Hiiii')
...
>>> class Cerdo:
... def gruñir(self):
... print('Oing')
...
Queremos hacer «hablar» a estos animales de manera genérica.
He aquí una clase que se corresponde con la interfaz deseada:
>>> import abc
>>> class Animal(metaclass=abc.ABCMeta):
... @abc.abstractmethod
... def hacerRuido(self):
... return
...
Podríamos retomar cada una de las cuatro clases anteriores y reescribirlas con el primer método, pero esto supondría una pérdida a nivel semántico, aunque sí es potencialmente útil para otros usos.
Python proporciona...
Patrones de comportamiento
1. Cadena de responsabilidad
Presentación de la problemática
El patrón de diseño llamado cadena de responsabilidad permite crear una cadena entre distintos componentes que deben procesar un dato. De este modo, cada componente recibe un dato, lo procesa tal y como debe, y lo transmite al siguiente componente de la cadena, todo ello sin preocuparse por saber si el mensaje interesa o no a su sucesor.
Solución
He aquí un componente autónomo que gestiona el procesamiento o no de un dato en función de las condiciones que se le pasan en su inicialización:
>>> class Componente:
... def __init__(self, name, conditions):
... self.name = name
... self.conditions = conditions
... self.next = None
... def setNext(self, next):
... self.next = next
... def procesamiento(self, condition, message):
... if condition in self.conditions:
... print('Procesamiento del mensaje %s por %s' %
(message, self.name))
... if self.next is not None:
... self.next.procesamiento(condition, message)
...
He aquí cómo crear tres componentes:
>>> c0 = Componente('c0', [1, 2])
>>> c1 = Componente('c1', [1])
>>> c2 = Componente('c2', [2])
Cómo crear la cadena de dependencia:
>>> c0.setNext(c1)
>>> c1.setNext(c2)
Y el resultado cuando se pasa una condición y un mensaje:
>>> c0.procesamiento(1, 'Test 1')
Procesamiento del mensaje Test 1 por c0
Procesamiento del mensaje Test 1 por c1
>>> c0.procesamiento(2, 'Test 2')
Procesamiento del mensaje Test 2 por c0
Procesamiento del mensaje Test 2 por c2
Conclusiones
Esta metodología es una manera sencilla de desacoplar funcionalidades secuencialmente ejecutadas....
ZCA
1. Consideraciones
ZCA se corresponde con Zope Component Architecture, un conjunto de librerías independientes que permiten crear una arquitectura entre componentes.
2. Adaptador
Declaración
He aquí, declarados conforme a los patrones de uso de la ZCA, dos interfaces y dos objetos:
>>> from zope.interface import Interface
>>> from zope.interface import Attribute
>>> from zope.interface import implements
>>> class IPerro(Interface):
... nombre = Attribute("""Nombre del perro""")
... def ladrar(filename)
... """Método que permite hacerlo ladrar"""
...
>>> class Perro(object):
... implements(IPerro)
... nombre = u''
... def __init__(self, nombre):
... self.nombre = nombre
... def ladrar(self):
... """Método que permite hacerlo ladrar"""
... print('Guau')
...
>>> class IGato(Interface):
... nombre = Attribute("""Nombre del gato""")
... def maullar(filename):
... """Método que permite hacerlo maullar"""
...
>>> class Gato(object):
... implements(IGato)
... nombre = u''
... def __init__(self, nombre):
... self.nombre = nombre
... def maullar(self):
... """Método que permite hacerlo maullar"""
... print('Miau')
...
La idea de este ejemplo es adaptar ambos objetos en una única clase. Empezaremos...