Patrones de diseño
Definición
1. Situación respecto a la noción de objeto
Un objeto no constituye 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 utilizan, de manera consciente o no, patrones de diseño que corresponden, cada uno, a 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 adaptados...
Patrones de creación
1. Singleton (Instancia única)
Un singleton es, en matemáticas, un conjunto que contiene un único elemento. Antes de que se formalizaran los patrones de creación, este vocabulario existía en Python para conjuntos, listas o n-tuplas de un solo elemento, y la palabra singleton se utiliza en la documentación oficial con este significado matemático.
Así mismo, en la documentación oficial, el patrón de diseño Singleton se implementa utilizando un módulo y posicionando datos compartidos dentro de él (https://docs.python.org/3/faq/programming.html#how-do-i-share-global-variables-across-modules):
# config.py:
x = 0 # Valor predefinido del parámetro de configuración 'x'
# mod.py:
import config
config.x = 1
# main.py:
import config
import mod
print(config.x)
En informática, por lo general, se trata de una clase con una sola instancia. En Python, existe el singleton None, que es de la clase NoneType:
>>> NoneType = type(None)
>>> NoneType()
None
En el modelo objeto, el patrón de creación puede implementarse de la siguiente forma:
>>> 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 inusual.
Para los programadores que son nuevos en Python y aún no conocen el método __new__ o las sutilezas del modelo de objetos de Python, o para los que están acostumbrados a otro lenguaje, la solución propuesta se acerca más a estos otros lenguajes:
>>> class Singleton: ...
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 adecuará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
A continuación, se muestra 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...
ZCA
1. Consideraciones
ZCA corresponde a 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 para hacer que ladre"""
...
>>> class Perro(object):
... implements(IPerro)
... nombre = u''
... def __init__(self, nombre):
... self.nombre = nombre
... def ladrar(self):
... """Método para hacer que ladre"""
... print('Guau')
...
>>> class IGato(Interface):
... nombre = Attribute("""Nombre del gato""")
... def maullar(filename):
... """Método para hacer que maúlle"""
...
>>> class Gato(object):
... implements(IGato)
... nombre = u''
... def __init__(self, nombre):
... self.nombre = nombre
... def maullar(self):
... """Método para hacer que maúlle"""
... print('Miau')
...
La idea de este ejemplo es adaptar ambos objetos en una única clase. Empezaremos creando una interfaz. Esta adaptación se realiza, simplemente, utilizando la función adapts.
Aquí está el componente:
>>>...