martes, 30 de octubre de 2007

Patrones de diseño para torpes - 1ª parte

Hoy hablamos de los famosos patrones de diseño o design patterns. Hay montones de libros y artículos sobre el tema, empezando por el famoso libro "Desing Patterns", de Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides.

El caso es que no me acabo de enterar muy bien (a ratos soy un poco lerdo) y me pierdo un poco, así que he decidido explicarme a mí mismo algunos patrones de diseño típicos.



Una de las cosas que no me gustan es que las explicaciones y ejemplos que se proponen en muchos libros o artículos están relacionadas con objetos "sofware". ¿No es acaso la programación orientada a objetos una ayuda para representar o modelizar sistemas u objetos "reales"?

Pues voy a tratar de explicar los patrones de diseño con ejemplos cotidianos, no "software", a ver qué tal me sale.

El patrón "Factory"


Este patrón de diseño es muy útil cuando tenemos que crear (=instanciar) distintos objetos que tienen algo en común (=comparten una interfaz). Ya me he liado con tecnicismos.

Imaginemos que vamos con nuestra cámara de fotos y el coche teledirijido de nuestr@ hij@ a una tienda de electricidad para comprar pilas. El enfoque "sencillo" (=aplicando un patrón de diseño) es decirle al vendedor: "quiero pilas para estos cacharros". El enfoque complicado (=instanciar cada objeto por separado) es decirl: "quiero pilas AAA (de 1,5 V) para la cámara, una pila de petaca de 9V para el mando y pilas AA para el cochecito".

Analicemos el objeto demandado en cuestión: las pilas. ¿Qué caracteriza a una pila? Su voltaje, su tamaño y poco más. Es decir, las pilas tienen una interfaz común, pero son diferentes unas de otras. Si tuviésemos que modelizarlas usando clases tendríamos la clase PilaAAA, PilaPetaca, PilaAA y alguna más.
mi_pilaAAA = new PilaAAA()
mi_pilaPetaca = new PilaPetaca()
mi_pilaAA = new PilaAA()

Cada uno de estos objetos instanciados deberíamos asociarlo a su receptor:
camara_fotos.setPila(mi_pilaAAA)
mando_distancia.setPila(mi_pilaPetaca)
cochecito.setPila(mi_pilaAA)

¿No sería más cómodo tener algo (=el dependiente) que se encargase de darnos las pilas con sólo decirle para qué las queremos?
dependiente = new Dependiente()
camara_fotos.setPila(dependiente.damePilas(this))
mando_distancia.setPila(dependiente.damePilas(this))
cochecito.setPila(dependiente.damePilas(this))

Parece un gran avance, ¿no? Cada objeto cliente (la cámara, el mando a distancia y el cochecito) se despreocupan de saber qué características tienen las pilas que usan. Simplemente le piden pilas al dependiente y listo.
Es tarea del dependiente saber qué tipo de pilas tiene que devolver, y la forma de hacerlo (=implementación) es transparente para el cliente. Hoy puede que el dependiente mire un catálogo, mañana puede que consulte una base de datos y pasado mañana a lo mejor solicita un fichero XML de aparatos y pilas, pero esto no es algo que deba saber el objeto cliente.

Bien, pues simplificando bastante, el patrón "Factory" es esto mismo. Un objeto especializado se encarga de instanciar objetos para clientes, y estos utilizan los métodos que proporciona la interfaz del objeto recibido, pero sin saber nada de las interioridades del objeto, ni siquiera qué tipo de objeto es (=a partir de qué clase está instanciado).

Veamos un ejemplo un poco más "software". Imaginemos que tenemos una clase sencilla en una aplicación de blogs que se dedica a "pintar" en pantalla el título, autor y fecha de publicación de algunos envíos.
Estos envíos pueden estar guardados en una base de datos o provenir de un RSS, por ejemplo.

A la clase cliente (la que "pinta" en pantalla") le da igual el origen de los datos, sólo quiere los datos. Voy a poner un poco de código --utilizaré Python, que me gusta bastante ;-) y me parece muy claro.
class EnviosRSS:
def __init__(self, url_rss):
# Hacemos algo para traer los datos de la url
self.post = lo_que_sea
def dameAutor(self):
return self.post.autor
def dameTitulo(self):
return self.post.titulo
def dameFecha(self):
return self.post.fecha

class EnviosBBDD:
def __init__(self):
# Hacemos algo para traer los datos de la base de datos
self.post = lo_que_sea
def dameAutor(self):
return self.post.autor
def dameTitulo(self):
return self.post.titulo
def dameFecha(self):
return self.post.fecha

class EnviosFactory:
def dameEnvios(self, url=''):
if url == '':
return EnviosBBDD()
else:
return EnviosRSS(url)

# código cliente
factory = EnviosFactory()
envios1 = factory.dameEnvios()
envios2 = factory.dameEnvios('http://davidasorey.net/index.php/feed/rss')
# pintamos los envios
print envios1.dameTitulo()
print envios1.dameAutor()
print envios1.dameFecha()
print envios2.dameTitulo()
print envios2.dameAutor()
print envios2.dameFecha()

Unas notas sobre la terminología "pythonera": los métodos se definen con def, las clases se instancian llamando al nombre de la clase, sin new. El constructor (algo parecido, realmente) es el método __init__. Por últimos, los métodos al definirse siempre deben llevar el self por delante. Realmente Python no es un lenguaje orientado a objetos puro, pero ni falta que le hace ;-)

Para finalizar, muy importante: esto no es una lección magistral. Es mi visión personal del patrón "Factory", que puede ser errónea e incompleta. Se admiten (se ruegan) sugerencias y comentarios.

6 comentarios:

  1. Mola, si quieres una buena bateria de ejemplos "no software" para patrones de diseño, te recomiendo el libro "Head First Design Patterns".

    ResponderEliminar
  2. estoy buscando ejemplos (software comercial) donde se aplican patrones de diseño. Agradezco cualquier ayuda

    ResponderEliminar
  3. [...] más “software”. En el libro “Desing Patterns” que mencionábamos en el anterior artículo proponen dos ejemplos muy [...]

    ResponderEliminar
  4. ¡¡WOW!! Genial!! La verdad es que los únicos patrones de diseño que me traen por el camino de la amargura son los de creación (Factory Method y Abstact Factory). Y la verdad que con tu explicación "vida real" me ha quedado bastante clara su uso.Muchas gracias, voy a seguir "cotilleando" por aquí haber si hay más patrones explicados así. Muchas gracias! ^^

    ResponderEliminar
  5. Muy claro la explicacion con las pilas.. (Soy programador y me gusta mas cuando entiendo para que lo puedo usar muy claro)Por favor mas ejemplo con los otros patrones .. :-)Gracias Igualmente por la explicación me alegraste la noche (Medio Nerd, me parece que voy a mirar un poco de porno en Internet en vez de programacion)

    ResponderEliminar
  6. Gran explicación!!! he llegado a tu blog buscando información sobre este patrón y me ha quedado muy claro, muchísimas gracias.

    ResponderEliminar