Traducción Thinking in Python

203
Pensando en Python Bruce Eckel Presidente MindView,Inc. April 11, 2017 Patrones de Dise˜ no y resoluci´on de problemas t´ ecnicos Traducci´ on del libro Thinking in Python, disponible en: http://docs.linuxtone.org/ebooks/Python/Thinking_In_Python.pdf

Transcript of Traducción Thinking in Python

Page 1: Traducción Thinking in Python

Pensandoen

Python

Bruce EckelPresidente MindView,Inc.

April 11, 2017

Patrones de Diseno yresolucion de problemas tecnicos

Traduccion del libro Thinking in Python, disponible en:http://docs.linuxtone.org/ebooks/Python/Thinking_In_Python.pdf

Page 2: Traducción Thinking in Python

Contents

Prologo i

Introduccion iiEl sındrome Y2K . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . iiiContexto y composicion . . . . . . . . . . . . . . . . . . . . . . . . . . iv

Un rapido curso para programadores 1Vision General de Python . . . . . . . . . . . . . . . . . . . . . . . . . 1

Construido en contenedores . . . . . . . . . . . . . . . . . . . . . 2Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3Cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

El concepto de Patron 10¿Que es un Patron? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10Taxonomıa Patron . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11Estructuras de Diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . 13Principios de Diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15Clasificacion de Patrones . . . . . . . . . . . . . . . . . . . . . . . . . 20El desafıo para el desarrollo . . . . . . . . . . . . . . . . . . . . . . . . 21Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

2: Pruebas Unitarias 23Escribir las pruebas primero . . . . . . . . . . . . . . . . . . . . . . . . 25Simples pruebas de Python . . . . . . . . . . . . . . . . . . . . . . . . 26Un framework muy simple . . . . . . . . . . . . . . . . . . . . . . . . . 27Escribiendo pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29Pruebas de caja blanca y caja negra . . . . . . . . . . . . . . . . . . . 32Ejecucion de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . 35Ejecutar Pruebas Automaticamente . . . . . . . . . . . . . . . . . . . 38Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

3: Construyendo aplicaciones Framework 39Template Method (Metodo Plantilla) . . . . . . . . . . . . . . . . . . . 39Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40

4: Al frente de una implementacion 41Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44StateMachine (Maquina de Estados) . . . . . . . . . . . . . . . . . . . 47Table-Driven State Machine . . . . . . . . . . . . . . . . . . . . . . . . 55

La clase State . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57Condiciones para la transicion . . . . . . . . . . . . . . . . . . . . 57Acciones de transicion . . . . . . . . . . . . . . . . . . . . . . . . 58

Page 3: Traducción Thinking in Python

La tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58La maquina basica . . . . . . . . . . . . . . . . . . . . . . . . . . 59Simple maquina expendedora . . . . . . . . . . . . . . . . . . . . 60Prueba de la maquina . . . . . . . . . . . . . . . . . . . . . . . . 64

Herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

X: Decoradores:Seleccion de tipo dinamico 68Estructura Decorador basico . . . . . . . . . . . . . . . . . . . . . . . 69Un ejemplo cafe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69Clase para cada combinacion . . . . . . . . . . . . . . . . . . . . . . . 69El enfoque decorador . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72Compromiso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75Otras consideraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 78Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

Y: Iteradores:Algoritmos de desacoplamiento de contenedores 80Iteradores Type-safe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81

5: Fabricas:encapsularla creacion de objetos 83Simple metodo de fabrica . . . . . . . . . . . . . . . . . . . . . . . . . 83Fabricas polimorficas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86Fabricas abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92

6 : Funcion de los objetos 94Command: la eleccion de la operacion en tiempo de ejecucion . . . . . 94Estrategia: elegir el algoritmo en tiempo de ejecucion . . . . . . . . . 96Chain of Responsibility (Cadena de responsabilidad) . . . . . . . . . . 99Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102

7: Cambiando la interfaz 104Adapter (Adaptador) . . . . . . . . . . . . . . . . . . . . . . . . . . . 104Facade (Fachada) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107

8: Codigo de la tabla dirigido:flexibilidad de configuracion 108Codigo de la tabla dirigido por el uso de clases internas anonimas . . . 108

Page 4: Traducción Thinking in Python

10: Devoluciones de llamados 109Observer : Observador . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

Observando Flores . . . . . . . . . . . . . . . . . . . . . . . . . . 112Un ejemplo visual de Observers . . . . . . . . . . . . . . . . . . . . . . 121Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

11 : Despacho Multiple 128Visitor, un tipo de despacho multiple . . . . . . . . . . . . . . . . . . . 133Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

12 : Patron Refactorizacion 136Simulando el reciclador de basura . . . . . . . . . . . . . . . . . . . . . 136Mejorando el diseno . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141

“Hacer mas objetos” . . . . . . . . . . . . . . . . . . . . . . . . . 141Un patron para la creacion de prototipos . . . . . . . . . . . . . . . . 145Subclases Trash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149Analizar Trash desde un archivo externo . . . . . . . . . . . . . . . . 151Reciclaje con prototipos . . . . . . . . . . . . . . . . . . . . . . . . . . 154Haciendo abstraccion de uso . . . . . . . . . . . . . . . . . . . . . . . . 156Despacho multiple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

La implementacion del doble despacho . . . . . . . . . . . . . . . 163El patron Visitor (Visitante) . . . . . . . . . . . . . . . . . . . . . . . 170Un decorador reflexivo . . . . . . . . . . . . . . . . . . . . . . . . . . . 174

¿Mas acoplamiento? . . . . . . . . . . . . . . . . . . . . . . . . . 180¿RTTI considerado danino? . . . . . . . . . . . . . . . . . . . . . . . . 180Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

13 : Proyectos 187Ratas y Laberintos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187

Otros Recursos para Laberinto . . . . . . . . . . . . . . . . . . . 192Decorador XML . . . . . . . . . . . . . . . . . . . . . . . . . . . 193

Page 5: Traducción Thinking in Python

La composicion de esta traduccion se realizo utilizando LATEX,(gracias al editor online ShareLatex)1.

El lector es totalmente libre de hacer correcciones (en caso de algun error desintaxis en la traduccion de ingles a espanol) dentro de la traduccion en benefi-cio de la comunidad.

El codigo fuente de esta traduccion se encuentra en:https://github.com/LeidyAldana/ThinkingInPython

y su respectivo ejecutable en:https:

//es.slideshare.net/glud/traduccin-thinking-in-python-62703684

Adicionalmente, el autor del libro tiene la siguiente opinion frente a la tra-duccion:

“Supongo que esto es util para usted. Ese libro nunca fue terminado y loque yo escribirıa ahora es muy diferente. Pero si funciona para usted, eso estabien.”

09 de Enero de 2016. Enviado por Bruce Eckel, vıa Gmail.

1www.sharelatex.com

Page 6: Traducción Thinking in Python

Grupo GNU Linux Universidad Distrital.Semillero de Investigacion en Tecnologıa Libre.

https://glud.org/

Traducido por :Leidy Marcela Aldana Burgos.

[email protected](Estudiante de Ingenierıa de Sistemas)

Universidad Distrital Francisco Jose de Caldas.www.udistrital.edu.co

Correcciones hechas por:Jose Noe [email protected]

Docente Facultad de Ingenierıa.Coordinador Grupo GNU Linux Universidad Distrital (Ano 2017)

Bogota, Colombia.

Page 7: Traducción Thinking in Python

Prologo

El material de este libro se inicio en conjuncion con el seminariode Java que yo he dado por varios anos, un par de veces con LarryO’Brien, luego con Bill Venners. Bill y yo hemos dado muchas repeti-ciones de este seminario y a traves de los anos lo hemos cambiado yaque los dos hemos aprendido mas acerca de patrones y sobre dar elseminario.

En el proceso, ambos hemos producido informacion mas que suficiente paraque cada uno de nosotros tengamos nuestros propios seminarios, un impulso quefuertemente hemos resistido porque juntos nos hemos divertido mucho dandoel seminario. En numerosos lugares de Estados Unidos hemos dado el semi-nario, ası como en Praga (donde nosotros intentamos hacer una miniconferenciaen cada primavera, junto a otros seminarios). Ocasionalmente hemos dado unseminario en Praga, pero esto es costoso y difıcil de programar, ya que solosomos dos.

Muchos agradecimientos a las personas que han participado en estos semi-narios en los ultimos anos, y a Larry y a Bill, ya que me han ayudado a trabajaren estas ideas y a perfeccionarlas. Espero ser capaz de continuar para formary desarrollar este tipo de ideas a traves de este libro y del seminario durantemuchos anos por venir.

Este libro no parara aquı, tampoco. Originalmente, este material era partede un libro de C++, luego de un libro de Java, entonces este fue separado desu propio libro basado en Java, y finalmente despues de mucho examinar, de-cidı que la mejor manera para crear inicialmente mi escrito sobre patrones dediseno era escribir esto primero en Python (pues sabemos que Python hace unlenguaje ideal de prototipos!) y luego traducir las partes pertinentes del libro denuevo en la version de Java. He tenido la experiencia antes de probar la idea enun lenguaje mas potente, luego traducir de nuevo en otro lenguaje, y he encon-trado que esto es mucho mas facil para obtener informacion y tener la idea clara.

Ası que Pensando en Python es, inicialmente, una traduccion de Thinking inPatterns with Java, en lugar de una introduccion a Python (ya hay un montonde introducciones finas para este esplendido lenguaje). Me parece que esteprospecto es mucho mas emocionante que la idea de esforzarse a traves de otrotutorial del lenguaje (mis disculpas a aquellos que estaban esperando para esto).

i

Page 8: Traducción Thinking in Python

Introduccion

Este es un libro sobre el proyecto en el que he estado trabajando du-rante anos, ya que basicamente nunca empece a tratar de leer DesignPatterns (Patrones de Diseno) (Gamma,Helm,Johnson y Vlissides,Addison-Wesley, 1995), comunmente conocida como Gang of Four 2

o solo GoF).

Hay un capıtulo sobre los patrones de diseno en la primera edicion de Think-ing in C++, que ha evolucionado en el Volumen 2 de la segunda edicion deThinking in C++ y usted tambien encontrara un capıtulo sobre los patrones enla primera edicion de Thinking in Java. Tome ese capıtulo de la segunda edicionde Thinking in Java porque ese libro fue creciendo demasiado, y tambien porqueyo habıa decidido escribir Thinking in Patterns. Ese libro, aun no se ha ter-minado, se ha convertido en este. La facilidad de expresar estas ideas mascomplejas en Python, creo que, finalmente, me permitira decirlo todo.

Este no es un libro introductorio. Estoy asumiendo que su forma de tra-bajo ha pasado a traves de por lo menos Learning Python (por Mark Lutz yDavid Ascher; OReilly, 1999) o un texto equivalente antes de empezar este libro.

Ademas, supongo que tiene algo mas que una comprension de la sintaxis dePython. Usted debe tener una buena comprension de los objetos y de lo queellos son, incluyendo el polimorfismo.

Por otro lado, al pasar por este libro va a aprender mucho acerca de laprogramacion orientada a objetos al ver objetos utilizados en muchas situacionesdiferentes. Si su conocimiento de objetos es elemental, con este libro obtendramas experiencia en el proceso de comprension en el diseno de los objetos.

2Esta es una referencia ironica para un evento en China despues de la muerte de MaoTze Tung, cuando cuatro personas incluyendo la viuda de Mao hicieron un juego de poder, yfueron estigmatizadas por el Partido Comunista de China bajo ese nombre.

ii

Page 9: Traducción Thinking in Python

El sındrome Y2K

En un libro que tiene “tecnicas de resolucion de problemas” en su subtıtulo, valela pena mencionar una de las mayores dificultades encontradas en la progra-macion: la optimizacion prematura. Cada vez traigo este concepto a colacion,casi todo el mundo esta de acuerdo con ello. Ademas, todo el mundo parecereservar en su propia mente un caso especial “a excepcion de esto que, me heenterado, es un problema particular”.

La razon por la que llamo a esto el sındrome Y2K debo hacerlo con eseespecial conocimiento. Los computadores son un misterio para la mayorıa dela gente, ası que cuando alguien anuncio que los tontos programadores de com-putadoras habıan olvidado poner suficientes dıgitos para mantener las fechasmas alla del ano 1999, de repente todo el mundo se convirtio en un expertoen informatica ”estas cosas no son tan difıciles despues de todo, si puedo verun problema tan obvio”. Por ejemplo, mi experiencia fue originalmente en in-genierıa informatica, y empece a cabo mediante la programacion de sistemasembebidos. Como resultado, se que muchos sistemas embebidos no tienen ideaque fecha u hora es, e incluso si lo hacen esos datos a menudo no se utilizan enlos calculos importantes. Y sin embargo, me dijeron en terminos muy claros quetodos los sistemas embebidos iban a bloquearse el 01 de enero del 2000 3 .En loque puedo decir el unico recuerdo que se perdio en esa fecha en particular fueel de las personas que estaban prediciendo la perdida – que es como si nuncahubieran dicho nada de eso.

El punto es que es muy facil caer en el habito de pensar que el algoritmoparticular o la pieza de codigo que usted por casualidad entiende en parte o creeentender totalmente,

naturalmente, sera el estancamiento en su sistema, simplemente porquepuede imaginar lo que esta pasando en esa pieza de codigo y ası, que ustedpiensa, que debe ser de alguna manera mucho menos eficiente que el resto depiezas de codigo que usted no conoce. Pero a menos que haya ejecutado laspruebas reales, tıpicamente con un perfilador, realmente no se puede saber loque esta pasando. E incluso si usted tiene razon, que una pieza de codigo esmuy ineficiente, recuerde que la mayorıa de los programas gastan algo ası como90% de su tiempo en menos de 10% del codigo en el programa, ası que a menosque el trozo de codigo que usted esta pensando sobre lo que sucede al caer enese 10% no va a ser importante.

”La optimizacion prematura es la raız de todo mal.” se refiere a veces como”La ley de Knuth” (de Donald E. Knuth).

3Estas mismas personas tambien estaban convencidos de que todos los ordenadores ibana bloquearse tambien a continuacion. Pero como casi todo el mundo tenıa la experiencia desu maquina Windows estrellandose todo el tiempo sin resultados particularmente graves, estono parece llevar el mismo drama de la catastrofe inminente.

iii

Page 10: Traducción Thinking in Python

Contexto y composicion

Uno de los terminos que se vera utilizado una y otra vez en la literatura depatrones de diseno es context. De hecho, una definicion comun de un patron dediseno es: ”Una solucion a un problema en un contexto.” Los patrones GoF amenudo tienen un ”objeto de contexto” donde el programador interactua con elcliente. En cierto momento se me ocurrio que dichos objetos parecıan dominarel paisaje de muchos patrones, y ası comence preguntando de que se trataban.

El objeto de contexto a menudo actua como una pequena fachada para ocul-tar la complejidad del resto del patron, y ademas, a menudo sera el controladorque gestiona el funcionamiento del patron. Inicialmente, me parecıa que no erarealmente esencial para la implementacion, uso y comprension del patron. Sinembargo, Recorde una de las declaraciones mas espectaculares realizadas en elGoF: ”Preferirıa la composicion a la herencia.” El objeto de contexto le permiteutilizar el patron en una composicion, y eso puede ser su valor principal.

iv

Page 11: Traducción Thinking in Python

Un rapido curso para programadores

Este libro asume que usted es un programador experimentado, y esmejor si usted ha aprendido Python a traves de otro libro. Para todoslos demas, este capitulo da una rapida introduccion al lenguaje.

Vision General de Python

Esta breve introduccion es para el programador experimentado (que es lo queusted deberıa ser si esta leyendo este libro). Usted puede consultar la doc-umentacion completa de www.Python.org (especialmente la pagina HTML in-creıblemente util A Python Quick Reference), y tambien numerosos libros comoLearning Python por Mark Lutz y David Ascher(O’Reilly, 1999).

Python se conoce a menudo como un lenguaje de script, pero los lenguajesde script tienden a estar limitando, especialmente en el ambito de los problemasque ellos resuelven. Python, por otro lado, es un lenguaje de programacion quetambien soporta scripting. Es maravilloso para scripting, y puede encontrarusted mismo la sustitucion de todos sus archivos por lotes, scripts de shell, yprogramas sencillos con scripts de Python. Pero es mucho mas que un lenguajede script.

Python esta disenado para ser muy limpio para escribir y especialmente paraleer. Usted encontrara que es muy facil leer su propio codigo mucho despues deque lo ha escrito, y tambien para leer el codigo de otras personas. Esto se lograparcialmente a traves de la sintaxis limpia, al punto, pero un factor mayor en lalegibilidad del codigo es la identacion – la determinacion del alcance en Pythonviene determinada por la identacion. Por ejemplo:

#: c01 : i f . pyresponse = ” yes ”i f r e sponse == ” yes ” :

p r i n t ” a f f i r m a t i v e ”va l = 1

pr in t ” cont inu ing . . . ”#:˜

El ’#’ denota un comentario que va hasta el final de la linea, al igual queC++ y Java ‘//’ comenta.

La primera noticia es que la sintaxis basica de Python es C-ish como sepuede ver en la declaracion if. Pero en C un if, se vera obligado a utilizarparentesis alrededor del condicional, mientras que no son necesarios en Python(no reclamara si los usa de todas formas).

1

Page 12: Traducción Thinking in Python

La clausula condicional termina con dos puntos, y esto indica que lo quesigue sera un grupo de sentencias identadas, que son la parte ”entonces” de lasentencia if. En este caso hay una declaracion de ”imprimir” el cual envıa elresultado a la salida estandar, seguido de una asignacion a una variable llamadaval. La declaracion posterior no esta identada ası que ya no es parte del if.Identando puede anidar a cualquier nivel, al igual que los corchetes en C ++o Java, pero a diferencia de esos lenguajes no hay ninguna opcion (y ningunargumento) acerca de donde se colocan los corchetes – el compilador obliga alcodigo de cada uno para ser formateado de la misma manera, lo cual es una delas principales razones de legibilidad consistente de Python.

Python normalmente tiene solo una declaracion por lınea (se puede ponermas separandolos con punto y coma), por lo que el punto y coma de terminacionno es necesario. Incluso desde el breve ejemplo anterior se puede ver que ellenguaje esta disenado para ser tan simple como sea posible, y sin embargosigue siendo muy legible.

Construido en contenedores

Con lenguajes como C++ y Java, los contenedores son anadidos en las librerıasy no integros al lenguaje. En Python, la naturaleza esencial de los contene-dores para la programacion es reconocido por su construccion en el nucleo dellenguaje: ambas, (arrays:) las listas y las matrices asociativas (mapas alias,diccionarios, tablas hash) son tipos de datos fundamentales. Esto anade muchoa la elegancia del lenguaje.

Ademas, la declaracion for itera automaticamente a traves de las listas yno solo contando a traves de una secuencia de numeros. Tiene mucho sentidocuando se piensa en esto, ya que casi siempre se esta utilizando un bucle for pararecorrer una matriz o un contenedor. Python formaliza esto automaticamentehaciendo uso de for, que es un iterador el cual funciona a traves de una secuen-cia. Aquı esta un ejemplo:

#: c01 : l i s t . pyl i s t = [ 1 , 3 , 5 , 7 , 9 , 11 ]p r i n t l i s tl i s t . append (13)f o r x in l i s t :

p r i n t x#:˜

La primera linea crea una lista. Puede imprimir la lista y esto mostraraexactamente como usted la coloco (en contraste, recuerde que yo tuve que crearuna clase especial Arrays2 en Thinking in Java, 2da Edicion en orden para im-primir arrays en Java). Las listas son como contenedores de Java – usted puedeanadir elementos nuevos a estas (aquı, es usado append()) y van a cambiar

2

Page 13: Traducción Thinking in Python

automaticamente el tamano de sı mismos. La sentencia for crea un iterador xque toma cada valor de la lista.

Usted puede crear una lista de numeros con la funcion range( ), ası que siusted realmente necesita imitar el for de C, lo puede hacer.

Notece que no hay declaracion para el tipo de funcion –los nombres de losobjetos aparecen simplemente, y Python infiere el tipo de dato por la forma enque se usan. Es como si Python estuviera disenado para que usted solo necesitepulsar las teclas que sean absolutamente necesarias. Usted encontrara, despuesde haber trabajado con Python por un corto tiempo, que ha estado utilizandouna gran cantidad de ciclos cerebrales analizando punto y coma, corchetes ytodo tipo de palabras adicionales, exigidos por lenguajes alternos a Python,pero no describen en realidad lo que se suponıa que hiciera su programa.

Funciones

Para crear una funcion en Python, use la palabra clave def, seguido por el nom-bre de la funcion y la lista de argumentos, y dos puntos para empezar el cuerpode la funcion. Aquı esta el primer ejemplo convertido en una funcion:

#: c01 : myFunction . pyde f myFunction ( re sponse ) :

va l = 0i f r e sponse == ” yes ” :

p r i n t ” a f f i r m a t i v e ”va l = 1

pr in t ” cont inu ing . . . ”re turn va l

p r i n t myFunction (” no ”)p r i n t myFunction (” yes ”)#:˜

Notece que no hay informacion de tipo que identifique a la funcion – todo loque se especifica es el nombre de la funcion y los identificadores de argumentos,pero no los tipos de argumentos o el tipo de dato que devuelve. Python esun lenguaje debilmente tipado, lo que significa que pone los requisitos mınimosposibles en la introduccion de caracteres. Por ejemplo, usted podrıa pasar ydevolver diferentes tipos de datos dentro de la misma funcion:

3

Page 14: Traducción Thinking in Python

#: c01 : d i f f e r e n t R e t u r n s . pyde f d i f f e r e n t R e t u r n s ( arg ) :

i f arg == 1 :re turn ”one”

i f arg == ”one ” :re turn 1

p r in t d i f f e r e n t R e t u r n s (1 )p r i n t d i f f e r e n t R e t u r n s (” one ”)#:˜

Las unicas limitaciones sobre un objeto que se pasa a la funcion, son quela funcion puede aplicar sus operaciones a ese objeto, pero aparte de eso, nadaimporta. Aquı, la misma funcion aplica el operador ’+’para enteros y cadenas:

#: c01 : sum . pyde f sum( arg1 , arg2 ) :

r e turn arg1 + arg2p r in t sum(42 , 47)p r i n t sum( ’ spam ’ , ” eggs ”)#:˜

Cuando el operador ‘+’ es usado con cadenas, esto significa concatenacion,(si, Python soporta la sobrecarga de operadores, y esto hace un buen trabajodel mismo).

Cadenas

El ejemplo anterior tambien muestra un poco sobre manejo de cadenas dePython, que es el mejor lenguaje que he visto. Usted puede usar comillas sim-ples o dobles para representar cadenas, lo cual es muy agradable porque si ustedrodea una cadena con comillas dobles puede incluir comillas simples y viceversa:

#: c01 : s t r i n g s . pyp r in t ”That isn ’ t a horse ”p r in t ’You are not a ” Viking ” ’p r i n t ”””You ’ re j u s t pounding twococonut ha lve s toge the r .”””p r in t ’ ’ ’ ”Oh no ! ” He excla imed .” It ’ s the blemange ! ” ’ ’ ’p r i n t r ’ c :\ python\ l i b \ u t i l s ’#:˜

Tenga en cuenta que Python no fue nombrado por la serpiente, sino por la co-media que lleva por nombre Monty Python, y ası los ejemplos estan practicamenteobligados a incluir el estilo de Pythonesque.

4

Page 15: Traducción Thinking in Python

La sintaxis de comillas triples engloba todo, incluyendo saltos de lınea. Estohace que sea especialmente util y facilita las cosas como la generacion de paginasweb (Python es un lenguaje CGI (computer-generated imagery) especialmentebueno), ya que usted puede con solo comillas triples, seleccionar la pagina com-pleta que desee sin ninguna otra edicion.

La ‘r’ justo antes significa una cadena ”raw”, que toma las barras invertidas: \\, literalmente, ası que usted no tiene que poner en una barra inversa extraa fin de insertar una barra invertida literal.

La sustitucion en cadenas es excepcionalmente facil, ya que Python usa deC la sintaxis de sustitucion printf( ), pero es para todas las cadenas. Ustedsimplemente sigue la cadena con un ‘%’ y los valores para sustituir:

#: c01 : s t r ingFormatt ing . pyva l = 47pr in t ”The number i s %d” % valva l2 = 63 .4s = ” va l : %d , va l2 : %f ” % ( val , va l2 )p r i n t s#:˜

Como se puede ver en el segundo caso, si usted tiene mas de un argumentoentre parentesis (esto forma una tupla, que es una lista que no puede ser mod-ificado – tambien puede utilizar las listas regulares para multiples argumentos,pero las tuplas son tıpicas).

Todo el formato de printf() esta disponible, incluyendo el control sobre ellugar y alineacion de numeros decimales. Python tambien tiene expresionesregulares muy sofisticadas.

5

Page 16: Traducción Thinking in Python

Clases

Como todo lo demas en Python, la definicion de una clase utiliza una mınimasintaxis adicional. Usted utiliza la palabra clave class, y dentro del cuerpo seutiliza def para crear metodos. Aquı esta una clase simple:

#: c01 : S impleClass . pyc l a s s Simple :

de f i n i t ( s e l f , s t r ) :p r i n t ” I n s i d e the Simple con s t ruc to r ”s e l f . s = s t r

# Two methods :de f show ( s e l f ) :

p r i n t s e l f . sde f showMsg( s e l f , msg ) :

p r i n t msg + ’ : ’ ,s e l f . show ( ) # Ca l l i ng another method

i f name == ” main ” :# Create an ob j e c t :x = Simple (” con s t ruc to r argument ”)x . show ( )x . showMsg(”A message ”)

#:˜

Ambos metodos tienen ”self” como su primer argumento. C++ y Java,ambos tienen un primer argumento oculto en sus metodos de clase, el cualapunta al objeto para el metodo que fue llamado y se puede acceder usandola palabra clave this. Los metodos de Python tambien utilizan una referenciaal objeto actual, pero cuando usted esta definiendo un metodo debe especificarexplıcitamente la referencia como el primer argumento. Tradicionalmente, lareferencia se llama self pero usted podrıa utilizar cualquier identificador quedesee (sin embargo, si usted no utiliza self probablemente confundira a muchagente). Si necesita hacer referencia a campos en el objeto u otros metodos en elobjeto, debe utilizar self en la expresion. Sin embargo, cuando usted llama aun metodo para un objeto como en x.show( ), no le da la referencia al objeto– que esta hecho para usted.

Aquı, el primer metodo es especial, como lo es cualquier identificador quecomienza y termina con doble guion bajo. En este caso, define el constructor,el cual es llamado automaticamente cuando se crea el objeto, al igual que en C++ y en Java. Sin embargo, en la parte inferior del ejemplo se puede ver que lacreacion de un objeto se parece a una llamada a la funcion utilizando el nombrede la clase. La sintaxis disponible de Python, le hace notar que la palabra clavenew no es realmente necesaria en C ++, tampoco en Java.

6

Page 17: Traducción Thinking in Python

Todo el codigo inferior se ejecuta por la sentencia if, la cual hace un chequeopara verificar si algun llamado a name es equivalente a main . Denuevo, los dobles guiones bajos indican nombres especiales. La razon de if esque cualquier archivo tambien puede ser utilizado como un modulo de librerıadentro de otro programa (modulos se describen en breve). En ese caso, ustedsolo quiere las clases definidas, pero usted no quiere el codigo en la parte inferiordel archivo a ejecutar. Esta sentencia if en particular, solo es verdadera cuandose esta ejecutando este archivo directamente; eso es, si usted lo especifica en lalınea de comandos:

Python SimpleClass . py \newl ine

Sin embargo, si este archivo se importa como un modulo en otro programa,no se ejecuta el codigo main .

Algo que es un poco sorprendente en principio es que se definen camposdentro de los metodos, y no fuera de los metodos como C ++ o Java (si creacampos utilizando el estilo de C ++ / Java, implıcitamente se convierten encampos estaticos). Para crear un campo de objeto, solo lo nombra – usandoself – dentro de uno de los metodos (usualmente en el constructor, pero nosiempre), y se crea el espacio cuando se ejecuta ese metodo. Esto parece unpoco extrano viniendo de C++ o Java donde debe decidir de antemano cuantoespacio su objeto va a ocupar, pero resulta ser una manera muy flexible paraprogramar.

HerenciaPorque Python es debilmente tipado, esto realmente no tiene importancia

para las interfaces – lo unico que importa es la aplicacion de las operaciones a losobjetos (de hecho, la palabra reservada interface de Java podrıa ser descartadaen Python). Esto significa que la herencia en Python es diferente de la herenciaen C++ o Java, donde a menudo se hereda simplemente para establecer unainterfaz comun. En Python, la unica razon por la que hereda es para heredaruna implementacion – reutilizar el codigo de la clase base.

Si usted va a heredar de una clase, usted debe decirle a Python que in-cluya esa clase en el nuevo archivo. Python controla sus espacios de nom-bre tan audazmente como lo hace Java, y de manera similar (aunque con lapredileccion de Python por su sencillez). Cada vez que se crea un archivo, secrea implıcitamente un modulo (que es como un paquete en Java) con el mismonombre que el archivo. Por lo tanto, no se necesito la palabra clave package enPython. Cuando se desea utilizar un modulo, solo dice import y da el nombredel modulo. Python busca el PYTHONPATH del mismo modo que Java buscael CLASSPATH (pero por alguna razon, Python no tiene el mismo tipo de difi-cultades, como si las tiene Java) y lee en el archivo. Para referirse a cualquierade las funciones o clases dentro de un modulo, usted le da el nombre del modulo,un perıodo, y el nombre de la funcion o clase. Si usted no quiere preocuparse

7

Page 18: Traducción Thinking in Python

por la calificacion del nombre, puede decir:

from module import name(s)

Donde ”name(s)” puede ser una lista de nombres separada por comas.

Usted hereda una clase (o clases – Python soporta herencia multiple) enu-merando el nombre(s) : name(s) de la clase dentro de parentesis despues delnombre de la clase heredera. Tenga en cuenta que la clase Simple, la cualreside en el archivo (y por lo tanto, el modulo) llamado SimpleClass y se poneen este nuevo espacio de nombres utilizando una sentencia import:

#: c01 : Simple2 . pyfrom SimpleClass import Simplec l a s s Simple2 ( Simple ) :

de f i n i t ( s e l f , s t r ) :p r i n t ” I n s i d e Simple2 con s t ruc to r ”# You must e x p l i c i t l y c a l l# the base−c l a s s con s t ruc to r :Simple . i n i t ( s e l f , s t r )

de f d i s p l a y ( s e l f ) :s e l f . showMsg(” Cal led from d i s p l a y ( ) ” )

# Overr id ing a base−c l a s s methodde f show ( s e l f ) :

p r i n t ” Overridden show ( ) method”# Ca l l i ng a base−c l a s s method from i n s i d e# the over r idden method :Simple . show ( s e l f )

c l a s s D i f f e r e n t :de f show ( s e l f ) :

p r i n t ”Not der ived from Simple ”i f name == ” main ” :

x = Simple2 (” Simple2 con s t ruc to r argument ”)x . d i s p l a y ( )x . show ( )x . showMsg(” I n s i d e main ”)de f f ( obj ) : obj . show ( ) # One−l i n e d e f i n i t i o nf ( x )f ( D i f f e r e n t ( ) )

#:˜

Simple2 se hereda de Simple, y el constructor de la clase base es llamadoen el constructor. En display( ), showMsg( ) puede ser llamado como unmetodo de self, pero al llamar a la version de la clase base del metodo ustedesta modificando, se debe calificar por completo el nombre y pasar self comoel primer argumento, como se muestra en la llamada al constructor de la clase

8

Page 19: Traducción Thinking in Python

base. Esto tambien puede verse en la version modificada de show( ).

En main , usted puede ver (cuando corre el programa) que el construc-tor de la clase base es llamado. Tambien puede ver que el metodo showMsg()es valido en las clases derivadas, del mismo modo que se puede esperar con laherencia.

La clase Different tambien tiene un metodo llamado show(), pero estaclase no es derivada de Simple. El metodo f( ) definido en main demuestratipificacion debil: lo unico que importa es que show( ) se puede aplicar a obj,y no tiene ningun otro tipo de requisito. Usted puede ver que f( ) se puedeaplicar igualmente a un objeto de una clase derivada de Simple y a uno que nolo es, sin discriminacion. Si usted es un programador de C++, deberıa ver queel objetivo de la funcion template de C ++ es esto exactamente: proporcionartipificacion debil en un lenguaje fuertemente tipado. Por lo tanto, en Pythonautomaticamente obtendra el equivalente de plantillas – sin tener que aprenderesa sintaxis y esa semantica particularmente difıcil.

9

Page 20: Traducción Thinking in Python

El concepto de Patron

“Los patrones de diseno ayudan a aprender de los exitos de losdemas en lugar de sus propios fracasos” 4

Probablemente el avance mas importante en el diseno orientado a objetoses el movimiento “patrones de diseno”, descrito en Design Patterns (ibid)5 Eselibro muestra 23 soluciones diferentes a las clases particulares de problemas. Eneste libro, los conceptos basicos de los patrones de diseno se introduciran juntocon ejemplos. Esto deberıa abrir su apetito para leer el libro Design Patternspor Gamma, et. al., una fuente de lo que ahora se ha convertido en un elementoesencial, casi obligatorio, vocabulario para los usuarios de la programacion ori-entada a objetos.

La ultima parte de este libro contiene un ejemplo del proceso de evolucion deldiseno, comenzando con una solucion inicial y moviendose a traves de la logicay el proceso de la evolucion de la solucion a los disenos mas apropiados. Elprograma mostrado (una simulacion de clasificacion de basura) ha evolucionadocon el tiempo, puede mirar en dicha evolucion como un prototipo de la formaen que su propio diseno puede comenzar como una solucion adecuada a unproblema particular y evolucionar hacia un enfoque flexible para una clase deproblemas.

¿Que es un Patron?

Inicialmente, usted puede pensar en un patron como una forma especialmenteinteligente y perspicaz de la solucion de una determinada clase de problemas.Es decir, parece que muchas personas han trabajado todos los angulos de unproblema y han llegado a la solucion mas general y flexible para ello. El prob-lema podrıa ser uno que usted ha visto y ha resuelto antes, pero su solucionprobablemente no tenıa el conjunto de complementos que usted verıa incorpo-rados en un patron.

Aunque se les llama ”patrones de diseno”, ellos realmente no estan atadosal ambito del diseno. Un patron parece estar al margen de la forma tradicionalde pensar en el analisis, diseno, e implementacion. En lugar, un patron encarnauna idea completa dentro de un programa, y por lo tanto a veces puede apareceren la fase de analisis o de la fase de diseno de alto nivel. Esto es interesanteporque un patron tiene una aplicacion directa en el codigo y por lo que podrıano esperar que aparezca antes del diseno o implementacion de bajo nivel (dehecho, es posible que no se de cuenta de que se necesita un patron particularhasta llegar a esas fases).

4De Mark Johnson5Pero cuidado: los ejemplos estan en C ++.

10

Page 21: Traducción Thinking in Python

El concepto basico de un patron tambien puede ser visto como el conceptobasico de diseno del programa; es decir, la adicion de una capa de abstraccion.Cuando usted abstrae algo, usted esta aislando detalles particulares, y una delas motivaciones mas convincentes detras de esto es separar cosas que cambiande las cosas que al final quedan igual. Otra manera de poner esto es que unavez usted encuentra alguna parte de su programa que es probable que cambiepor una razon u otra, usted querra mantener esos cambios con respecto a lapropagacion de otros cambios a traves de su codigo. Esto no solo hace el codigomucho mas economico de mantener, pero tambien resulta que por lo general esmas facil de entender (lo cual resulta en menores costes).

A menudo, la parte mas difıcil de desarrollar un diseno elegante y sencillode mantener, es en el descubrimiento de lo que yo llamo “el vector del cambio.”(Aquı, “vector” se refiere al gradiente maximo y no una clase contenedora.) Estosignifica encontrar la cosa mas importante que cambia en su sistema, o dichode otra manera, descubrir donde esta su mayor valor. Una vez que descubra elvector del cambio, usted tiene el punto focal alrededor del cual estructurar sudiseno.

Ası que el objetivo de los patrones de diseno es identificar los cambios en sucodigo. Si se mira de esta manera, usted ha estado viendo algunos patrones dediseno que ya estan en este libro. Por ejemplo, la herencia puede ser pensadacomo un patron de diseno (aunque uno puesto en ejecucion por el compilador).Esto le permite expresar diferencias del comportamiento (esto cambia) de losobjetos que todos tienen la misma interfaz (permaneciendo igual). La com-posicion tambien puede ser considerada un patron, ya que le permite cambiar— dinamica o estaticamente — los objetos que implementan la clase, y por lotanto la forma en que funciona la clase.

Otro patron que aparece en Design Patterns es el iterador, el cual ha sidoimplıcitamente dispuesto en el bucle for desde el comienzo del lenguaje, y fueintroducido como una caracterıstica explıcita en Python 2.2. Un iterador lepermite ocultar la implementacion particular del contenedor como usted estapasando a traves de los elementos y seleccionando uno por uno. Ası, puedeescribir codigo generico que realiza una operacion en todos los elementos en unasecuencia sin tener en cuenta la forma en que se construye la secuencia. Ası, sucodigo generico se puede utilizar con cualquier objeto que pueda producir uniterador.

Taxonomıa Patron

Uno de los acontecimientos que se produjeron con el aumento de patrones dediseno es lo que podrıa ser considerado como la “contaminacion” del termino –la gente ha empezado a utilizar el termino para definir casi cualquier cosa ensinonimo de “bueno”. Despues de alguna ponderacion, yo he llegado con una

11

Page 22: Traducción Thinking in Python

especie de jerarquıa que describe una sucesion de diferentes tipos de categorıas:

1. Idioma: Como escribimos codigo en un lenguaje particular, para hacereste tipo particular de cosas. Esto podrıa ser algo tan comun como laforma en que codifica el proceso de paso a paso a traves de una matriz enC (y no se corre hasta el final).

2. Diseno Especifico: la solucion que se nos ocurrio para resolver este prob-lema en particular. Esto podrıa ser un diseno inteligente, pero no intentaser general.

3. Diseno Estandar: una manera de resolver este tipo de problema. Un disenoque se ha vuelto mas general, tıpicamente a traves de la reutilizacion.

4. Patron de Diseno: como resolver problemas similares. Esto normalmentesolo aparece despues de la aplicacion de un diseno estandar un numero deveces, y despues de ver un patron comun a traves de estas aplicaciones.

Siento que esto ayuda a poner las cosas en perspectiva, y para mostrar dondealgo podrıa encajar. Sin embargo, esto no dice que uno es mejor que otro. Notiene sentido tratar de tomar todas las soluciones de problemas y generalizarlasa un patron de diseno – no es un buen uso de su tiempo, y no se puede forzar eldescubrimiento de patrones de esa manera; ellos tienden a ser sutiles y aparecencon el tiempo.

Tambien se podrıa argumentar a favor de la inclusion del Analysis Pattern(Patron Analisis) y Architectural Pattern (Patron arquitectonico) en esta tax-onomıa.

12

Page 23: Traducción Thinking in Python

Estructuras de Diseno

Una de las luchas que he tenido con los patrones de diseno es su clasificacion- A menudo he encontrado el enfoque GoF (Gang of Four, mencionado en laintroduccion) a ser demasiado oscuro, y no siempre muy servicial. Ciertamente,los patrones creacionales son bastante sencillos: ¿como se van a crear sus ob-jetos? Esta es una pregunta que normalmente necesita hacerse, y el nombreque lleva directamente a ese grupo de patrones. Pero encuentro Structural andBehavioral : Estructurales y de comportamiento a ser distinciones mucho menosutiles. No he sido capaz de mirar un problema y decir ”Claramente, se necesitaun patron estructural aquı”, por lo que la clasificacion no me lleva a una solucion.

He trabajado por un tiempo con este problema, primero senalando que laestructura subyacente de algunos de los patrones GoF son similares entre sı, ytratando de desarrollar relaciones basadas en esa semejanza. Si bien este fue unexperimento interesante, no creo que produjo gran parte de su uso en el final,porque el punto es resolver problemas, por lo que un enfoque util se vera enel problema a resolver y tratar de encontrar relaciones entre el problema y lasposibles soluciones.

Con ese fin, he empezado a intentar reunir las estructuras basicas de diseno,y tratar de ver si hay una manera de relacionar aquellas estructuras a los diver-sos patrones de diseno que aparecen en sistemas bien pensados. Corrientemente,solo estoy tratando de hacer una lista, pero eventualmente espero hacer pasoshacia la conexion de estas estructuras con los patrones (o Puedo llegar con un en-foque totalmente diferente – esta se encuentra todavıa en su etapa de formacion)

Aquı 6 esta la lista actual de candidatos, solo algunos de los cuales llegaranal final de la lista. Sientase libre de sugerir otros, o posiblemente, las relacionescon los patrones.

• Encapsulacion: auto contencion y que incorpora un modelo de uso.

• Concurrencia

• Localizacion

• Separacion

• Ocultacion

• Custodiando

• Conector

• Obstaculo/valla

6Esta lista incluye sugerencias de Kevlin Henney, David Scott, y otros.

13

Page 24: Traducción Thinking in Python

• Variacion en el Comportamiento

• Notificacion

• Transaccion

• Espejo: “Capacidad para mantener un universo paralelo(s) en el pasocon el mundo dorado”

• Sombra: “Sigue su movimiento y hace algo diferente en un medio difer-ente” (Puede ser una variacion de Proxy).

Principios de Diseno

Cuando puse un concurso de ideas en mi boletın de noticias7, una serie de sug-erencias regresaron, lo cual resulto ser muy util, pero diferente a la clasificacionanterior, y me di cuenta de que una lista de principios de diseno es al menostan importante como estructuras de diseno, pero por una razon diferente: estospermiten hacer preguntas sobre su diseno propuesto, para aplicar las pruebasde calidad.

• Principio de menor asombro: (no se sorprenda).

• Hacer comun las cosas faciles, y raras las cosas posibles

• Consistencia: Debido a Python, ha sido muy claro para mı especial-mente: las normas mas al azar que se acumulan sobre el programador, lasreglas que no tienen nada que ver con la solucion del problema en cuestion,el programador mas lento puede producir. Y esto no parece ser un factorlineal, sino una exponencial.

• Ley de Demeter: tambien denominado ”No hables con extranos”. Unobjeto solo debe hacer referencia a sı mismo, sus atributos, y los argumen-tos de sus metodos.

• Sustraccion: un diseno se termina cuando no puede llevar nada maslejos8.

• Simplicidad antes de generalidad:9 (Una variacion de Occam’s Razor,que dice que ”la solucion mas simple es el mejor”). Un problema comunque encontramos en la estructura es que estan disenados para ser de usogeneral sin hacer referencia a los sistemas reales. Esto lleva a una increıblevariedad de opciones que estan a menudo sin uso, mal uso o simplemente

7Una publicacion de correo electronico gratuito. Ver www.BruceEckel.com para suscribirse.8Esta idea se atribuye generalmente a Antoine de St. Exupery de The Little Prince : El

principito ”La perfection est atteinte non quand il ne reste rien a ajouter, mais quand il nereste rien a enlever,” o ”La perfeccion se alcanza no cuando no hay nada mas que anadir, sinocuando no hay nada mas que eliminar”.

9A partir de un correo electronico de Kevlin Henney.

14

Page 25: Traducción Thinking in Python

no es util. Sin embargo, la mayorıa de los desarrolladores trabajan ensistemas especıficos, y la busqueda de la generalidad no siempre sirvebien. La mejor ruta para la generalidad es a traves de la comprension deejemplos especıficos bien definidos. Por lo tanto, este principio actua comoel punto decisivo entre alternativas de diseno de otro modo igualmenteviables. Por supuesto, es totalmente posible que la solucion mas simple esla mas general.

• La reflexividad: (mi termino sugerido). Una abstraccion por clase, unaclase por la abstraccion. Tambien podrıa ser llamado Isomorfismo.

• Independencia o Ortogonalidad. Expresar ideas independientes deforma independiente. Esto complementa Separacion, Encapsulacion yVariacion, y es parte del Bajo-Acoplamiento-Alta-Cohesion.

• Una vez y solo una vez: Evitar la duplicacion de la logica y de laestructura donde la duplicacion no es accidental, es decir, donde ambaspiezas de codigo expresan la misma intencion por la misma razon.

En el proceso de lluvia de ideas para una idea, espero llegar con un pequenopunado de ideas fundamentales que se puede mantener en su cabeza mientrasusted analiza un problema. Ahora bien, otras ideas que vienen de esta listapuede terminar siendo utiles como una lista de verificacion mientras recorre yanaliza su diseno.

Singleton

Posiblemente el patron de diseno mas simple es el Singleton, el cual es una man-era de proporcionar un y solo un objeto de un tipo particular. Para lograr esto,usted debe tomar el control de la creacion de objetos fuera de las manos delprogramador. Una forma comoda de hacerlo es delegar una sola instancia deuna clase interna privada anidada:

#: c01 : S ing l e tonPat te rn . py

c l a s s OnlyOne :c l a s s OnlyOne :

de f i n i t ( s e l f , arg ) :s e l f . va l = arg

de f s t r ( s e l f ) :r e turn ‘ s e l f ‘ + s e l f . va l

i n s t anc e = Nonede f i n i t ( s e l f , arg ) :

i f not OnlyOne . i n s t ance :OnlyOne . i n s t ance = OnlyOne . OnlyOne ( arg )

e l s e :

15

Page 26: Traducción Thinking in Python

OnlyOne . i n s t ance . va l = argde f g e t a t t r ( s e l f , name ) :

r e turn g e t a t t r ( s e l f . in s tance , name)

x = OnlyOne ( ’ sausage ’ )p r i n t xy = OnlyOne ( ’ eggs ’ )p r i n t yz = OnlyOne ( ’ spam ’ )p r i n t zp r i n t xp r in t yp r in t ‘x ‘p r i n t ‘y ‘p r i n t ‘ z ‘output = ’ ’ ’< main . OnlyOne in s t anc e at 0076B7AC>sausage< main . OnlyOne in s t anc e at 0076B7AC>eggs< main . OnlyOne in s t anc e at 0076B7AC>spam< main . OnlyOne in s t anc e at 0076B7AC>spam< main . OnlyOne in s t anc e at 0076B7AC>spam< main . OnlyOne in s t anc e at 0076C54C>< main . OnlyOne in s t anc e at 0076DAAC>< main . OnlyOne in s t anc e at 0076AA3C>’ ’ ’#:˜

Debido a que la clase interna se llama con una doble raya al piso, esta esprivada por lo que el usuario no puede acceder directamente a ella. La claseinterna contiene todos los metodos que normalmente se ponen en la clase si nose va a ser un singleton, y luego se envuelve en la clase externa la cual controlala creacion mediante el uso de su constructor. La primera vez que usted creaun OnlyOne, inicializa instance, pero despues de eso solo la ignora.

El acceso viene a traves de la delegacion, usando el metodo getattr ( )para redireccionar las llamadas a la instancia unica. Se puede ver en la salidaque a pesar de que parece que se han creado multiples objetos, el mismo objeto

OnlyOne se utiliza para ambos. Las instancias de OnlyOne son distintaspero todas ellas representan al mismo objeto OnlyOne.

Tenga en cuenta que el enfoque anterior no le restringe a la creacion de unsolo objeto. Esta es tambien una tecnica para crear un grupo limitado de obje-tos. En esa situacion, sin embargo, usted puede encontrarse con el problema decompartir objetos en el grupo. Si esto es un problema, puede crear una solucioninvolucrando una comprobacion y un registro de los objetos compartidos.

16

Page 27: Traducción Thinking in Python

Una variacion en esta tecnica utiliza el metodo de la clase new anadidoen Python 2.2:

#: c01 : NewSingleton . py

c l a s s OnlyOne ( ob j e c t ) :c l a s s OnlyOne :

de f i n i t ( s e l f ) :s e l f . va l = None

de f s t r ( s e l f ) :r e turn ‘ s e l f ‘ + s e l f . va l

i n s t anc e = Nonede f new ( c l s ) : # new always a classmethod

i f not OnlyOne . i n s t ance :OnlyOne . i n s t ance = OnlyOne . OnlyOne ( )

re turn OnlyOne . i n s t ancede f g e t a t t r ( s e l f , name ) :

r e turn g e t a t t r ( s e l f . in s tance , name)de f s e t a t t r ( s e l f , name ) :

r e turn s e t a t t r ( s e l f . in s tance , name)

x = OnlyOne ( )x . va l = ’ sausage ’p r i n t xy = OnlyOne ( )y . va l = ’ eggs ’p r i n t yz = OnlyOne ( )z . va l = ’ spam ’p r in t zp r i n t xp r in t y#<hr>output = ’ ’ ’< main . OnlyOne in s t anc e at 0x00798900>sausage< main . OnlyOne in s t anc e at 0x00798900>eggs< main . OnlyOne in s t anc e at 0x00798900>spam< main . OnlyOne in s t anc e at 0x00798900>spam< main . OnlyOne in s t anc e at 0x00798900>spam’ ’ ’#:˜

Alex Martelli hace la observacion de que lo que realmente queremos con unSingleton es tener un unico conjunto de datos de estado de todos los objetos.Es decir, puede crear tantos objetos como desee y, siempre y cuando todos serefieren a la misma informacion de estado y luego lograr el efecto de Singleton.

17

Page 28: Traducción Thinking in Python

El logra esto con lo que el llama Borg10, lo cual se logra configurando todas lasdict s a la misma pieza estatica de almacenamiento:

#: c01 : BorgSing leton . py# Alex Marte l l i ’ s ’ Borg ’

c l a s s Borg :s h a r e d s t a t e =

de f i n i t ( s e l f ) :s e l f . d i c t = s e l f . s h a r e d s t a t e

c l a s s S ing l e ton ( Borg ) :de f i n i t ( s e l f , arg ) :

Borg . i n i t ( s e l f )s e l f . va l = arg

de f s t r ( s e l f ) : r e turn s e l f . va l

x = S ing l e ton ( ’ sausage ’ )p r i n t xy = S ing l e ton ( ’ eggs ’ )p r i n t yz = S ing l e ton ( ’ spam ’ )p r i n t zp r i n t xp r in t yp r in t ‘x ‘p r i n t ‘y ‘p r i n t ‘ z ‘output = ’ ’ ’sausageeggsspamspamspam< main . S ing l e ton in s t anc e at 0079EF2C>< main . S ing l e ton in s t anc e at 0079E10C>< main . S ing l e ton in s t anc e at 00798F9C>’ ’ ’#:˜

Esto tiene un efecto identico como SingletonPattern.py, pero este es maselegante. En el primer caso, deben conectarse en el comportamiento Singleton acada una de sus clases, pero Borg esta disenado para ser reutilizado facilmente

10Del programa de television Star Trek: The Next Generation. Los Borg son un colectivocolmena-mente: ”todos somos uno.”

18

Page 29: Traducción Thinking in Python

a traves de la herencia.

Otras dos formas interesantes para definir singleton11 incluyen envolviendouna clase y utilizando metaclases. El primer enfoque podrıa ser pensado comoun decorador de clase (decoradores se definiran mas adelante en el libro), porquelleva la clase de interes y anade funcionalidad a ella envolviendola en otra clase:

#: c01 : S ing l e tonDecorator . py

c l a s s S ing l e tonDecorator :de f i n i t ( s e l f , k l a s s ) :

s e l f . k l a s s = k l a s ss e l f . i n s t anc e = None

de f c a l l ( s e l f ,∗ args ,∗∗ kwds ) :i f s e l f . i n s t ance == None :

s e l f . i n s t anc e = s e l f . k l a s s (∗ args ,∗∗ kwds )re turn s e l f . i n s t anc e

c l a s s foo : passfoo = Sing l e tonDecorator ( foo )

x=foo ( )y=foo ( )z=foo ( )x . va l = ’ sausage ’y . va l = ’ eggs ’z . va l = ’ spam ’p r in t x . va lp r i n t y . va lp r i n t z . va lp r i n t x i s y i s z#:˜

[[ Descripcion ]]

El segundo enfoque utiliza metaclases, un tema que aun no entiendo pero elcual se ve muy interesante y poderoso ciertamente (tenga en cuenta que Python2.2 ha mejorado / simplificado la sintaxis metaclase, y por lo que este ejemplopuede cambiar):

#: c01 : S ing letonMetaClass . pyc l a s s S ing letonMetaClass ( type ) :

de f i n i t ( c l s , name , bases , d i c t ) :super ( SingletonMetaClass , c l s )\

11Sugerido por Chih Chung Chang.

19

Page 30: Traducción Thinking in Python

. i n i t (name , bases , d i c t )o r i g i n a l n e w = c l s . newde f my new( c l s ,∗ args ,∗∗ kwds ) :

i f c l s . i n s t anc e == None :c l s . i n s t anc e = \

o r i g i n a l n e w ( c l s ,∗ args ,∗∗ kwds )re turn c l s . i n s t ance

c l s . i n s t anc e = Nonec l s . new = stat icmethod (my new)

c l a s s bar ( ob j e c t ) :m e t a c l a s s = Sing letonMetaClass

de f i n i t ( s e l f , va l ) :s e l f . va l = va l

de f s t r ( s e l f ) :r e turn ‘ s e l f ‘ + s e l f . va l

x=bar ( ’ sausage ’ )y=bar ( ’ eggs ’ )z=bar ( ’ spam ’ )p r i n t xp r in t yp r in t zp r i n t x i s y i s z#:˜

[[Descripcion prolongada, detallada, informativa de lo que son metaclases ycomo funcionan, por arte de magia insertado aquı]]

Ejercicio

Modificar BorgSingleton.py para que utilice un metodo new () de clase.

Clasificacion de Patrones

El libro Design Patterns discute 23 patrones diferentes, clasificados en trespropositos (los cuales giran en torno al aspecto particular que puede variar).Los tres propositos son:

1. Creacional: como se puede crear un objeto. Esto a menudo involucrael aislamiento de los detalles en la creacion de objetos, por lo que su codigono depende de que tipos de objetos existen y por lo tanto no tiene que cam-biarse cuando se agrega un nuevo tipo de objeto. El ya mencionado Singletones clasificado como un patron creacional, y mas adelante en este libro usted veraejemplos de Factory Method and Prototype.

20

Page 31: Traducción Thinking in Python

2. Estructural: disenar objetos para complementar las limitaciones delproyecto. Estos funcionan con la forma en que los objetos estan conectados conotros objetos para asegurar que los cambios en el sistema no requieren cambiosen dichas conexiones.

3. Comportamental: corresponde a los objetos que manejan tipos partic-ulares de acciones dentro de un programa. Estos encapsulan procesos que usteddesea realizar, tales como la interpretacion de un lenguaje, el cumplimiento deuna solicitud, el movimiento a traves de una secuencia (como en un iterador),o la implementacion un algoritmo. Este libro contiene ejemplos de los patronesObserver : Observador y Visitor : visitante.

El libro Design Patterns tiene una seccion para cada uno de sus 23 patronesjunto con uno o mas ejemplos para cada uno, normalmente en C ++, pero al-gunos en Smalltalk. (Usted encontrara que esto no importa demasiado puestoque puedes traducir facilmente los conceptos de cualquier lenguaje en Python.)En este libro no se repetiran todos los patrones mostrados en Design Patternsya que el libro se destaca por su cuenta y deberıan ser estudiados por separado.En lugar de ello, este libro le dara algunos ejemplos que deberıan proporcionarleuna sensacion decente de para que son los patrones y por que son tan impor-tantes.

Despues de algunos anos de trabajar en el tema,me di cuenta que los mis-mos patones utilizan principios basicos de organizacion, aparte de (y mas fun-damental que) los descritos en Design Patterns. Estos principios se basan en laestructura de las implementaciones, que es donde he visto grandes similitudesentre los patrones (mas que aquellos expresados en Design Patterns). Aunquenosotros generalmente tratamos de evitar la implementacion en favor de la in-terfaz, he encontrado que a menudo es mas facil de pensar, y especialmente paraaprender, acerca de los patrones en terminos de estos principios estructurales.Este libro tratara de presentar los patrones basados en su estructura en lugarde las categorıas presentadas en Design Patterns.

El desafıo para el desarrollo

Problemas del desarrollo, el proceso UML y la programacion extrema.

¿Es la evaluacion valiosa? La Capacidad de la inmadurez del modelo:

Wiki Pagina: http://c2.com/cgi-bin/wiki?CapabilityImMaturityModel

Articulo: http://www.embedded.com/98/9807br.htm

Investigacion programacion en parejas:

21

Page 32: Traducción Thinking in Python

http://collaboration.csc.ncsu.edu/laurie/

Ejercicios

1. SingletonPattern.py siempre crea un objeto, incluso si nunca se ha uti-lizado. Modifique este programa para usar lazy initialization, de tal forma queel objeto singleton sea creado la primera vez que se necesite.

2. Usando SingletonPattern.py como punto de partida, cree una claseque gestione una serie fija de sus propios objetos. Asuma que los objetos sonlas conexiones de base de datos y usted tiene solamente una licencia para usaruna cantidad fija de estos objetos en cualquier momento.

22

Page 33: Traducción Thinking in Python

2: Pruebas Unitarias

Una de las importantes realizaciones recientes es el valor impresio-nante de las pruebas unitarias.

Este es el proceso de construccion de pruebas integradas en todoel codigo que usted crea, y ejecuta esas pruebas cada vez que haceuna construccion. Es como si usted estuviera ampliando el compi-lador, diciendole mas acerca de lo que se supone que su programahace. De esa manera, el proceso de construccion puede comprobarsobre el para algo mas que errores de sintaxis, ya que usted tambienle puede ensenar a corregir errores semanticos.

Los Lenguajes de programacion estilo C y C++ en particular,han valorado tıpicamente el rendimiento sobre la seguridad de pro-gramacion. La razon de que el desarrollo de programas en Java seamucho mas rapido que en C ++, es debido a la red de seguridadde Java: caracterısticas como el mejor tipo de verificacion, excep-ciones forzadas y recoleccion de basura. Mediante la integracionde la unidad de pruebas en su proceso de construccion, usted estaampliando esta red de seguridad, y el resultado es que usted puedeacelerar su desarrollo. Tambien puede ser mas audaz en los cambiosque realice, y editar mas facilmente su codigo cuando usted des-cubra defectos de diseno o de implementacion, y en general, hacerun producto mejor y mas rapido.

Las pruebas unitarias no se consideran generalmente un patronde diseno; de hecho, podrıan ser consideradas un ”patron de desar-rollo”, pero tal vez ya hay suficientes frases ”patron” en el mundo.Su efecto sobre el desarrollo es tan significativo que se va a utilizaren todo este libro.

Mi propia experiencia con las pruebas unitarias empezo cuandome di cuenta que todos los programas en un libro debe ser extraıdode forma automatica y organizado en un arbol de codigo fuente,junto con makefiles12 apropiados (o alguna tecnologıa equivalente)ası que usted solo podrıa escribir make para construir todo el arbol.El efecto de este proceso sobre la calidad del codigo de este libro era

12 https://en.wikipedia.org/wiki/Makefile

23

Page 34: Traducción Thinking in Python

tan inmediato e importante que pronto se convirtio (en mi mente) enun requisito para cualquier libro de programacion — ¿como puedeconfiar en el codigo que usted no compila?. Tambien descubrı que siquerıa hacer cambios radicales, podrıa hacerlo ası mediante el pro-ceso de buscar y reemplazar en todo el libro, y tambien modificarel codigo a voluntad. Yo sabıa que si se introdujese una falla, elextractor de codigo y los makefiles podrıan eliminarla.

Ası los programas llegaron a ser mas complejos, sin embargo,tambien me di cuenta de que habıan serios vacios en mi sistema.Siendo capaz de compilar con exito programas es claramente unprimer paso importante, y para un libro publicado parecerıa bas-tante revolucionario — por lo general debido a las presiones de lapublicacion, es bastante tıpico abrir al azar un libro de programaciony descubrir un defecto de codificacion. Ahora bien, Seguı recibiendomensajes de los lectores reportando problemas semanticos en micodigo (en Thinking in Java). Estos problemas solo podıan ser des-cubiertos mediante la ejecucion del codigo. Naturalmente, Entendıesto y habıa tomado algunos pasos vacilantes tempranos hacia laimplementacion de un sistema que realizarıa pruebas de ejecucionautomatica, pero yo habıa sucumbido a las presiones de la pub-licacion, todo el tiempo sabiendo que definitivamente habıa algoequivocado con mi proceso y que esto se me devolverıa a traves deinformes de errores vergonzosos (en el mundo del codigo abierto, laverguenza es uno de los principales factores de motivacion hacia elaumento de la calidad de su codigo!).

El otro problema fue que me faltaba una estructura para el sis-tema de pruebas. Eventualmente, empece escuchando acerca de laspruebas unitarias y JUnit13, lo cual proporciono una base para unaestructura de prueba. No obstante, aunque JUnit esta destinado ahacer facil la creacion de codigo de prueba, querıa ver si podıa hac-erlo aun mas facil, aplicando el principio de Programacion Extremade ”Hacer la cosa mas simple que podrıa posiblemente funcionar”como punto de partida, y luego la evolucion del sistema como de-mandas de uso (Ademas, querıa tratar de reducir la cantidad decodigo de prueba, en un intento de ajustarse a una mayor funcionali-dad en menos codigo para presentaciones en pantalla). Este capıtulo

13http://www.junit.org

24

Page 35: Traducción Thinking in Python

es el resultado.

Escribir las pruebas primero

Como ya mencione, uno de los problemas que encontre — que lamayorıa de la gente encuentra, a resolver — fue someterse a laspresiones de la editorial y como resultado eliminando algunas prue-bas en el transcurso de la edicion. Esto es facil de hacer si ustedsigue adelante y escribe el codigo de su programa porque hay unapequena voz que te dice que, despues de todo, lo has conseguido,ahora lo tienes funcionando, y no serıa mas interesante, util y opor-tuno que seguir adelante y escribir la otra parte (siempre podemosvolver atras y escribir las pruebas posteriormente). Como resultado,las pruebas asumen menos importancia, como hacen a menudo enun proyecto de desarrollo.

La respuesta a este problema, que encontre la primera vez de-scrito en Extreme Programming Explained, es escribir las pruebasantes de escribir el codigo. Puede parecer que esto fuerza artificial-mente las pruebas a la vanguardia del proceso de desarrollo, pero loque hace es dar pruebas de valor adicional, suficientes para que seaesencial. Si escribe las pruebas primero, usted:

1. Describe lo que se supone que el codigo hace, no con alguna her-ramienta grafica externa pero con el codigo que realmente es-tablece la especificacion en concreto, en terminos verificables.

2. Proporciona un ejemplo de como se debe utilizar el codigo; denuevo, esto es un funcionamiento, ejemplo probado, mostrandonormalmente todas las llamadas a metodos importantes, en lugarde solo una descripcion academica de una librerıa.

3. Provee una forma de verificacion cuando se termina el codigo(cuando todas las pruebas se ejecutan correctamente).

Ası, si usted escribe las pruebas primero entonces la prueba seconvierte en una herramienta de desarrollo, no solo un paso de ver-ificacion que se puede omitir si se siente seguro con el codigo queha escrito. (un consuelo, he encontrado, que es usualmente equivo-cado).

25

Page 36: Traducción Thinking in Python

Usted puede encontrar argumentos convincentes en Extreme Pro-gramming Explained, como ”escribir pruebas primero” es un princi-pio fundamental de XP14. Si usted no esta convencido que necesitaadoptar cualquiera de los cambios sugeridos por XP, tenga en cuentaque conforme a los estudios del Instituto de Ingenierıa de Software(SEI), casi el 70% de las organizaciones de software se ha quedadoatascado en los dos primeros niveles de escala de sofisticacion delSEI: caos, y un poco mejor que el caos. Si no cambia nada mas,realice pruebas automatizadas.

Simples pruebas de Python

Comprobacion de validez de una prueba rapida de los programas eneste libro, y anexar la salida de cada programa (como un string :una cadena) a su listado:

#: SanityCheck . pyimport s t r i ng , glob , os# Do not in c lude the f o l l o w i n g in the automatic# t e s t s :exc lude = ( ” SanityCheck . py ” , ”BoxObserver . py ” , )

de f v i s i t o r ( arg , dirname , names ) :d i r = os . getcwd ( )os . chd i r ( dirname )t ry :

pyprogs = [ p f o r p in glob . g lob ( ’ ∗ . py ’ )i f p not in exc lude ]

i f not pyprogs : r e turnp r i n t ’ [ ’ + os . getcwd ( ) + ’ ] ’f o r program in pyprogs :

p r i n t ’\ t ’ , programos . system ( ”python %s > tmp” % program )

f i l e = open ( program ) . read ( )output = open ( ’ tmp ’ ) . read ( )# Append output i f i t ’ s not a l r eady the re :

14Programacion extrema,https://es.wikipedia.org/wiki/Programaci\unhbox\voidb@x\bgroup\let\unhbox\voidb@x\setbox\@tempboxa\hboxo\global\mathchardef\accent@

spacefactor\spacefactor\accent19o\egroup\spacefactor\accent@spacefactorn_extrema

26

Page 37: Traducción Thinking in Python

i f f i l e . f i n d (” output = ’ ’ ’ ” ) == −1 and \l en ( output ) > 0 :d i v i d e r = ’ #’ ∗ 50 + ’\n ’f i l e = f i l e . r e p l a c e ( ’ #’ + ’ : ˜ ’ , ’#<hr>\n ’ )f i l e += ” output = ’ ’ ’\n” + \

open ( ’ tmp ’ ) . read ( ) + ” ’ ’ ’\n”open ( program , ’w ’ ) . wr i t e ( f i l e )

f i n a l l y :os . chd i r ( d i r )

i f name == ” main ” :os . path . walk ( ’ . ’ , v i s i t o r , None )

#:˜

Solo tiene que ejecutar esto desde el directorio raız de los lista-dos de codigo para el libro; ello descendera en cada subdirectorioy ejecutar el programa allı. Una forma sencilla de comprobar lascosas es redirigir la salida estandar a un archivo, entonces, si haycualquier error seran la unica cosa que aparece en la consola durantela ejecucion del programa.

Un framework muy simple

Como mencione, un objetivo primario de este codigo es hacer laescritura de codigo de pruebas unitarias muy simple, incluso massimple que con JUnit. Como otras necesidades se descubren duranteel uso de este sistema, entonces la funcionalidad puede ser anadida,pero para empezar con el framework solo podrıa proporcionar unamanera de ejecutar y crear pruebas, e reportar fallos si hay algunrompimiento (el exito no producira resultados distintos de la sal-ida normal que puede ocurrir durante la ejecucion de la prueba).Mi uso previsto de este framework es en makefiles, y make abortasi hay un valor de retorno distinto de cero de la ejecucion de uncomando. El proceso de construccion consistira en la compilacionde los programas y la ejecucion de pruebas unitarias, y si make espositivo durante el recorrido, entonces el sistema sera validado, delo contrario, se anulara en el lugar de la falla, El mensaje de errorreportara la prueba que fallo, pero nada mas, ası que usted puedeproveer granularidad, escribiendo tantas pruebas como quiera, cadauna cubriendo tanto o tan poco como usted lo crea necesario.

27

Page 38: Traducción Thinking in Python

En algun sentido, este framework proporciona un lugar alterna-tivo para todas aquellas declaraciones de ”print” que he escrito yposteriormente borrados a traves de los anos.

Para crear un conjunto de pruebas, usted comienza haciendo unaclase interna static dentro de la clase que desea probar (su codigode prueba tambien puede probar otras clases; usted decide). Estecodigo de prueba se distingue heredando de UnitTest:

# t e s t : UnitTest . py# The ba s i c un i t t e s t i n g c l a s s

c l a s s UnitTest :s t a t i c S t r ing tes t IDs t a t i c L i s t e r r o r s = ArrayList ( )

# Overr ide cleanup ( ) i f t e s t ob j e c t# c r e a t i o n a l l o c a t e s non−memory# r e s o u r c e s that must be c l eaned up :de f c leanup ( s e l f ) :# Ver i fy the truth o f a cond i t i on :pro tec ted f i n a l void a f f i r m ( boolean cond i t i on )

i f ( ! c ond i t i on )e r r o r s . add (” f a i l e d : ” + test ID )

# : ˜

El unico metodo de prueba [[Hasta ahora]] es affirm( )15, elcual es protected de modo que pueda ser utilizado de la clase quehereda. Todo lo que este metodo hace es verificar que algo es true.Si no, anade un error a la lista, informando que la prueba actual(establecida por la static testID, que es fijado por el programa depruebas de funcionamiento que se vera mas adelante) ha fracasado.Aunque no se trata de una gran cantidad de informacion — es posi-ble que tambien desee tener el numero de lınea, lo que podrıa serextraıdo de una excepcion — puede ser suficiente para la mayorıade las situaciones.

15Yo habıa llamado originalmente esta assert(), pero esa palabra llego a ser reservada enel JDK 1.4 cuando se anadieron las afirmaciones al lenguaje.

28

Page 39: Traducción Thinking in Python

A diferencia de JUnit, (que usa los metodos setUp( ) y tear-Down()), los objetos de prueba se elaboraran usando la construccionordinaria de Python. Usted define los objetos de prueba mediantela creacion de ellos como miembros de la clase ordinaria de la clasede prueba, y un nuevo objeto de clase de prueba se creara para cadametodo de prueba (evitando ası cualquier problema que pueda ocur-rir debido a efectos secundarios entre las pruebas). Ocasionalmente,la creacion de un objeto de prueba asignara recursos sin memoria,en cuyo caso debe anular cleanup( ) para liberar esos recursos.

Escribiendo pruebas

Escribir pruebas llega a ser muy simple. Aquı esta un ejemplo quecrea la clase interna necesaria static y realiza pruebas triviales:

# c02 : TestDemo . py# Creat ing a t e s t

c l a s s TestDemo :p r i v a t e s t a t i c i n t objCounter = 0p r i v a t e i n t id = ++objCounterpub l i c TestDemo ( St r ing s ) :

p r i n t ( s + ” : count = ” + id )

de f c l o s e ( s e l f ) :p r i n t (” Cleaning up : ” + id )

de f someCondition ( s e l f ) : r e turn 1pub l i c s t a t i c c l a s s Test ( UnitTest ) :

TestDemo t e s t 1 = TestDemo(” t e s t 1 ”)TestDemo t e s t 2 = TestDemo(” t e s t 2 ”)de f c leanup ( s e l f ) :

t e s t 2 . c l o s e ( )t e s t 1 . c l o s e ( )

de f testA ( s e l f ) :p r i n t ‘ ‘ TestDemo . testA ”a f f i r m ( t e s t 1 . someCondition ( ) )

29

Page 40: Traducción Thinking in Python

de f testB ( s e l f ) :p r i n t ‘ ‘ TestDemo . testB ”a f f i r m ( t e s t 2 . someCondition ( ) )a f f i r m ( TestDemo . objCounter != 0)

# Causes the bu i ld to ha l t :#! pub l i c void t e s t 3 ( ) : a f f i r m (0)

# : ˜

El metodo test3() esta comentado, porque, como vera, hace quela acumulacion automatica de codigo fuente de arbol de este librose detenga.

Usted puede nombrar su clase interna cualquier cosa que tenga; elunico factor importante es extends UnitTest. Tambien puede in-cluir cualquier codigo de apoyo necesario en otros metodos. Solo losmetodos public que no toman argumentos y el retorno void serantratados como pruebas (los nombres de estos metodos son ilimita-dos).

La clase de prueba superior crea dos instancias de TestDemo.El constructor TestDemo imprime algo, para que podamos verque esta siendo llamado. Usted podrıa tambien definir un construc-tor por defecto (el unico tipo que es utilizado por el framework deprueba), aunque ninguno es necesario aquı. La clase TestDemotiene un metodo close() que sugiere que se utiliza como parte de lalimpieza del objeto, ası este es llamado en el metodo reemplazadocleanup() en Test.

Los metodos de prueba utilizan el metodo affirm( ) para val-idar expresiones, si hay un fallo de la informacion se almacena yse imprime; despues se ejecutan todas las pruebas. Por supuesto,los argumentos affirm() son usualmente mas complicados que este;usted vera mas ejemplos a lo largo de este libro.

Observe que en testB(), el campo private objCounter es ac-cesible para el codigo de prueba — esto es proque Test tiene lospermisos de una clase interna.

30

Page 41: Traducción Thinking in Python

Se puede ver que escribir codigo de prueba requiere muy poco es-fuerzo adicional, y ningun conocimiento distinto del utilizado paraescribir las clases ordinarias.

Para ejecutar las pruebas, utilice RunUnitTests.py (que serapresentado mas adelante). El comando para el codigo anterior se veası:

java com.bruceeckel.test.RunUnitTests TestDemo

Esto produce el siguiente resultado:

t e s t 1 : count = 1t e s t 2 : count = 2 ra the r than putt ing i t in and s t r i p p i n g i t out as i s t y p i c a l l y done withTestDemo . testACleaning up : 2Cleaning up : 1t e s t 1 : count = 3t e s t 2 : count = 4TestDemo . testBCleaning up : 4Cleaning up : 3

Todo el ruido de salida esta tan lejos como el exito o el fracasode la unidad de pruebas en referencia. Solamente uno o mas fal-los de la unidad de prueba hace que el programa tenga un valor deretorno distinto de cero y termine el proceso make. Por lo tanto,se puede optar por producir una salida o no, como se adapte a susnecesidades, y la clase de prueba llega a ser un buen lugar paraponer cualquier codigo de impresion que pueda necesitar — si ustedhace esto, se tiende a mantener dicho codigo alrededor en lugar deponerlo dentro y luego ser despojado, como se hace normalmentecon el codigo de seguimiento.

Si es necesario agregar una prueba para una clase derivada deuno que ya tiene una clase de prueba, no hay problema, como sepuede ver aquı:

31

Page 42: Traducción Thinking in Python

# c02 : TestDemo2 . py# I n h e r i t i n g from a c l a s s that# al ready has a t e s t i s no problem .

c l a s s TestDemo2 ( TestDemo ) :pub l i c TestDemo2 ( St r ing s ) : . i n i t ( s )# You can even use the same name# as the t e s t c l a s s in the base c l a s s :pub l i c s t a t i c c l a s s Test ( UnitTest ) :

de f testA ( s e l f ) :p r i n t ‘ ‘ TestDemo2 . testA ”a f f i r m (1 + 1 == 2)

de f testB ( s e l f ) :p r i n t ‘ ‘ TestDemo2 . testB ”a f f i r m (2 ∗ 2 == 4)

# : ˜

Incluso el nombre de la clase interna puede ser el mismo. En elcodigo anterior, todas las afirmaciones son siempre verdaderas porlo que las pruebas nunca fallaran.

Pruebas de caja blanca y caja negra

Los ejemplos de prueba de unidad hasta el momento son los quetradicionalmente se llaman white-box tests : pruebas de caja blanca.Esto significa que el codigo de prueba tiene un acceso completo ala parte interna de la clase que esta siendo probada (por lo quepodrıa ser llamado mas apropiadamente las pruebas ”caja trans-parente”). Las pruebas de caja blanca suceden automaticamentecuando usted hace la clase de prueba de unidad como una clase in-terna de la clase que esta probando, ya que las clases internas tienenautomaticamente acceso a todos sus elementos de clase exteriores,incluso las que son private.

Una forma posiblemente mas comun de la prueba es la black-box testing : prueba de caja negra, que se refiere al tratamientode la clase que se esta probando como una caja impenetrable. Nose puede ver el funcionamiento interno; solo se puede acceder a las

32

Page 43: Traducción Thinking in Python

partes public de la clase. Ası, las pruebas de caja negra corre-sponden mas estrechamente a las pruebas funcionales, para verificarlos metodos que el programador-cliente va a utilizar. En adicion,las pruebas de caja negra proporcionan una hoja de instruccionesmınimas para el programador-cliente – en ausencia de toda otra doc-umentacion, las pruebas de caja negra al menos demuestran comohacer llamadas basicas a los metodos de la clase public.

Para realizar las pruebas de caja negra utilizando el frameworkde la unidad de pruebas presentado en este libro, todo lo que nece-sita hacer es crear su clase de prueba como una clase global en lugarde una clase interna. Todas las demas reglas son las mismas (porejemplo, la clase de prueba de unidad debe ser public, y derivadode UnitTest).

Hay otra advertencia, que tambien proporcionara un pequenorepaso de los paquetes Java. Si usted quiere ser completamente rig-uroso, debe poner su clase de prueba de caja negra en un directorioindependiente de la clase puesta a prueba; de lo contrario, tendraacceso al paquete y a los elementos de la clase en prueba. Es decir,usted sera capaz de acceder a los elementos protected y friendlyde la clase en prueba. Aquı esta un ejemplo:

# c02 : Testab le . py

c l a s s Testab le :p r i v a t e void f1 ( ) :de f f 2 ( s e l f ) : # ” Fr i end ly ” : package a c c e s sde f f 3 ( s e l f ) : # Also package a c c e s sde f f 4 ( s e l f ) :

# : ˜

Normalmente, el unico metodo que podrıa ser accesible directa-mente para el programador-cliente es f4(). Sin embargo, si ustedpone su prueba de caja negra en el mismo directorio, automaticamentese convierte en parte de un mismo paquete (en este caso, el paquetepor defecto ya que no se especifica ninguno) y entonces tiene unacceso inapropiado:

33

Page 44: Traducción Thinking in Python

# c02 : TooMuchAccess . py

c l a s s TooMuchAccess ( UnitTest ) :Testab le t s t = Testab le ( )de f t e s t 1 ( s e l f ) :

t s t . f 2 ( ) # Oops !t s t . f 3 ( ) # Oops !t s t . f 4 ( ) # OK

# : ˜

Puede resolver el problema moviendo TooMuchAcces.py a supropio subdirectorio, de este modo poniendo esto en su propio pa-quete por defecto (por lo tanto un paquete diferente de Testable.py).Por supuesto, cuando usted hace esto, entonces Testable debe estaren su propio paquete, de modo que pueda ser importado (tenga encuenta que tambien es posible importar una clase ”paquete-menos”,dando el nombre de clase en la declaracion import y asegurandoque la clase esta en su CLASSPATH):

# c02 : t e s t a b l e : Testab le . pypackage c02 . t e s t a b l e

c l a s s Testab le :p r i v a t e void f1 ( ) :de f f 2 ( s e l f ) : # ” Fr i end ly ” : package a c c e s sde f f 3 ( s e l f ) : # Also package a c c e s sde f f 4 ( s e l f ) :

# : ˜

Aquı esta la prueba de la caja-negra en su propio paquete, mostrandocomo solamente los metodos publicos pueden ser llamados:

# c02 : t e s t : BlackBoxTest . py

c l a s s BlackBoxTest ( UnitTest ) :Testab le t s t = Testab le ( )de f t e s t 1 ( s e l f ) :

#! t s t . f 2 ( ) # Nope !

34

Page 45: Traducción Thinking in Python

#! t s t . f 3 ( ) # Nope !t s t . f 4 ( ) # Only pub l i c methods a v a i l a b l e

# : ˜

Tenga en cuenta que el programa anterior es de hecho muy sim-ilar al que el programador-cliente escribirıa para utilizar su clase,incluyendo las importaciones y los metodos disponibles. De modoque lo hace un buen ejemplo de programacion. Claro, es mas facildesde el punto de vista de codificacion para simplemente hacer unaclase interna, y a menos que vea la necesidad para pruebas especifi-cas de Black Box es posible que solo quiera seguir adelante y utilizarlas clases internas (a sabiendas que si usted lo requiere mas adelantepuede extraer las clases internas en clases de prueba de caja negraseparadas, sin demasiado esfuerzo).

Ejecucion de Pruebas

El programa que ejecuta las pruebas hace un uso significativo de laobservacion por lo que escribir las pruebas puede ser simple para elprogramador cliente.

# t e s t : RunUnitTests . py# Discover ing the un i t t e s t# c l a s s and running each t e s t .

c l a s s RunUnitTests :pub l i c s t a t i c voidr e q u i r e ( boolean requirement , S t r ing errmsg ) :

i f ( ! requirement ) :System . e r r . p r i n t l n ( errmsg )System . e x i t (1 )

de f main ( s e l f , S t r ing [ ] a rgs ) :r e q u i r e ( args . l ength == 1 ,

”Usage : RunUnitTests q u a l i f i e d−c l a s s ”)t ry :

Class c = Class . forName ( args [ 0 ] )# Only f i n d s the inner c l a s s e s# dec la r ed in the cur rent c l a s s :

35

Page 46: Traducción Thinking in Python

Class [ ] c l a s s e s = c . ge tDec l a r edCla s s e s ( )Class ut = n u l lf o r ( i n t j = 0 j < c l a s s e s . l ength j ++):

# Skip inner c l a s s e s that are# not der ived from UnitTest :i f ( ! UnitTest . c l a s s .

i sAss ignableFrom ( c l a s s e s [ j ] ) )cont inue

ut = c l a s s e s [ j ]break # Finds the f i r s t t e s t c l a s s only

# I f i t found an inner c l a s s ,# that c l a s s must be s t a t i c :i f ( ut != n u l l )

r e q u i r e (Modi f i e r . i s S t a t i c ( ut . g e tMod i f i e r s ( ) ) ,” inne r UnitTest c l a s s must be s t a t i c ”)

# I f i t couldn ’ t f i n d the inner c l a s s ,# maybe i t ’ s a r e g u l a r c l a s s ( f o r black−# box t e s t i n g :i f ( ut == n u l l )

i f ( UnitTest . c l a s s . i sAss ignableFrom ( c ) )ut = c

r e q u i r e ( ut != nul l ,”No UnitTest c l a s s found ”)

r e q u i r e (Modi f i e r . i s P u b l i c ( ut . g e tMod i f i e r s ( ) ) ,” UnitTest c l a s s must be pub l i c ”)

Method [ ] methods = ut . getDeclaredMethods ( )f o r ( i n t k = 0 k < methods . l ength k++):

Method m = methods [ k ]# Ignore over r idden UnitTest methods :i f (m. getName ( ) . equa l s (” cleanup ”) )

cont inue# Only pub l i c methods with no# arguments and void re turn# types w i l l be used as t e s t code :i f (m. getParameterTypes ( ) . l ength == 0 &&

m. getReturnType ( ) == void . c l a s s &&

36

Page 47: Traducción Thinking in Python

Modi f i e r . i s P u b l i c (m. g e tMod i f i e r s ( ) ) ) :# The name o f the t e s t i s# used in e r r o r messages :UnitTest . t e s t ID = m. getName ( )# A in s tance o f the# t e s t ob j e c t i s c r ea ted and# cleaned up f o r each t e s t :Object t e s t = ut . newInstance ( )m. invoke ( t e s t , Object [ 0 ] )( ( UnitTest ) t e s t ) . c leanup ( )

catch ( Exception e ) :e . pr intStackTrace ( System . e r r )# Any except ion w i l l r e turn a nonzero# value to the conso le , so that# ’make ’ w i l l abort :System . e r r . p r i n t l n (” Aborting make”)System . e x i t (1 )

# After a l l t e s t s in t h i s c l a s s are run ,# d i sp l ay any r e s u l t s . I f the r e were e r r o r s ,# abort ’make ’ by r e tu rn ing a nonzero va lue .i f ( UnitTest . e r r o r s . s i z e ( ) != 0 ) :

I t e r a t o r i t = UnitTest . e r r o r s . i t e r a t o r ( )whi l e ( i t . hasNext ( ) )

System . e r r . p r i n t l n ( i t . next ( ) )System . e x i t (1 )

# : ˜

37

Page 48: Traducción Thinking in Python

Ejecutar Pruebas Automaticamente

Ejercicios

1. Instalar el arbol del codigo fuente este libro y asegurar que ustedtenga una utilidad make instalada en su sistema. (GNU makeesta disponible libremente en varios sitios de internet). En Test-Demo.py, no comente test3( ), entonces escriba make y observelos resultados.

2. Modificar TestDemo.java mediante la adicion de una nuevaprueba que produzca una excepcion. Escriba make y observar losresultados.

3. Modifique sus soluciones a los ejercicios en el capıtulo 1,anadiendo las pruebas unitarias. Escribe makefiles que incorporenlas pruebas unitarias.

38

Page 49: Traducción Thinking in Python

3: Construyendo aplicaciones Framework

Una aplicacion framework le permite heredar de una clase o con-junto de clases y crear una nueva aplicacion, reutilizando la mayorparte del codigo en las clases existentes y anular uno o mas metodoscon el fin de personalizar la aplicacion a sus necesidades. Un con-cepto fundamental en el entorno de aplicacion es el Template Method(Metodo Plantilla) el cual normalmente se oculta debajo de las cu-biertas e impulsa la aplicacion llamando a los diversos metodos enla clase base (algunos de los cuales usted ha reemplazado con el finde crear la aplicacion).

Por ejemplo, cuando se crea un applet, se esta utilizando unaaplicacion framework: hereda de JApplet y luego reemplaza init().El mecanismo applet (que es un metodo plantilla) se encarga delresto mediante la elaboracion de la pantalla, el manejo del ciclo deeventos, los cambios de tamano, etc.

Template Method (Metodo Plantilla)

Una caracterıstica importante del Metodo Plantilla es que esta definidoen la clase base y no puede ser cambiado. Este algunas veces es unmetodo Privado pero siempre es practicamente final. El llamadoa otros metodos de la clase base (los que usted reemplaza) con elfin de hacer su trabajo, pero se le suele llamar solo como parte deun proceso de inicializacion (y por tanto el programador-cliente nonecesariamente es capaz de llamarlo directamente).

#: c03 : TemplateMethod . py# Simple demonstrat ion o f Template Method .

c l a s s ApplicationFramework :de f i n i t ( s e l f ) :

s e l f . templateMethod ( )de f templateMethod ( s e l f ) :

f o r i in range ( 5 ) :s e l f . customize1 ( )s e l f . customize2 ( )

39

Page 50: Traducción Thinking in Python

# Create a ” a p p l i c a t i o n ” :c l a s s MyApp( ApplicationFramework ) :

de f customize1 ( s e l f ) :p r i n t ”Nudge , nudge , wink , wink ! ” ,

de f customize2 ( s e l f ) :p r i n t ”Say no more , Say no more ! ”

MyApp( )#:˜

El constructor de la clase base es responsable de realizar la inicial-izacion necesaria y despues de iniciar el ”motor” (el metodo plan-tilla) que ejecuta la aplicacion (en una aplicacion GUI, este ”mo-tor” serıa el bucle principal del evento). El programador clientesimplemente proporciona definiciones para customize1() y cus-tomize2() y la ”aplicacion” esta lista para funcionar.

Veremos Template Method (Metodo plantilla) otras numerosasveces a lo largo del libro.

Ejercicios

1. Crear un framework que tome una lista de nombres de archivo enla lınea de comandos. Este abre cada archivo, excepto el ultimopara la lectura, y el ultimo para la escritura. El framework proce-sara cada archivo de entrada utilizando una polıtica indetermi-nada y escribir la salida al ultimo archivo. Heredar para person-alizar este framework para crear dos aplicaciones separadas:

1) Convierte todas las letras en cada archivo a mayusculas.

2) Busca los archivos de las palabras dadas en el primer archivo.

40

Page 51: Traducción Thinking in Python

4: Al frente de una implementacion

Tanto Proxy y State proporcionan una clase sustituta que se uti-liza en el codigo; la clase real que hace el trabajo se esconde detrasde esta clase sustituta. Cuando se llama a un metodo en la clasesurrogate (sustituto), este simplemente gira y llama al metodo enla implementacion de la clase. Estos dos patrones son tan similaresque el Proxy es simplemente un caso especial de State. Uno estatentado a agrupar a los dos juntos en un patron llamado Surrogate(sustituto), pero el termino ”proxy” tiene un significado antiguo yespecializado, que probablemente explica la razon de los dos pa-trones diferentes.

La idea basica es simple: de una clase base, el sustituto se derivajunto con la clase o clases que proporcionan la implementacion real:

Figure 1: tomado de: http://docs.linuxtone.org/ebooks/Python/Thinking In Python.pdf

Cuando se crea un objeto sustituto, se da una implementacion ala cual enviar todos los llamados a los metodos.

Estructuralmente, la diferencia entre Proxy y State es simple: unProxy tiene una sola implementacion, mientras que State tiene masde una implementacion. La aplicacion de los patrones se consideraen Design Patterns (Patrones de Diseno) de forma distinta: Proxyes usado para controlar el acceso a esta implementacion, mientrasState le permite cambiar la implementacion de forma dinamica. Sinembargo, si expande su nocion de ”controlando el acceso a la imple-mentacion”, entonces los dos encajan pulcramente juntos.

41

Page 52: Traducción Thinking in Python

Proxy

Si implementamos Proxy siguiendo el diagrama anterior, se ve ası:

#: c04 : ProxyDemo . py# Simple demonstrat ion o f the Proxy pattern .

c l a s s Implementation :de f f ( s e l f ) :

p r i n t ” Implementation . f ( )”de f g ( s e l f ) :

p r i n t ” Implementation . g ( )”de f h( s e l f ) :

p r i n t ” Implementation . h ( )”

c l a s s Proxy :de f i n i t ( s e l f ) :

s e l f . implementat ion = Implementation ( )# Pass method c a l l s to the implementation :

de f f ( s e l f ) : s e l f . implementat ion . f ( )de f g ( s e l f ) : s e l f . implementat ion . g ( )de f h( s e l f ) : s e l f . implementat ion . h ( )

p = Proxy ( )p . f ( ) ; p . g ( ) ; p . h ( )#:˜

No es necesario que Implementation tenga la misma interfazque Proxy; siempre y cuando Proxy es de alguna manera ”speak-ing for” (”hablando en nombre de”) la clase se refiere a la llamadadel metodo, para entonces la idea basica esta satisfecha (tenga encuenta que esta declaracion esta en contradiccion con la definicionde Proxy en GoF). Sin embargo, es conveniente tener una interfazcomun para que Implementation se vea obligado a cumplir contodos los metodos que Proxy necesita llamar.

Por supuesto, en Python tenemos un mecanismo de delegacionintegrado, lo que hace que el Proxy sea aun mas simple de imple-mentar:

#: c04 : ProxyDemo2 . py

42

Page 53: Traducción Thinking in Python

# Simple demonstrat ion o f the Proxy pattern .

c l a s s Implementation2 :de f f ( s e l f ) :

p r i n t ” Implementation . f ( )”de f g ( s e l f ) :

p r i n t ” Implementation . g ( )”de f h( s e l f ) :

p r i n t ” Implementation . h ( )”

c l a s s Proxy2 :de f i n i t ( s e l f ) :

s e l f . implementat ion = Implementation2 ( )de f g e t a t t r ( s e l f , name ) :

r e turn g e t a t t r ( s e l f . implementat ion , name)

p = Proxy2 ( )p . f ( ) ; p . g ( ) ; p . h ( ) ;#:˜

La belleza de la utilizacion de getattr ( ) es que Proxy2 escompletamente generico, y no vinculada a cualquier implementacionparticular (en Java, un ”proxy dinamico” bastante complicado hasido creado para lograr esto mismo).

43

Page 54: Traducción Thinking in Python

State

El patron State anade mas implementaciones a Proxy, junto con unamanera de cambiar de una implementacion a otra durante tiempode vida del sustituto:

#: c04 : StateDemo . py# Simple demonstrat ion o f the State pattern .

c l a s s State d :de f i n i t ( s e l f , imp ) :

s e l f . implementat ion = impde f changeImp ( s e l f , newImp ) :

s e l f . implementat ion = newImp# Delegate c a l l s to the implementation :de f g e t a t t r ( s e l f , name ) :

r e turn g e t a t t r ( s e l f . implementat ion , name)

c l a s s Implementation1 :de f f ( s e l f ) :

p r i n t ” Fiddle de dum, Fiddle de dee , ”de f g ( s e l f ) :

p r i n t ” Er ic the h a l f a bee . ”de f h( s e l f ) :

p r i n t ”Ho ho ho , t e e hee hee , ”

c l a s s Implementation2 :de f f ( s e l f ) :p r i n t ”We’ re Knights o f the Round Table . ”

de f g ( s e l f ) :p r i n t ”We dance whene ’ e r we ’ re ab le . ”

de f h( s e l f ) :p r i n t ”We do r o u t i n e s and chorus s c ene s ”

de f run (b ) :b . f ( )b . g ( )b . h ( )b . g ( )

44

Page 55: Traducción Thinking in Python

b = State d ( Implementation1 ( ) )run (b)b . changeImp ( Implementation2 ( ) )run (b)#:˜

Se puede ver que la primera implementacion se usa para unaparte, a continuacion, la segunda implementacion se intercambia yse utiliza.

La diferencia entre Proxy y State esta en los problemas que seresuelven. Los usos comunes para Proxy como se describe en DesignPatterns son:

1. Proxy remoto. Este proxy para un objeto en un espacio dedireccion diferente. Se crea un proxy remoto de forma automaticapor el compilador RMI rmic ya que crea ramales y esqueletos.

2. Proxy virtual. Esto proporciona ”inicializacion relajada” paracrear objetos costosos por demanda.

3. Proxy de Proteccion. Se usa cuando no se desea que el pro-gramador cliente tenga acceso completo a los objetos proxy.

4. Referencia inteligente. Para agregar acciones adicionales cuandose accede al objeto proxy. Por ejemplo, o para llevar un registrodel numero de referencias que se realizan para un objeto en partic-ular, con el fin de implementar el lenguaje copy-on-write (copiaren escritura) y prevenir objeto aliasing. Un ejemplo sencillo eshacer el seguimiento del numero de llamadas a un metodo enparticular.

Usted podrıa mirar una referencia de Python como un tipo deproxy de proteccion, ya que controla el acceso al objeto real de losdemas (y asegura, por ejemplo, que no utilice una referencia nula).

[[Proxy y State no son vistos como relacionados entre sı porquelos dos se les da (lo que considero arbitrario) diferentes estructuras.State, en particular, utiliza una jerarquıa de implementacion sepa-rada pero esto me parece innecesario a menos que usted haya deci-dido que la implementacion no esta bajo su control (ciertamente una

45

Page 56: Traducción Thinking in Python

posibilidad, pero si usted es dueno de todo el codigo no parece haberninguna razon para no beneficiarse de la elegancia y amabilidad dela clase base individual). En adicion, Proxy no necesita utilizarla misma clase base para su implementacion, siempre y cuando elobjeto proxy este controlando el acceso al objetarlo ”frente” a fa-vor. Independientemente de los detalles, en ambos Proxy y State unsustituto esta pasando la llamada al metodo a traves de un objetode implementacion.]]

46

Page 57: Traducción Thinking in Python

StateMachine (Maquina de Estados)

Mientras State tiene una manera de permitir que el programadorcliente cambie la implementacion, StateMachine impone una estruc-tura para cambiar automaticamente la implementacion de un objetoal siguiente. La implementacion actual representa el estado en queun sistema esta, y el sistema se comporta de manera diferente deun estado a otro (ya que utiliza State). Basicamente, esta es una”state machine : maquina de estados” usando objetos.

El codigo que mueve el sistema de un estado a otro es a menudoun Template Method (Metodo Plantilla), como se ve en el siguienteframework para una maquina de estados basica.

Cada estado puede estar en un run( ) para cumplir con su com-portamiento, y (en este diseno) tambien puede pasarlo a un objeto”input” y ası puede decirle a que nuevo estado es removido basadoen este ”input”. La distincion clave entre este diseno y el siguientees que aquı, cada objeto State decide lo que otros estados puedenavanzar, basado en ”input”, mientras que en el posterior diseno detodas las transiciones de estado se llevan a cabo en una sola tabla.Otra forma de decirlo es que aquı, cada objeto State tiene su propiapequena tabla State, y en el subsiguiente diseno hay una sola tabladirectora de transicion de estado para todo el sistema.

#: c04 : statemachine : State . py# A State has an operat ion , and can be moved# into the next State g iven an Input :

c l a s s State :de f run ( s e l f ) :

a s s e r t 1 , ” run not implemented”de f next ( s e l f , input ) :

a s s e r t 1 , ” next not implemented”#:˜

Esta clase es claramente innecesaria, pero que nos permite decirque algo es un objeto State en el codigo, y proporcionar un mensajede error ligeramente diferente cuando no se implementan todos losmetodos. Podrıamos haber conseguido basicamente el mismo efecto

47

Page 58: Traducción Thinking in Python

diciendo:

c l a s s State : pass

Porque todavıa conseguirıamos excepciones si run o next() hu-bieran sido llamados por un tipo derivado, y no hubieran sido im-plementados.

El StateMachine hace un seguimiento de la situacion actual, elcual es inicializado por el constructor. El metodo runAll() tomauna lista de objetos Input. Este metodo no solo avanza al siguienteestado, sino que tambien llama run( ) para cada objeto state – porlo tanto se puede ver que es una expansion de la idea del patronState, ya que run( ) hace algo diferente dependiendo del estado enque el sistema esta.

#: c04 : statemachine : StateMachine . py# Takes a l i s t o f Inputs to move from State to# State us ing a template method .

c l a s s StateMachine :de f i n i t ( s e l f , i n i t i a l S t a t e ) :

s e l f . cu r r en tS ta t e = i n i t i a l S t a t es e l f . cu r r en tS ta t e . run ( )

# Template method :de f runAl l ( s e l f , inputs ) :

f o r i in inputs :p r i n t is e l f . cu r r en tS ta t e = s e l f . cu r r en tS ta t e . next ( i )s e l f . cu r r en tS ta t e . run ( )

#:˜

Tambien he tratado runAll( ) como un metodo plantilla. Esto estıpico, pero ciertamente no es necesario – posiblemente podrıa quererreemplazarlo, pero por lo general el cambio de comportamiento seproducira en run( ) de State en su lugar.

En este punto se ha completado el framework basico para esteestilo de StateMachine (donde cada estado decide los proximos es-tados). Como ejemplo, voy a utilizar una trampa de fantasıa para

48

Page 59: Traducción Thinking in Python

que el raton pueda moverse a traves de varios estados en el procesode atrapar un raton16. Las clases raton y la informacion se almace-nan en el paquete mouse, incluyendo una clase en representacionde todas los posibles movimientos que un raton puede hacer, queseran las entradas a la state machine (maquina de estados):

#: c04 : mouse : MouseAction . py

c l a s s MouseAction :de f i n i t ( s e l f , a c t i on ) :

s e l f . a c t i on = act i onde f s t r ( s e l f ) : r e turn s e l f . a c t i onde f cmp ( s e l f , o ther ) :

r e turn cmp( s e l f . act ion , other . a c t i on )# Necessary when cmp or e q i s de f ined# in order to make t h i s c l a s s usab le as a# d i c t i o n a r y key :de f h a s h ( s e l f ) :

r e turn hash ( s e l f . a c t i on )

# S t a t i c f i e l d s ; an enumeration o f i n s t a n c e s :MouseAction . appears = MouseAction (” mouse appears ”)MouseAction . runsAway = MouseAction (” mouse runs away”)MouseAction . e n t e r s = MouseAction (” mouse e n t e r s trap ”)MouseAction . e s capes = MouseAction (” mouse e scapes ”)MouseAction . trapped = MouseAction (” mouse trapped ”)MouseAction . removed = MouseAction (” mouse removed ”)#:˜

Usted observara que cmp ( ) se ha reemplazado para imple-mentar una comparacion entre los valores de action. Tambien, cadaposible movida de un raton se enumera como un objeto de Mouse-Action, todos los cuales son los campos estaticos en MouseAction.

Para la creacion de codigo de prueba, una secuencia de entradasde mouse esta provisto de un archivo de texto:

#:! c04 : mouse : MouseMoves . txt

16Ningun raton fue perjudicado en la creacion de este ejemplo.

49

Page 60: Traducción Thinking in Python

mouse appearsmouse runs awaymouse appearsmouse e n t e r s trapmouse e scapesmouse appearsmouse e n t e r s trapmouse trappedmouse removedmouse appearsmouse runs awaymouse appearsmouse e n t e r s trapmouse trappedmouse removed#:˜

Con estas herramientas en su lugar, ahora es posible crear laprimera version del programa mousetrap (ratonera). Cada subclaseState define su comportamiento run( ) y tambien establece su sigu-iente estado con una sentencia if-else:

#: c04 : mousetrap1 : MouseTrapTest . py# State Machine pattern us ing ’ i f ’ s tatements# to determine the next s t a t e .import s t r i ng , syssys . path += [ ’ . . / statemachine ’ , ’ . . / mouse ’ ]from State import Statefrom StateMachine import StateMachinefrom MouseAction import MouseAction# A d i f f e r e n t s u b c l a s s f o r each s t a t e :

c l a s s Waiting ( State ) :de f run ( s e l f ) :

p r i n t ”Waiting : Broadcast ing cheese sme l l ”

de f next ( s e l f , input ) :i f input == MouseAction . appears :

r e turn MouseTrap . l u r i n gre turn MouseTrap . wa i t ing

50

Page 61: Traducción Thinking in Python

c l a s s Luring ( State ) :de f run ( s e l f ) :

p r i n t ” Luring : Present ing Cheese , door open”de f next ( s e l f , input ) :

i f input == MouseAction . runsAway :re turn MouseTrap . wa i t ing

i f input == MouseAction . e n t e r s :r e turn MouseTrap . t rapping

re turn MouseTrap . l u r i n g

c l a s s Trapping ( State ) :de f run ( s e l f ) :

p r i n t ”Trapping : Clos ing door ”

de f next ( s e l f , input ) :i f input == MouseAction . e s capes :

r e turn MouseTrap . wa i t ingi f input == MouseAction . trapped :

re turn MouseTrap . ho ld ingre turn MouseTrap . t rapping

c l a s s Holding ( State ) :de f run ( s e l f ) :

p r i n t ” Holding : Mouse caught ”de f next ( s e l f , input ) :

i f input == MouseAction . removed :re turn MouseTrap . wa i t ing

re turn MouseTrap . ho ld ing

c l a s s MouseTrap ( StateMachine ) :de f i n i t ( s e l f ) :

# I n i t i a l s t a t eStateMachine . i n i t ( s e l f , MouseTrap . wa i t ing )

# S t a t i c v a r i a b l e i n i t i a l i z a t i o n :MouseTrap . wa i t ing = Waiting ( )MouseTrap . l u r i n g = Luring ( )MouseTrap . t rapping = Trapping ( )

51

Page 62: Traducción Thinking in Python

MouseTrap . ho ld ing = Holding ( )

moves = map( s t r i n g . s t r i p ,open ( ” . . / mouse/MouseMoves . txt ” ) . r e a d l i n e s ( ) )

MouseTrap ( ) . runAl l (map( MouseAction , moves ) )#:˜

La clase StateMachine simplemente define todos los posiblesestados como objetos estaticos, y tambien establece el estado ini-cial. UnitTest crea un MouseTrap y luego prueba con todas lasentradas de un MouseMoveList.

Mientras el uso de las sentencias if dentro de los metodos next()es perfectamente razonable, la gestion de un gran numero de ellospodrıa llegar a ser difıcil. Otro enfoque es crear tablas dentro decada objeto State definiendo los diversos estados proximos basadosen la entrada.

Inicialmente, esto parece que deberıa ser bastante simple. Usteddebe ser capaz de definir una tabla estatica en cada subclase Stateque define las transiciones en terminos de los otros objetos State.Sin embargo, resulta que este enfoque genera dependencias de ini-cializacion cıclicas. Para resolver el problema, He tenido que retrasarla inicializacion de las tablas hasta la primera vez que se llama almetodo next( ) para un objeto en particular State. Inicialmente,los metodos next() pueden parecer un poco extranos debido a esto.

La clase StateT es una implementacion de State ((de modo quela misma clase StateMachine puede ser utilizado en el ejemplo an-terior) que anade un Map y un metodo para inicializar el mapa apartir de una matriz de dos dimensiones. El ”metodo next()” tieneuna implementacion de la clase base que debe ser llamado desde el”metodo next() de la clase derivada anulada”, despues de que seponen a prueba para un null Map (y inicializarlo si es nulo):

#: c04 : mousetrap2 : MouseTrap2Test . py# A b e t t e r mousetrap us ing t a b l e simport s t r i ng , syssys . path += [ ’ . . / statemachine ’ , ’ . . / mouse ’ ]from State import State

52

Page 63: Traducción Thinking in Python

from StateMachine import StateMachinefrom MouseAction import MouseAction

c l a s s StateT ( State ) :de f i n i t ( s e l f ) :

s e l f . t r a n s i t i o n s = Nonede f next ( s e l f , input ) :

i f s e l f . t r a n s i t i o n s . has key ( input ) :r e turn s e l f . t r a n s i t i o n s [ input ]

e l s e :r a i s e ” Input not supported f o r cur rent s t a t e ”

c l a s s Waiting ( StateT ) :de f run ( s e l f ) :

p r i n t ”Waiting : Broadcast ing cheese sme l l ”de f next ( s e l f , input ) :

# Lazy i n i t i a l i z a t i o n :i f not s e l f . t r a n s i t i o n s :

s e l f . t r a n s i t i o n s = MouseAction . appears : MouseTrap . l u r i n g

r e turn StateT . next ( s e l f , input )

c l a s s Luring ( StateT ) :de f run ( s e l f ) :

p r i n t ” Luring : Present ing Cheese , door open”de f next ( s e l f , input ) :

# Lazy i n i t i a l i z a t i o n :i f not s e l f . t r a n s i t i o n s :

s e l f . t r a n s i t i o n s = MouseAction . e n t e r s : MouseTrap . trapping ,MouseAction . runsAway : MouseTrap . wa i t ing

r e turn StateT . next ( s e l f , input )

c l a s s Trapping ( StateT ) :de f run ( s e l f ) :

p r i n t ”Trapping : Clos ing door ”de f next ( s e l f , input ) :

# Lazy i n i t i a l i z a t i o n :i f not s e l f . t r a n s i t i o n s :

53

Page 64: Traducción Thinking in Python

s e l f . t r a n s i t i o n s = MouseAction . e s capes : MouseTrap . wait ing ,MouseAction . trapped : MouseTrap . ho ld ing

r e turn StateT . next ( s e l f , input )

c l a s s Holding ( StateT ) :de f run ( s e l f ) :

p r i n t ” Holding : Mouse caught ”de f next ( s e l f , input ) :

# Lazy i n i t i a l i z a t i o n :i f not s e l f . t r a n s i t i o n s :

s e l f . t r a n s i t i o n s = MouseAction . removed : MouseTrap . wa i t ing

r e turn StateT . next ( s e l f , input )

c l a s s MouseTrap ( StateMachine ) :de f i n i t ( s e l f ) :

# I n i t i a l s t a t eStateMachine . i n i t ( s e l f , MouseTrap . wa i t ing )

# S t a t i c v a r i a b l e i n i t i a l i z a t i o n :MouseTrap . wa i t ing = Waiting ( )MouseTrap . l u r i n g = Luring ( )MouseTrap . t rapping = Trapping ( )MouseTrap . ho ld ing = Holding ( )moves = map( s t r i n g . s t r i p ,

open ( ” . . / mouse/MouseMoves . txt ” ) . r e a d l i n e s ( ) )mouseMoves = map( MouseAction , moves )MouseTrap ( ) . runAl l ( mouseMoves )#:˜

El resto del codigo es identico – la diferencia esta en los metodosnext() y la clase StateT.

Si usted tiene que crear y mantener una gran cantidad de clasesState, este enfoque es una mejora, ya que es mas facil de leerde forma rapida y comprender las transiciones de estado viendo latabla.

54

Page 65: Traducción Thinking in Python

Table-Driven State Machine

La ventaja del diseno anterior es que toda la informacion acerca deun estado, incluyendo la informacion de transicion del estado, seencuentra dentro de la misma clase state. Esto es generalmente unbuen principio de diseno.

Sin embargo, en una state machine (maquina de estados) pura,la maquina puede ser completamente representada por una unicatabla de transicion de estados. Esto tiene la ventaja de localizartoda la informacion sobre la maquina de estados en un solo lugar, loque significa que usted puede con mayor facilidad crear y mantenerla tabla basada en un diagrama de transicion de estados clasica.

El diagrama clasico de transicion de estados utiliza un cırculopara representar cada estado, y las lıneas del state senalando a todoslos estados en que state puede trasladarse. Cada lınea de transicionse anota con condiciones para la transicion y una accion durante latransicion. El siguiente es su aspecto:

(Diagrama State Machine simple)

Objetivos:

• Traduccion directa del diagrama de estado

• Vector del cambio: la representacion del diagrama de estado

• Implementacion razonable

• No hay exceso de estados (usted podrıa representar a cada cam-bio individual con un nuevo estado)

• La simplicidad y la flexibilidad

Observaciones:

• Los estados son triviales – ninguna informacion o funciones /datos, solo una identidad.

• Diferente a State Pattern!

• La maquina regula el paso de un estado a otro.

55

Page 66: Traducción Thinking in Python

• Al igual que en flyweight : peso mosca

• Cada estado puede pasar a muchos otros

• Funciones de condicion / accion tambien deben ser externos alos estados

• Centralizar la descripcion en una sola tabla que contiene todaslas variaciones, para facilitar la configuracion.

Ejemplo:

• State Machine y Table-Driven Code

• Implementa una maquina expendedora

• Utiliza diferentes patrones

• Separa codigo comun state-machine de aplicaciones especıficas(como metodo de plantilla)

• Cada entrada causa una busqueda de la solucion apropiada(como cadena de responsabilidad)

• Las pruebas y transiciones se encapsulan en objetos de fun-ciones (objetos que contienen funciones)

• Restriccion de Java: los metodos no son objetos de primeraclase.

56

Page 67: Traducción Thinking in Python

La clase State

La clase State es claramente diferente a la anterior, ya que es enrealidad solo un marcador de posicion con un nombre. Por lo tanto,no se hereda de las clases State anteriores:

# c04 : statemachine2 : State . pyc l a s s State :

de f i n i t ( s e l f , name ) : s e l f . name = namede f s t r ( s e l f ) : r e turn s e l f . name

# : ˜

Condiciones para la transicion

En el diagrama de transicion de estados, una entrada se pone aprueba para ver si satisface las condiciones necesarias para transferiral Estado en cuestion. Como antes, el Input es solo una interfaz deetiquetado:

57

Page 68: Traducción Thinking in Python

# c04 : statemachine2 : Input . py# Inputs to a s t a t e machinec l a s s Input : pass

# : ˜

La Condition evalua el Input para decidir si esta fila en la tablaes la transicion correcta:

# c04 : statemachine2 : Condit ion . py# Condit ion func t i on ob j e c t f o r s t a t e machine

c l a s s Condit ion :boolean cond i t i on ( input ) :

a s s e r t 1 , ” cond i t i on ( ) not implemented”# : ˜

Acciones de transicion

Si Condition devuelve true, entonces se hace la transicion a unnuevo estado, y dado que esa transicion se hace, algun tipo de accionocurre (en el diseno anterior de state machine : maquina de estado,este era el metodorun( )).

# c04 : statemachine2 : Trans i t i on . py# Trans i t i on func t i on ob j e c t f o r s t a t e machine

c l a s s Trans i t i on :de f t r a n s i t i o n ( s e l f , input ) :

a s s e r t 1 , ” t r a n s i t i o n ( ) not implemented”# : ˜

La tabla

Con estas clases en el lugar, podemos establecer una tabla de 3 di-mensiones, donde cada fila describe completamente un estado. Elprimer elemento en la fila es el estado actual, y el resto de los el-ementos de la fila, son los diferentes tipos de entradas posibles, lacondicion que se debe satisfacer para que este cambio de estado seael correcto, la accion que ocurre durante la transicion, y el nuevo

58

Page 69: Traducción Thinking in Python

estado al que se movera dentro. Observe que el objeto Input nosolo se utiliza para su tipo, tambien es un objeto Messenger quelleva la informacion a los objetos Condition y Transition :

( CurrentState , InputA ) : ( ConditionA , Transit ionA , NextA ) ,( CurrentState , InputB ) : ( ConditionB , Transit ionB , NextB ) ,( CurrentState , InputC ) : ( ConditionC , Transit ionC , NextC ) ,. . .

La maquina basica

# c04 : statemachine2 : StateMachine . py# A table−dr iven s t a t e machine

c l a s s StateMachine :de f i n i t ( s e l f , i n i t i a l S t a t e , tranTable ) :

s e l f . s t a t e = i n i t i a l S t a t es e l f . t r a n s i t i o n T a b l e = tranTable

de f nextState ( s e l f , input ) :

I t e r a t o r i t =(( L i s t )map . get ( s t a t e ) ) . i t e r a t o r ( )whi l e ( i t . hasNext ( ) ) :

Object [ ] t ran = ( Object [ ] ) i t . next ( )i f ( input == tran [ 0 ] | |

input . ge tC la s s ( ) == tran [ 0 ] ) :i f ( tran [ 1 ] != n u l l ) :

Condit ion c = ( Condit ion ) tran [ 1 ]i f ( ! c . c ond i t i on ( input ) )

cont inue #Fai l ed t e s t

i f ( tran [ 2 ] != n u l l )( ( Trans i t i on ) tran [ 2 ] ) . t r a n s i t i o n ( input )

s t a t e = ( State ) tran [ 3 ]r e turn

throw RuntimeException (” Input not supported f o r cur r ent s t a t e ”)

59

Page 70: Traducción Thinking in Python

# : ˜

Simple maquina expendedora

# c04 : vendingmachine : VendingMachine . py# Demonstrates use o f StateMachine . pyimport syssys . path += [ ’ . . / statemachine2 ’ ]import StateMachine

c l a s s State :de f i n i t ( s e l f , name ) : s e l f . name = namede f s t r ( s e l f ) : r e turn s e l f . name

State . qu i e s c en t = State (” Quiesecent ”)State . c o l l e c t i n g = State (” C o l l e c t i n g ”)State . s e l e c t i n g = State (” S e l e c t i n g ”)State . unava i l ab l e = State (” Unava i lab le ”)State . wantMore = State (”Want More ?”)State . noChange = State (” Use Exact Change Only ”)State . makesChange = State (” Machine makes change ”)

c l a s s HasChange :de f i n i t ( s e l f , name ) : s e l f . name = namede f s t r ( s e l f ) : r e turn s e l f . name

HasChange . yes = HasChange (” Has change ”)HasChange . no = HasChange (” Cannot make change ”)

c l a s s ChangeAvailable ( StateMachine ) :de f i n i t ( s e l f ) :

StateMachine . i n i t ( State . makesChange , # Current s ta te , input( State . makesChange , HasChange . no ) :

# te s t , t r a n s i t i o n , next s t a t e :( nu l l , nu l l , State . noChange ) ,

( State . noChange , HasChange . yes ) :( nu l l , nu l l , State . noChange )

)

60

Page 71: Traducción Thinking in Python

c l a s s Money :de f i n i t ( s e l f , name , va lue ) :

s e l f . name = names e l f . va lue = value

de f s t r ( s e l f ) : r e turn s e l f . namede f getValue ( s e l f ) : r e turn s e l f . va lue

Money . quar te r = Money(” Quarter ” , 25)Money . d o l l a r = Money(” Do l l a r ” , 100)

c l a s s Quit :de f s t r ( s e l f ) : r e turn ”Quit”

Quit . qu i t = Quit ( )

c l a s s D ig i t :de f i n i t ( s e l f , name , va lue ) :

s e l f . name = names e l f . va lue = value

de f s t r ( s e l f ) : r e turn s e l f . namede f getValue ( s e l f ) : r e turn s e l f . va lue

c l a s s F i r s t D i g i t ( D ig i t ) : passF i r s t D i g i t .A = F i r s t D i g i t (”A” , 0)F i r s t D i g i t .B = F i r s t D i g i t (”B” , 1)F i r s t D i g i t .C = F i r s t D i g i t (”C” , 2)F i r s t D i g i t .D = F i r s t D i g i t (”D” , 3)

c l a s s SecondDigit ( D ig i t ) : passSecondDigit . one = SecondDigit (” one ” , 0)SecondDigit . two = SecondDigit (” two ” , 1)SecondDigit . th ree = SecondDigit (” three ” , 2)SecondDigit . f our = SecondDigit (” four ” , 3)

c l a s s I temSlot :id = 0de f i n i t ( s e l f , p r i c e , quant i ty ) :

s e l f . p r i c e = p r i c es e l f . quant i ty = quant i ty

61

Page 72: Traducción Thinking in Python

de f s t r ( s e l f ) : r e turn ‘ I temSlot . id ‘de f g e tPr i c e ( s e l f ) : r e turn s e l f . p r i c ede f getQuantity ( s e l f ) : r e turn s e l f . quant i tyde f decrQuantity ( s e l f ) : s e l f . quant i ty −= 1

c l a s s VendingMachine ( StateMachine ) :changeAvai lab le = ChangeAvailable ( )amount = 0F i r s t D i g i t f i r s t = n u l lI temSlot [ ] [ ] i tems = ItemSlot [ 4 ] [ 4 ]

# Condit ions :de f notEnough ( s e l f , input ) :

i 1 = f i r s t . getValue ( )i 2 = input . getValue ( )re turn items [ i 1 ] [ i 2 ] . g e tPr i c e ( ) > amount

de f i t emAvai lab l e ( s e l f , input ) :i 1 = f i r s t . getValue ( )i 2 = input . getValue ( )re turn items [ i 1 ] [ i 2 ] . getQuantity ( ) > 0

de f i temNotAvai lable ( s e l f , input ) :r e turn ! i t emAvai lab l e . c ond i t i on ( input )#i 1 = f i r s t . getValue ( )#i 2 = input . getValue ( )#return items [ i 1 ] [ i 2 ] . getQuantity ( ) == 0

# Trans i t i on s :de f c l e a r S e l e c t i o n ( s e l f , input ) :

i 1 = f i r s t . getValue ( )i 2 = input . getValue ( )I temSlot i s = items [ i 1 ] [ i 2 ]p r i n t (

” Clear ing s e l e c t i o n : item ” + i s +” c o s t s ” + i s . g e tPr i c e ( ) +” and has quant i ty ” + i s . getQuantity ( ) )

f i r s t = n u l l

62

Page 73: Traducción Thinking in Python

de f d i spense ( s e l f , input ) :i 1 = f i r s t . getValue ( )i 2 = input . getValue ( )I temSlot i s = items [ i 1 ] [ i 2 ]p r i n t (” Dispens ing item ” +

i s + ” c o s t s ” + i s . g e tPr i c e ( ) +” and has quant i ty ” + i s . getQuantity ( ) )

i tems [ i 1 ] [ i 2 ] . decrQuantity ( )p r i n t (” Quantity ” +

i s . getQuantity ( ) )amount −= i s . g e tPr i c e ( )p r i n t (”Amount remaining ” +

amount )

de f showTotal ( s e l f , input ) :amount += ( ( Money) input ) . getValue ( )p r i n t ” Total amount = ” + amount

de f returnChange ( s e l f , input ) :p r i n t ” Returning ” + amountamount = 0

de f showDigit ( s e l f , input ) :f i r s t = ( F i r s t D i g i t ) inputp r i n t ” F i r s t D ig i t= ”+ f i r s t

de f i n i t ( s e l f ) :StateMachine . i n i t ( s e l f , State . qu i e s c en t )f o r ( i n t i = 0 i < i tems . l ength i++)

f o r ( i n t j = 0 j < i tems [ i ] . l ength j++)items [ i ] [ j ] = ItemSlot ( ( j +1)∗25 , 5)

items [ 3 ] [ 0 ] = ItemSlot (25 , 0)bui ldTable ( Object [ ] [ ] [ ]

: : State . qu ie scent , # Current s t a t e# Input , t e s t , t r a n s i t i o n , next s t a t e :

: Money . c l a s s , nu l l ,showTotal , State . c o l l e c t i n g ,

: : State . c o l l e c t i n g , # Current s t a t e

63

Page 74: Traducción Thinking in Python

# Input , t e s t , t r a n s i t i o n , next s t a t e :: Quit . quit , nu l l ,

returnChange , State . qu ie scent ,: Money . c l a s s , nu l l ,

showTotal , State . c o l l e c t i n g ,: F i r s t D i g i t . c l a s s , nu l l ,

showDigit , State . s e l e c t i n g ,: : State . s e l e c t i n g , # Current s t a t e

# Input , t e s t , t r a n s i t i o n , next s t a t e :: Quit . quit , nu l l ,

returnChange , State . qu ie scent ,: SecondDigit . c l a s s , notEnough ,

c l e a r S e l e c t i o n , State . c o l l e c t i n g ,: SecondDigit . c l a s s , i temNotAvai lable ,

c l e a r S e l e c t i o n , State . unava i lab l e ,: SecondDigit . c l a s s , i temAvai lab le ,

d i spense , State . wantMore ,: : State . unava i l ab l e , # Current s t a t e

# Input , t e s t , t r a n s i t i o n , next s t a t e :: Quit . quit , nu l l ,

returnChange , State . qu ie scent ,: F i r s t D i g i t . c l a s s , nu l l ,

showDigit , State . s e l e c t i n g ,: : State . wantMore , # Current s t a t e

# Input , t e s t , t r a n s i t i o n , next s t a t e :: Quit . quit , nu l l ,

returnChange , State . qu ie scent ,: F i r s t D i g i t . c l a s s , nu l l ,

showDigit , State . s e l e c t i n g ,)# : ˜

Prueba de la maquina

# c04 : vendingmachine : VendingMachineTest . py# Demonstrates use o f StateMachine . py

vm = VendingMachine ( )f o r input in [

Money . quarter ,

64

Page 75: Traducción Thinking in Python

Money . quarter ,Money . do l l a r ,F i r s t D i g i t .A,SecondDigit . two ,F i r s t D i g i t .A,SecondDigit . two ,F i r s t D i g i t .C,SecondDigit . three ,F i r s t D i g i t .D,SecondDigit . one ,Quit . qu i t ] :

vm. nextState ( input )# : ˜

Herramientas

Otro enfoque, ya que su state machine (maquina de estado) se hacemas grande, es el uso de una herramienta de automatizacion me-diante el cual se configura una tabla y se deja que la herramientagenere el codigo state machine para usted. Esto puede ser creadopor sı mismo utilizando un lenguaje como Python, pero tambienhay herramientas libres de codigo abierto como Libero, en http:

//www.imatix.com/

Ejercicios

1. Crear un ejemplo del ”proxy virtual”.

2. Crear un ejemplo del proxy ”Referencia Inteligente” dondese guarda la cuenta del numero de llamadas a los metodos y a unobjeto en particular.

3. Crear un programa similar a ciertos sistemas DBMS (SistemaManejador de Bases de Datos) que solo permiten un cierto numerode conexiones en cualquier momento. Para implementar esto, uti-lizar ’Singleton’ como un sistema que controla el numero de objetos”connections” que se crean. Cuando un usuario ha terminado conuna conexion, el sistema debe ser informado de manera que puedacomprobar que la conexion volvera a ser reutilizada. Para garanti-zar esto, proporcionar un objeto proxy en lugar de una referencia a

65

Page 76: Traducción Thinking in Python

la conexion actual, y disenar el proxy de manera que haga que laconexion a ser liberada regrese al sistema.

4. Usando State, hacer una clase llamada UnpredictablePer-son que cambia el tipo de respuesta a su metodo hello( ) depen-diendo de que tipo de Mood esta adentro. Anada un tipo de claseadicional Mood llamada Prozac.

5. Cree una copia simple en la implementacion de escritura.

6. Aplicar TransitionTable.py al problema ”Washer : Lavadora”

7. Crear un sistema StateMachine mediante el cual el estadoactual junto con la informacion de entrada determina el siguienteestado en que el sistema estara. Para hacer esto, cada estado debealmacenar una referencia de nuevo al objeto proxy (el controladorde estado) de modo que pueda solicitar el cambio de estado. Useun HashMap para crear una tabla de estados, donde la clave esun String que nombre el nuevo estado y el valor es el nuevo estadodel objeto. Dentro de cada subclase state reemplazar un metodonextState( ) que tiene su propia tabla de transicion de estados.La entrada a nextState( ) debe ser una sola palabra que sale deun archivo de texto que contiene una palabra por lınea.

8. Modificar el ejercicio anterior para que la state machinepueda ser configurada mediante la creacion / modificacion de unasola matriz multidimensional.

9- Modificar el ejercicio ”mood” de la sesion anterior para quese convierta en una state machine (maquina de estado) usandoStateMachine.java

10. Crear un sistema elevador de state machine utilizandoStateMachine.java

11. Crear un sistema de calefaccion / aire acondicionado usandoStateMachine.java

12. Un generator(generador) es un objeto que produce otros ob-

66

Page 77: Traducción Thinking in Python

jetos, al igual que una fabrica, excepto que la funcion generadorno requiere ningun argumento. Cree un MouseMoveGeneratorque produce acciones correctas MouseMove como salidas cada vezque la funcion generador es llamada (es decir, el mouse debe mo-verse en la secuencia apropiada, por lo que los movimientos posiblesse basan en el movimiento anterior – esto es otra state machine).Agregue un metodo iterator( ) para producir un iterador, pero estemetodo debe tomar un argumento int que especifica el numero demovimientos a producir antes de hasNext( ) que retorna false.

67

Page 78: Traducción Thinking in Python

X: Decoradores:Seleccion de tipo dinamico

El uso de objetos en capas, para anadir de forma dinamicay transparente, responsabilidades a los objetos individualesse le conoce como el patron decorator (decorador).

Se utiliza cuando la subclasificacion crea demasiadas (o inflexi-bles) clases.

Todos los decoradores que envuelven al objeto original debentener la misma interfaz basica.

Dynamic proxy/surrogate? (¿Proxy dinamico/subrogado?)

Esto explica la estructura de herencia singular.

Tradeoff: la codificacion es mas complicado cuando se utilizadecoradores.

68

Page 79: Traducción Thinking in Python

Estructura Decorador basico

Un ejemplo cafe

Considere la posibilidad de bajar a la cafeterıa local, BeanMeUp, porun cafe. Normalmente hay muchas bebidas diferentes que se ofrecenexpresos, cafes con leche, tes, cafes helados, chocolate caliente para

nombrar unos pocos, ası como una serie de extras (que cuestan ex-tra tambien), tales como la crema batida o una inyeccion extra deexpreso. Usted tambien puede hacer ciertos cambios en su bebida,sin costo adicional, como pedir cafe descafeinado en lugar de caferegular.

Con bastante claridad si vamos a modelar todas estas bebidas ycombinaciones, habra diagramas de clases de tamano variable. Asıque para mayor claridad nosotros solo consideraremos un subcon-junto de los cafes: Expreso, cafe vienes, Caffe Latte, Cappuccino yCafe Mocha. Incluiremos 2 extras - crema batida (”batida”) y unainyeccion extra de cafe expreso; y tres cambios - descafeinado, lecheal vapor (”humeda”) y espuma de leche (”seco”).

Clase para cada combinacion

Una solucion es crear una clase individual para cada combinacion.Cadaclase describe la bebida y es responsable por el costo, etc. El menu

69

Page 80: Traducción Thinking in Python

resultante es enorme, y una parte del diagrama de clases serıa algocomo esto:

Aqui esta una de las combinaciones, una implementacion simplede un Cappuccino:

c l a s s Cappuccino :de f i n i t ( s e l f ) :

s e l f . c o s t = 1s e l f . d e s c r i p t i o n = ”Cappucino”

de f getCost ( s e l f ) :r e turn s e l f . c o s t

de f g e tDe s c r i p t i on ( s e l f ) :r e turn s e l f . d e s c r i p t i o n

La clave para el uso de este metodo es encontrar la combinacionparticular que desea. Ası, una vez que haya encontrado la bebidaque le gustarıa, aquı le presentamos una forma de hacerlo, como semuestra en la clase CoffeeShop en el siguiente codigo:

#: cX : decora to r : nodecorator s : CoffeeShop . py# Cof f ee example with no deco ra to r s

c l a s s Espresso : passc l a s s DoubleEspresso : pass

70

Page 81: Traducción Thinking in Python

c l a s s EspressoConPanna : pass

c l a s s Cappuccino :de f i n i t ( s e l f ) :

s e l f . c o s t = 1s e l f . d e s c r i p t i o n = ”Cappucino”

de f getCost ( s e l f ) :r e turn s e l f . c o s t

de f g e tDe s c r i p t i on ( s e l f ) :r e turn s e l f . d e s c r i p t i o n

c l a s s CappuccinoDecaf : passc l a s s CappuccinoDecafWhipped : passc l a s s CappuccinoDry : passc l a s s CappuccinoDryWhipped : passc l a s s CappuccinoExtraEspresso : passc l a s s CappuccinoExtraEspressoWhipped : passc l a s s CappuccinoWhipped : pass

c l a s s CafeMocha : passc l a s s CafeMochaDecaf : passc l a s s CafeMochaDecafWhipped :

de f i n i t ( s e l f ) :s e l f . c o s t = 1 .25s e l f . d e s c r i p t i o n = \

”Cafe Mocha deca f whipped cream”de f getCost ( s e l f ) :

r e turn s e l f . c o s tde f g e tDe s c r i p t i on ( s e l f ) :

r e turn s e l f . d e s c r i p t i o n

c l a s s CafeMochaExtraEspresso : passc l a s s CafeMochaExtraEspressoWhipped : passc l a s s CafeMochaWet : passc l a s s CafeMochaWetWhipped : passc l a s s CafeMochaWhipped : pass

c l a s s CafeLatte : passc l a s s CafeLatteDecaf : pass

71

Page 82: Traducción Thinking in Python

c l a s s CafeLatteDecafWhipped : passc l a s s CafeLatteExtraEspresso : passc l a s s CafeLatteExtraEspressoWhipped : passc l a s s CafeLatteWet : passc l a s s CafeLatteWetWhipped : pass

c l a s s CafeLatteWhipped : passcappuccino = Cappuccino ( )p r i n t ( cappuccino . g e tDe s c r i p t i on ( ) + ” : \$” +

‘ cappuccino . getCost ( ) ‘ )

cafeMocha = CafeMochaDecafWhipped ( )p r i n t ( cafeMocha . g e tDe s c r i p t i on ( )

+ ” : \$” + ‘ cafeMocha . getCost ( ) ‘ )#:˜

y aquı esta la salida correspondiente:

Cappucino : $1 . 0 Cafe Mocha deca f whipped cream : $1 .25

Se puede ver que la creacion de la combinacion particular quese desea es facil, ya que solo esta creando una instancia de unaclase. Sin embargo, hay una serie de problemas con este enfoque.En primer lugar, las combinaciones son fijadas estaticamente paraque cualquier combinacion de un cliente que, quiza desee ordenar,necesite ser creada por adelantado. En segundo lugar, el menu re-sultante es tan grande que la busqueda de su combinacion particulares difıcil y consume mucho tiempo.

El enfoque decorador

Otro enfoque serıa descomponer las bebidas en los diversos compo-nentes, tales como expreso y leche espumada, y luego dejar que elcliente combine los componentes para describir un cafe en particular.

Con el fin de hacer esto mediante programacion, utilizamos elpatron Decorador. Un decorador anade la responsabilidad de uncomponente envolviendolo, pero el decorador se ajusta a la interfazdel componente que encierra, por lo que la envoltura es transpar-ente. Los Decoradores tambien se pueden anidar sin la perdida de

72

Page 83: Traducción Thinking in Python

esta transparencia.

Metodos invocados en el Decorador a su vez pueden invocarmetodos en el componente, y puede realizar, por supuesto, el proce-samiento antes o despues de la invocacion.

Ası que si anadimos los metodos getTotalCost() y getDescrip-tion() a la interfaz DrinkComponent, un Espresso se ve ası:

c l a s s Espresso ( Decorator ) :c o s t = 0 .75 fd e s c r i p t i o n = ” e s p r e s s o ”pub l i c Espresso ( DrinkComponent ) :

Decorator . i n i t ( s e l f , component )

de f getTotalCost ( s e l f ) :r e turn s e l f . component . getTotalCost ( ) + cos t

de f g e tDe s c r i p t i on ( s e l f ) :r e turn s e l f . component . g e tDe s c r i p t i on ( ) +

d e s c r i p t i o n

Usted combina los componentes para crear una bebida de la sigu-iente manera, como se muestra en el siguiente codigo:

#: cX : decora to r : a l l d e c o r a t o r s : CoffeeShop . py# Cof f ee example us ing deco ra to r s

c l a s s DrinkComponent :de f g e tDe s c r i p t i on ( s e l f ) :

73

Page 84: Traducción Thinking in Python

r e turn s e l f . c l a s s . namede f getTotalCost ( s e l f ) :

r e turn s e l f . c l a s s . c o s t

c l a s s Mug( DrinkComponent ) :c o s t = 0 .0

c l a s s Decorator ( DrinkComponent ) :de f i n i t ( s e l f , drinkComponent ) :

s e l f . component = drinkComponentde f getTotalCost ( s e l f ) :

r e turn s e l f . component . getTotalCost ( ) + \DrinkComponent . getTotalCost ( s e l f )

de f g e tDe s c r i p t i on ( s e l f ) :r e turn s e l f . component . g e tDe s c r i p t i on ( ) + \

’ ’ + DrinkComponent . g e tDe s c r i p t i on ( s e l f )

c l a s s Espresso ( Decorator ) :c o s t = 0 .75de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Decaf ( Decorator ) :c o s t = 0 .0de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s FoamedMilk ( Decorator ) :c o s t = 0 .25de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s SteamedMilk ( Decorator ) :c o s t = 0 .25

de f i n i t ( s e l f , drinkComponent ) :Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Whipped( Decorator ) :c o s t = 0 .25de f i n i t ( s e l f , drinkComponent ) :

74

Page 85: Traducción Thinking in Python

Decorator . i n i t ( s e l f , drinkComponent )c l a s s Chocolate ( Decorator ) :

c o s t = 0 .25de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )cappuccino = Espresso ( FoamedMilk (Mug( ) ) )p r i n t cappuccino . g e tDe s c r i p t i on ( ) . s t r i p ( ) + \

” : \$” + ‘ cappuccino . getTotalCost ( ) ‘

cafeMocha = Espresso ( SteamedMilk ( Chocolate (Whipped( Decaf (Mug ( ) ) ) ) ) )

p r i n t cafeMocha . g e tDe s c r i p t i on ( ) . s t r i p ( ) + \” : \$” + ‘ cafeMocha . getTotalCost ( ) ‘

#:˜

Este enfoque, sin duda, proporciona la mayor flexibilidad y elmenu mas pequeno. Usted tiene un pequeno numero de compo-nentes para elegir, pero el montaje de la descripcion del cafe en-tonces se vuelve bastante arduo.

Si quiere describir un capuchino sencillo, se crea con

plainCap = Espresso ( FoamedMilk (Mug( ) ) )

Creando un Cafe Mocha descafeinado con crema batida requiereuna descripcion aun mas larga.

Compromiso

El enfoque anterior toma demasiado tiempo para describir un cafe.Tambien habra ciertas combinaciones que se iran describiendo demanera regular, y serıa conveniente tener una forma rapida de de-scribirlos.

El tercer enfoque es una mezcla de los 2 primeros enfoques, ycombina flexibilidad con la facilidad de uso. Este compromiso selogra mediante la creacion de un menu de tamano razonable de op-ciones basicas, que a menudo funcionan exactamente como son, pero

75

Page 86: Traducción Thinking in Python

si querıa decorarlos (crema batida, descafeinado etc), entonces ustedusarıa decoradores para hacer las modificaciones. Este es el tipo demenu que se le presenta en la mayorıa de tiendas de cafe.

Se muestra como crear una seleccion basica, ası como una se-leccion decorada:

#: cX : decora to r : compromise : CoffeeShop . py# Cof f ee example with a compromise o f ba s i c# combinations and deco ra to r s

c l a s s DrinkComponent :de f g e tDe s c r i p t i on ( s e l f ) :

r e turn s e l f . c l a s s . namede f getTotalCost ( s e l f ) :

r e turn s e l f . c l a s s . c o s t

c l a s s Espresso ( DrinkComponent ) :c o s t = 0 .75

c l a s s EspressoConPanna ( DrinkComponent ) :c o s t = 1 .0

c l a s s Cappuccino ( DrinkComponent ) :c o s t = 1 .0

c l a s s CafeLatte ( DrinkComponent ) :

76

Page 87: Traducción Thinking in Python

co s t = 1 .0

c l a s s CafeMocha ( DrinkComponent ) :c o s t = 1 .25

c l a s s Decorator ( DrinkComponent ) :de f i n i t ( s e l f , drinkComponent ) :

s e l f . component = drinkComponentde f getTotalCost ( s e l f ) :

r e turn s e l f . component . getTotalCost ( ) + \DrinkComponent . getTotalCost ( s e l f )

de f g e tDe s c r i p t i on ( s e l f ) :r e turn s e l f . component . g e tDe s c r i p t i on ( ) + \

’ ’ + DrinkComponent . g e tDe s c r i p t i on ( s e l f )

c l a s s ExtraEspresso ( Decorator ) :c o s t = 0 .75de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Whipped( Decorator ) :c o s t = 0 .50de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Decaf ( Decorator ) :c o s t = 0 .0de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Dry ( Decorator ) :c o s t = 0 .0

de f i n i t ( s e l f , drinkComponent ) :Decorator . i n i t ( s e l f , drinkComponent )

c l a s s Wet( Decorator ) :c o s t = 0 .0de f i n i t ( s e l f , drinkComponent ) :

Decorator . i n i t ( s e l f , drinkComponent )

77

Page 88: Traducción Thinking in Python

cappuccino = Cappuccino ( )p r i n t cappuccino . g e tDe s c r i p t i on ( ) + ” : \$” + \

‘ cappuccino . getTotalCost ( ) ‘

cafeMocha = Whipped( Decaf ( CafeMocha ( ) ) )p r i n t cafeMocha . g e tDe s c r i p t i on ( ) + ” : \$” + \

‘ cafeMocha . getTotalCost ( ) ‘#:˜

Usted puede ver que creando una seleccion basica es rapido yfacil, lo cual tiene sentido ya que seran descritos con regularidad.Describiendo una bebida decorada hay mas trabajo que cuando seutiliza una clase por combinacion, pero claramente menos trabajoque cuando se usan solo los decoradores.

El resultado final no es demasiadas clases, ni tampoco demasi-ados decoradores. La mayorıa de las veces es posible alejarse sinutilizar ningun decorador en absoluto, ası tenemos los beneficios deambos enfoques.

Otras consideraciones

¿Que sucede si decidimos cambiar el menu en una etapa poste-rior, tal como mediante la adicion de un nuevo tipo de bebida? Sihubieramos utilizado la clase por enfoque de combinacion, el efectode la adicion de un ejemplo adicional como Syrup serıa un crec-imiento exponencial en el numero de clases. Sin embargo, las impli-caciones para todos los enfoques decorador o de compromiso son losmismos , se crea una clase extra.

¿Que tal el efecto de cambiar el costo de la leche al vapor y es-puma de leche, cuando el precio de la leche sube? Teniendo unaclase para cada combinacion significa que usted necesita cambiar unmetodo en cada clase, y ası mantener muchas clases. Mediante el usode decoradores, el mantenimiento se reduce mediante la definicionde la logica en un solo lugar.

78

Page 89: Traducción Thinking in Python

Ejercicios

1. Anadir una clase Syrup al enfoque decorador descrito anterior-mente. A continuacion, cree un Cafe Latte (usted necesitara usarla leche al vapor con un expreso) con Syrup.

2. Repita el ejercicio 1 para el enfoque de compromiso.

3. Implementar el patron decorador para crear un restaurante dePizza, el cual tenga un menu de opciones, ası como la opcionde disenar su propia pizza. Siga el enfoque de compromiso paracrear un menu que consiste en una margherita, hawaianas, regina,y pizzas vegetarianas, con relleno (decoradores) de ajo, aceitunas,espinacas, aguacate, queso feta y Pepperdews. Crear una pizzahawaiana, ası como un Margherita decorado con espinacas, quesofeta, Pepperdews y aceitunas.

79

Page 90: Traducción Thinking in Python

Y: Iteradores:Algoritmos de desacoplamiento de contenedores

Alexander Stepanov penso durante anos sobre el problema de lastecnicas de programacion genericas antes de crear el STL (StandardTemplate Library) (junto con Dave Musser). Llego a la conclusionde que todos los algoritmos estan definidos en las estructuras alge-braicas – lo que llamarıamos contenedores.

En el proceso, Alexander Stepanov se dio cuenta que los iter-adores son fundamentales para el uso de algoritmos, porque de-sacoplan los algoritmos del tipo especifico de contenedor con queel algoritmo actualmente podrıa estar trabajando. Esto significaque usted puede describir el algoritmo sin preocuparse de la secuen-cia particular en la cual esta operando. En general, cualquier codigoque usted escribe utilizando iteradores es desacoplado de la estruc-tura de datos que el codigo esta manipulando, y por lo tanto sucodigo es mas general y reutilizable.

El uso de iteradores tambien amplıa su codigo en el campo de pro-gramacion funcional, cuyo objetivo es describir lo que un programaesta haciendo a cada paso en lugar de como lo esta haciendo. Esdecir, usted dice ”el tipo” en lugar de describir el tipo. El objetivodel STL de C ++ era proporcionar este enfoque a la programaciongenerica de C ++ (como el exito de este enfoque sera en la realidad,aun esta por verse).

Si ha utilizado contenedores en Java (y es difıcil escribir codigosin usarlos), ha utilizado iteradores – en forma del Enumerationen Java 1.0/1.1 y de Iterator en Java 2.0. Ası que usted ya debeestar familiarizado con su uso general.17

Debido a que en Java 2.0 los contenedores dependen en gran me-dida de los iteradores, se convierten en excelentes candidatos paralas tecnicas de programacion genericas / funcionales. Este capıtuloexplorara estas tecnicas mediante la conversion de los algoritmos deSTL para Java, para su uso con la librerıa de contenedor Java 2.

17Si no, consulte el Capıtulo 9, Holding Your Objects (Manteniendo Sus objetos), en Itera-tors in Thinking in Java segunda edicion (descargable gratuitamente desde www.bruceeckel.

com/).

80

Page 91: Traducción Thinking in Python

Iteradores Type-safe

En Thinking in Java, segunda edicion, muestro la creacion de uncontenedor Type-safe que solo aceptara un tipo particular de objeto.Un lector, Linda Pazzaglia, pregunto por el otro componente obviotype-safe, un iterador que trabajarıa con los contenedores basicosjava.util, pero imponer la restriccion de que el tipo de objetos so-bre los que itera sea de un tipo particular.

Si Java siempre incluye un mecanismo de plantilla, este tipo deiterador tendra la ventaja anadida de ser capaz de devolver un tipoespecıfico de objeto, pero sin las plantillas se ve obligado a retornarObjects genericos, o requerir un poco de codificacion manual paracada tipo que desea iterar. Tomare el enfoque anterior.

Una segunda decision de diseno involucra el tiempo en que se de-termina el tipo de objeto. Un enfoque consiste en tomar el tipo delprimer objeto que el iterador encuentra, pero esto es problematicodebido a que los contenedores pueden arreglar de nuevo los objetosde acuerdo con un mecanismo de ordenamiento interno (tal comouna tabla hash) y por lo tanto es posible obtener diferentes resul-tados de una iteracion a la siguiente. El enfoque seguro es exigir alusuario establecer el tipo durante la construccion del iterador.

Por ultimo, ¿como construir el iterador? No podemos reescribirlas librerıas de clases Java existentes que ya producen Enumer-ations e Iterators. Sin embargo, podemos utilizar el patron dediseno Decorator, y crear una clase que simplemente envuelve elEnumeration o Iterator que se produce, generando un nuevo ob-jeto que tiene el comportamiento de iteracion que queremos (quees, en este caso, lanzar un RuntimeException si se encuentra untipo incorrecto) pero con la misma interfaz que el Enumerationoriginal o Iterator, de modo que se puede utilizar en los mismoslugares (puede argumentar que esto es en realidad un patron Proxy,pero es mas probable Decorator debido a su intencion). Aquı estael codigo:

81

Page 92: Traducción Thinking in Python

# u t i l : TypedIterator . py

c l a s s TypedIterator ( I t e r a t o r ) :p r i v a t e I t e r a t o r impp r i v a t e Class typede f i n i t ( s e l f , I t e r a t o r i t , Class type ) :

imp = i ts e l f . type = type

de f hasNext ( s e l f ) :r e turn imp . hasNext ( )

de f remove ( s e l f ) : imp . remove ( )de f next ( s e l f ) :

Object obj = imp . next ( )i f ( ! type . i s I n s t a n c e ( obj ) )

throw ClassCastExcept ion (” TypedIterator f o r type ” + type +” encountered type : ” + obj . ge tC la s s ( ) )

r e turn obj# : ˜

82

Page 93: Traducción Thinking in Python

5: Fabricas:encapsularla creacion de objetos

Cuando descubre que es necesario agregar nuevos tipos a un sistema,el primer paso mas sensato es utilizar el polimorfismo para crear unainterfaz comun a esos nuevos tipos. Esto separa el resto del codigoen el sistema desde el conocimiento de los tipos especıficos que estaagregando. Nuevos tipos pueden anadirse sin molestar codigo exis-tente ... o al menos eso parece. Al principio parecerıa que el unicolugar que necesita cambiar el codigo en tal diseno es el lugar dondeusted hereda un nuevo tipo, pero esto no es del todo cierto. Ustedtodavıa debe crear un objeto de su nuevo tipo, y en el punto dela creacion debe especificar el constructor exacto a utilizar. Ası, siel codigo que crea objetos se distribuye a traves de su aplicacion,usted tiene el mismo problema cuando anade nuevos tipos — ustedtodavıa debe perseguir todos los puntos de su codigo en asuntos detipos. Esto sucede para ser la creacion del tipo que importa en estecaso en lugar del uso del tipo (que es atendido por el polimorfismo),pero el efecto es el mismo : la adicion de un nuevo tipo puede causarproblemas.

La solucion es forzar la creacion de objetos que se produzcan atraves de una fabrica (factory) comun, antes que permitir que elcodigo creacional se extienda por todo el sistema. Si todo el codigoen su programa debe ir a traves de esta fabrica cada vez que necesitacrear uno de sus objetos, entonces todo lo que debe hacer cuandoanada un nuevo objeto es modificar la fabrica.

Ya que cada programa orientado a objetos crea objetos, y puestoque es muy probable que se extienda su programa mediante laadicion de nuevos tipos, sospecho que las fabricas pueden ser lostipos mas universalmente utiles de los patrones de diseno.

Simple metodo de fabrica

Como ejemplo, vamos a revisar el sistema Shape. Un enfoque eshacer la fabrica de un metodo static de la clase base:

83

Page 94: Traducción Thinking in Python

#: c05 : shape fac t1 : ShapeFactory1 . py# A simple s t a t i c f a c t o r y method .from f u t u r e import gene ra to r simport random

c l a s s Shape ( ob j e c t ) :# Create based on c l a s s name :de f f a c t o r y ( type ) :

#return eva l ( type + ”( )” )i f type == ” C i r c l e ” : r e turn C i r c l e ( )i f type == ” Square ” : r e turn Square ( )a s s e r t 1 , ”Bad shape c r e a t i o n : ” + type

f a c t o r y = stat icmethod ( f a c t o r y )

c l a s s C i r c l e ( Shape ) :de f draw ( s e l f ) : p r i n t ” C i r c l e . draw”de f e r a s e ( s e l f ) : p r i n t ” C i r c l e . e r a s e ”

c l a s s Square ( Shape ) :de f draw ( s e l f ) : p r i n t ” Square . draw”de f e r a s e ( s e l f ) : p r i n t ” Square . e r a s e ”

# Generate shape name s t r i n g s :de f shapeNameGen (n ) :

types = Shape . s u b c l a s s e s ( )f o r i in range (n ) :

y i e l d random . cho i c e ( types ) . name

shapes = \[ Shape . f a c t o r y ( i ) f o r i in shapeNameGen ( 7 ) ]

f o r shape in shapes :shape . draw ( )shape . e r a s e ( )

#:˜

factory( ) toma un argumento que le permite determinar quetipo de Shape para crear; que pasa a ser un String en este caso,pero podrıa ser cualquier conjunto de datos. factory( ) es ahora el

84

Page 95: Traducción Thinking in Python

unico otro codigo en el sistema que necesita ser cambiado cuando untipo nuevo de Shape es agregado (los datos de inicializacion de losobjetos presumiblemente vendran de alguna parte fuera del sistema,y no son una matriz de codificacion fija como en el ejemplo anterior).

Tenga en cuenta que este ejemplo tambien muestra el nuevoPython 2.2 staticmethod( ) tecnicas para crear metodos estaticosen una clase.

Tambien he utilizado una herramienta que es nueva en Python 2.2llamada un generador (generator). Un generador es un caso especialde una fabrica: es una fabrica que no toma ningun argumento conel fin de crear un nuevo objeto. Normalmente usted entrega algunainformacion a una fabrica con el fin de decirle que tipo de objetocrear y como crearlo, pero un generador tiene algun tipo de algo-ritmo interno que le dice que y como construirlo. Esto ”se generade la nada” en vez de decirle que crear.

Ahora, esto puede no parecer consistente con el codigo que ustedve arriba:

f o r i in shapeNameGen (7)

parece que hay una inicializacion en la anterior lınea. Aquı esdonde un generador es un poco extrano – cuando llama una funcionque contiene una declaracion yield (yield es una nueva palabraclave que determina que una funcion es un generador), esa funcionen realidad devuelve un objeto generador que tiene un iterador. Esteiterador se utiliza implıcitamente en la sentencia for anterior, porlo que parece que se esta iterando a traves de la funcion generador,no lo que devuelve. Esto se hizo para mayor comodidad en su uso.

Por lo tanto, el codigo que usted escribe es en realidad una especiede fabrica, que crea los objetos generadores que hacen la generacionreal. Usted puede utilizar el generador de forma explıcita si quiere,por ejemplo:

gen = shapeNameGen (7)p r i n t gen . next ( )

85

Page 96: Traducción Thinking in Python

Ası que next() es el metodo iterador que es realmente llamadoa generar el siguiente objeto, y que no toma ningun argumento.shapeNameGen( ) es la fabrica, y gen es el generador.

En el interior del generador de fabrica, se puede ver la llamada asubclasses ( ), que produce una lista de referencias a cada una

de las subclases de Shape (que debe ser heredado de object paraque esto funcione). Debe tener en cuenta, sin embargo, que esto solofunciona para el primer nivel de herencia de Item, ası que si ustedfuera a heredar una nueva clase de Circle, no aparecerıa en la listagenerada por subclasses ( ). Si necesita crear una jerarquıa masprofunda de esta manera, debe recurse18 la lista subclasses ( ).

Tambien tenga en cuenta la sentencia shapeNameGen( )

types = Shape . s u b c l a s s e s ( )

Solo se ejecuta cuando se produce el objeto generador; cada vezque se llama al metodo next( ) de este objeto generador (que, comose senalo anteriormente, puede suceder de manera implıcita), solose ejecuta el codigo en el bucle for, por lo que no tiene ejecucionderrochadora (como lo harıa si esto fuera una funcion ordinaria).

Fabricas polimorficas

El metodo estatico factory( ) en el ejemplo anterior obliga a todaslas operaciones de creacion que se centran en un solo lugar, ası quees el unico lugar que necesita cambiar el codigo. Esto es ciertamenteuna solucion razonable, ya que arroja un cuadro alrededor del pro-ceso de creacion de objetos. Sin embargo, el libro Design Patternsenfatiza en que la razon para el patron de Factory Method es paraque diferentes tipos de fabricas pueden ser subclases de la fabricabasica (el diseno anterior se menciona como un caso especial). Sinembargo, el libro no proporciona un ejemplo, pero en su lugar jus-tamente repite el ejemplo utilizado para el Abstract Factory (ustedvera un ejemplo de esto en la siguiente seccion). Aquı ShapeFac-tory1.py esta modificado por lo que los metodos de fabrica estan

18utilizar la recursion en la programacion, usar funciones recursivas (que se repiten) en lacreacion de un programa

86

Page 97: Traducción Thinking in Python

en una clase separada como funciones virtuales. Observe tambienque las clases especıficas de Shape se cargan dinamicamente pordemanda:

#: c05 : shape fac t2 : ShapeFactory2 . py# Polymorphic f a c t o r y methods .from f u t u r e import gene ra to r simport random

c l a s s ShapeFactory :f a c t o r i e s = de f addFactory ( id , shapeFactory ) :

ShapeFactory . f a c t o r i e s . put [ id ] = shapeFactoryaddFactory = stat icmethod ( addFactory )# A Template Method :de f createShape ( id ) :

i f not ShapeFactory . f a c t o r i e s . has key ( id ) :ShapeFactory . f a c t o r i e s [ id ] = \

eva l ( id + ’ . Factory ( ) ’ )r e turn ShapeFactory . f a c t o r i e s [ id ] . c r e a t e ( )

createShape = stat icmethod ( createShape )

c l a s s Shape ( ob j e c t ) : pass

c l a s s C i r c l e ( Shape ) :de f draw ( s e l f ) : p r i n t ” C i r c l e . draw”de f e r a s e ( s e l f ) : p r i n t ” C i r c l e . e r a s e ”c l a s s Factory :

de f c r e a t e ( s e l f ) : r e turn C i r c l e ( )

c l a s s Square ( Shape ) :de f draw ( s e l f ) :

p r i n t ” Square . draw”de f e r a s e ( s e l f ) :

p r i n t ” Square . e r a s e ”c l a s s Factory :

de f c r e a t e ( s e l f ) : r e turn Square ( )

de f shapeNameGen (n ) :

87

Page 98: Traducción Thinking in Python

types = Shape . s u b c l a s s e s ( )f o r i in range (n ) :

y i e l d random . cho i c e ( types ) . name

shapes = [ ShapeFactory . createShape ( i )f o r i in shapeNameGen ( 7 ) ]

f o r shape in shapes :shape . draw ( )

shape . e r a s e ( )#:˜

Ahora el metodo de fabrica aparece en su propia clase, ShapeFac-tory, como el metodo create( ). Los diferentes tipos de formasdeben crear cada uno su propia fabrica con un metodo create() para crear un objeto de su propio tipo. La creacion real de for-mas se realiza llamando ShapeFactory.createShape( ), que es unmetodo estatico que utiliza el diccionario en ShapeFactory paraencontrar el objeto de fabrica apropiado basado en un identificadorque se le pasa. La fabrica se utiliza de inmediato para crear el objetoshape, pero se puede imaginar un problema mas complejo donde sedevuelve el objeto de fabrica apropiado y luego utilizado por la per-sona que llama para crear un objeto de una manera mas sofisticada.Ahora bien, parece que la mayor parte del tiempo usted no necesitala complejidad del metodo de fabrica polimorfico, y un solo metodoestatico en la clase base (como se muestra en ShapeFactory1.py)funcionara bien. Observe que ShapeFactory debe ser inicializadopor la carga de su diccionario con objetos de fabrica, que tiene lugaren la clausula de inicializacion estatica de cada una de las imple-mentaciones de forma.

Fabricas abstractas

El patron Abstract Factory (Fabrica abstracta) se parece a los ob-jetos de fabrica que hemos visto anteriormente, no con uno, sinovarios metodos de fabrica. Cada uno de los metodos de fabrica creaun tipo diferente de objeto. La idea es que en el punto de la creaciondel objeto de fabrica, usted decide como se usaran todos los obje-tos creados por esa fabrica. El ejemplo dado en Design Patterns

88

Page 99: Traducción Thinking in Python

implementa portabilidad a traves de diferentes interfaces graficasde usuario (GUI): crea un objeto de fabrica apropiada a la interfazgrafica de usuario que se esta trabajando, y a partir de entoncescuando se pida un menu, boton, control deslizante, etc. se crearaautomaticamente la version adecuada de ese ıtem para la interfazgrafica de usuario. De esta manera usted es capaz de aislar, en unsolo lugar, el efecto de cambiar de una interfaz grafica de usuario aotra.

Como otro ejemplo, supongamos que usted esta creando un en-torno de juego de uso general y usted quiere ser capaz de soportardiferentes tipos de juegos. Ası es como puede parecer utilizando unafabrica abstracta:

#: c05 : Games . py# An example o f the Abstract Factory pattern .

c l a s s Obstac le :de f a c t i on ( s e l f ) : pass

c l a s s Player :de f interactWith ( s e l f , o b s t a c l e ) : pass

c l a s s Kitty ( Player ) :de f interactWith ( s e l f , o b s t a c l e ) :

p r i n t ” Kitty has encountered a ” ,o b s t a c l e . a c t i on ( )

c l a s s KungFuGuy( Player ) :de f interactWith ( s e l f , o b s t a c l e ) :

p r i n t ”KungFuGuy now b a t t l e s a ” ,o b s t a c l e . a c t i on ( )

c l a s s Puzzle ( Obstac le ) :de f a c t i on ( s e l f ) :

p r i n t ” Puzzle ”

c l a s s NastyWeapon ( Obstac le ) :de f a c t i on ( s e l f ) :

89

Page 100: Traducción Thinking in Python

pr in t ”NastyWeapon”

# The Abstract Factory :c l a s s GameElementFactory :

de f makePlayer ( s e l f ) : passde f makeObstacle ( s e l f ) : pass

# Concrete f a c t o r i e s :c l a s s Kitt i e sAndPuzz les ( GameElementFactory ) :

de f makePlayer ( s e l f ) : r e turn Kitty ( )de f makeObstacle ( s e l f ) : r e turn Puzzle ( )

c l a s s KillAndDismember ( GameElementFactory ) :de f makePlayer ( s e l f ) : r e turn KungFuGuy( )de f makeObstacle ( s e l f ) : r e turn NastyWeapon ( )

c l a s s GameEnvironment :de f i n i t ( s e l f , f a c t o r y ) :

s e l f . f a c t o r y = f a c t o r ys e l f . p = f a c t o r y . makePlayer ( )s e l f . ob = f a c t o r y . makeObstacle ( )

de f play ( s e l f ) :s e l f . p . interactWith ( s e l f . ob )

g1 = GameEnvironment ( Kitt i e sAndPuzz les ( ) )g2 = GameEnvironment ( KillAndDismember ( ) )g1 . play ( )g2 . play ( )#:˜

En este entorno, los objetos Player interactuan con los objetosObstacle pero hay diferentes tipos de jugadores y obstaculos, de-pendiendo de que tipo de juego esta jugando. Usted determina eltipo de juego al elegir un determinado GameElementFactory, yluego el GameEnvironment controla la configuracion y el desar-rollo del juego. En este ejemplo, la configuracion y el juego es muysimple, pero esas actividades (las initial conditions : condicionesiniciales y el state change : cambio de estado) pueden determinargran parte el resultado del juego. Aquı, GameEnvironment noesta disenado para ser heredado, aunque podrıa muy posiblemente

90

Page 101: Traducción Thinking in Python

tener sentido hacer eso.

Esto tambien contiene ejemplos de Double Dispatching : Despa-cho doble y el Factory Method : Metodo de fabrica, ambos de loscuales se explicaran mas adelante.

Claro, la plataforma anterior deObstacle, Player y GameEle-mentFactory (que fue traducido de la version Java de este ejemplo)es innecesaria – que solo es requerido para lenguajess que tienencomprobacion de tipos estaticos. Siempre y cuando las clases dePython concretas siguen la forma de las clases obligatorias, no nece-sitamos ninguna clase de base:

#: c05 : Games2 . py# S i m p l i f i e d Abstract Factory .

c l a s s Kitty :de f interactWith ( s e l f , o b s t a c l e ) :

p r i n t ” Kitty has encountered a ” ,o b s t a c l e . a c t i on ( )

c l a s s KungFuGuy :de f interactWith ( s e l f , o b s t a c l e ) :

p r i n t ”KungFuGuy now b a t t l e s a ” ,o b s t a c l e . a c t i on ( )

c l a s s Puzzle :de f a c t i on ( s e l f ) : p r i n t ” Puzzle ”

c l a s s NastyWeapon :de f a c t i on ( s e l f ) : p r i n t ”NastyWeapon”

# Concrete f a c t o r i e s :c l a s s Kitt i e sAndPuzz les :

de f makePlayer ( s e l f ) : r e turn Kitty ( )de f makeObstacle ( s e l f ) : r e turn Puzzle ( )

c l a s s KillAndDismember :de f makePlayer ( s e l f ) : r e turn KungFuGuy( )

91

Page 102: Traducción Thinking in Python

de f makeObstacle ( s e l f ) : r e turn NastyWeapon ( )

c l a s s GameEnvironment :de f i n i t ( s e l f , f a c t o r y ) :

s e l f . f a c t o r y = f a c t o r ys e l f . p = f a c t o r y . makePlayer ( )s e l f . ob = f a c t o r y . makeObstacle ( )

de f play ( s e l f ) :s e l f . p . interactWith ( s e l f . ob )

g1 = GameEnvironment ( Kitt i e sAndPuzz les ( ) )g2 = GameEnvironment ( KillAndDismember ( ) )g1 . play ( )g2 . play ( )#:˜

Otra manera de poner esto es que toda la herencia en Python esla herencia de implementacion; ya que Python hace su la compro-bacion de tipo en tiempo de ejecucion, no hay necesidad de utilizarla herencia de interfaces para que pueda convertirla al tipo base.

Es posible que desee estudiar los dos ejemplos de comparacion,sin embargo. ¿La primera de ellas agrega suficiente informacionutil sobre el patron que vale la pena mantener algun aspecto de lamisma? Tal vez todo lo que necesita es ”clases de etiquetado” comoesta:

c l a s s Obstac le : passc l a s s Player : passc l a s s GameElementFactory : pass

A continuacion, la herencia solo sirve para indicar el tipo de lasclases derivadas.

Ejercicios

1. Agregar la clase Triangle a ShapeFactory1.py2. Agregar la clase Triangle a ShapeFactory2.py3. Agregar un nuevo tipo de GameEnvironment llamado Gnome-sAndFairies a GameEnvironment.py

92

Page 103: Traducción Thinking in Python

4. Modificar ShapeFactory2.py para que utilice una Abstract Fac-tory para crear diferentes conjuntos de formas (por ejemplo, un tipoparticular de objeto de fabrica crea ”formas gruesas,” otra crea ”for-mas delgadas,” pero cada objeto fabrica puede crear todas las for-mas: cırculos, cuadrados, triangulos, etc.).

93

Page 104: Traducción Thinking in Python

6 : Funcion de los objetos

Jim Coplien acuna19 el termino functor que es un objeto cuyo unicoproposito es encapsular una funcion (ya que ”Functor” tiene un sig-nificado en matematicas, Voy a utilizar el termino mas explıcitofunction object). El punto es desacoplar la eleccion de la funcionque se llamara desde el sitio en que esa funcion se llama.

Este termino se menciona pero no se utiliza en Design Patterns.Sin embargo, el tema del objeto de funcion se repite en una serie depatrones en ese libro.

Command: la eleccion de la operacion en tiempo de eje-cucion

Esta es la funcion del objeto en su sentido mas puro: un objeto quees un metodo 20. Al envolver un metodo en un objeto, usted puedepasarlo a otros metodos u objetos como un parametro, para decirleque realice esta operacion en particular durante en el proceso y quese cumpla la peticion.

#: c06 : CommandPattern . py

c l a s s Command:de f execute ( s e l f ) : pass

c l a s s Loony (Command ) :de f execute ( s e l f ) :

p r i n t ”You ’ re a loony . ”

c l a s s NewBrain (Command ) :de f execute ( s e l f ) :

p r i n t ”You might even need a new bra in . ”

c l a s s Af ford (Command ) :de f execute ( s e l f ) :

19en Advanced C++:Programming Styles And Idioms (Addison-Wesley, 1992)20En el lenguaje Python, todas las funciones son ya objetos y ası el patron Command suele

ser redundante.

94

Page 105: Traducción Thinking in Python

pr in t ” I couldn ’ t a f f o r d a whole new bra in . ”

# An ob j e c t that ho lds commands :c l a s s Macro :

de f i n i t ( s e l f ) :s e l f . commands = [ ]

de f add ( s e l f , command ) :s e l f . commands . append (command)

de f run ( s e l f ) :f o r c in s e l f . commands :

c . execute ( )

macro = Macro ( )macro . add ( Loony ( ) )macro . add ( NewBrain ( ) )macro . add ( Afford ( ) )macro . run ( )#:˜

El punto principal de Command es para que pueda entregaruna accion deseada a un metodo u objeto. En el ejemplo anterior,esto proporciona una manera de hacer cola en un conjunto de ac-ciones a realizar colectivamente. En este caso, ello le permite creardinamicamente un nuevo comportamiento, algo que solo se puedehacer normalmente escribiendo un nuevo codigo, pero en el ejemploanterior se podrıa hacer mediante la interpretacion de un script (verel patron Interpreter si lo que necesita hacer es un proceso muycomplejo).

Design Patterns dice que “Los comandos son un reemplazo ori-entado a objetos para el retorno de llamados21.” Sin embargo, creoque la palabra “back” es una parte esencial del concepto de retornode llamados. Es decir, creo que un retorno de llamado en realidadse remonta al creador del retorno de llamados. Por otro lado, conun objeto Command normalmente se acaba de crear y entregar aalgun metodo o a un objeto, y no esta conectado de otra forma enel transcurso del tiempo con el objeto Command. Esa es mi opinional respecto, de todos modos. Mas adelante en este libro, combinoun grupo de patrones de diseno bajo el tıtulo de “devoluciones de

21Pagina 235

95

Page 106: Traducción Thinking in Python

llamada.”

Estrategia: elegir el algoritmo en tiempo de ejecucion

Strategy parece ser una familia de clases Command, todo heredadode la misma base. Pero si nos fijamos en Command, vera que tienela misma estructura: una jerarquıa de objetos de funcion. La difer-encia esta en la forma en que se utiliza esta jerarquıa. Como se ve enc12:DirList.py, usted utiliza Command para resolver un problemaparticular — en este caso, seleccionando los archivos de una lista.La ”cosa que permanece igual” es el cuerpo del metodo que estasiendo llamado, y la parte que varıa es aislado en el objeto funcion.Me atreverıa a decir que Command proporciona flexibilidad. mien-tras usted esta escribiendo el programa, visto que la flexibilidad deStrategy esta en tiempo de ejecucion.

Strategy tambien agrega un ”Contexto” que puede ser una clasesustituta que controla la seleccion y uso de la estrategia de objetoparticular — al igual que State! Esto es lo que parece:

96

Page 107: Traducción Thinking in Python

#: c06 : StrategyPattern . py

# The s t r a t e g y i n t e r f a c e :c l a s s FindMinima :

# Line i s a sequence o f po in t s :de f a lgor i thm ( s e l f , l i n e ) : pass

# The var i ous s t r a t e g i e s :c l a s s LeastSquares ( FindMinima ) :

de f a lgor i thm ( s e l f , l i n e ) :r e turn [ 1 . 1 , 2 . 2 ] # Dummy

c l a s s NewtonsMethod ( FindMinima ) :de f a lgor i thm ( s e l f , l i n e ) :

r e turn [ 3 . 3 , 4 . 4 ] # Dummy

c l a s s B i s e c t i o n ( FindMinima ) :de f a lgor i thm ( s e l f , l i n e ) :

r e turn [ 5 . 5 , 6 . 6 ] # Dummyc l a s s ConjugateGradient ( FindMinima ) :

de f a lgor i thm ( s e l f , l i n e ) :r e turn [ 3 . 3 , 4 . 4 ] # Dummy

# The ”Context” c o n t r o l s the s t r a t e g y :c l a s s MinimaSolver :

de f i n i t ( s e l f , s t r a t e g y ) :s e l f . s t r a t e g y = s t r a t e g y

de f minima ( s e l f , l i n e ) :r e turn s e l f . s t r a t e g y . a lgor i thm ( l i n e )

de f changeAlgorithm ( s e l f , newAlgorithm ) :s e l f . s t r a t e g y = newAlgorithm

s o l v e r = MinimaSolver ( LeastSquares ( ) )l i n e = [

1 . 0 , 2 . 0 , 1 . 0 , 2 . 0 , −1.0 , 3 . 0 , 4 . 0 , 5 . 0 , 4 . 0]

p r i n t s o l v e r . minima ( l i n e )s o l v e r . changeAlgorithm ( B i s e c t i o n ( ) )p r i n t s o l v e r . minima ( l i n e )#:˜

97

Page 108: Traducción Thinking in Python

Observe similitud con el metodo de plantilla – TM afirma dis-tincion que este tiene mas de un metodo para llamar, hace las cosaspor partes. Ahora bien, no es probable que la estrategia de ob-jeto tendrıa mas de un llamado al metodo; considere el sistema decumplimiento de pedidos de Shalloway con informacion de los paısesen cada estrategia.

Ejemplo de Estrategia (Strategy) de Python estandar: sort( )toma un segundo argumento opcional que actua como un objeto decomparacion; esta es una estrategia.

98

Page 109: Traducción Thinking in Python

Chain of Responsibility (Cadena de responsabilidad)

Chain of Responsibility (Cadena de responsabilidad) podrıa ser pen-sado como una generalizacion dinamica de recursividad utilizandoobjetos Strategy. Usted hace un llamado, y cada Strategy es un in-tento de enlace de secuencia para satisfacer el llamado. El procesotermina cuando una de las estrategias es exitosa o termina la ca-dena. En la recursividad, un metodo se llama a sı mismo una y otravez hasta que se alcance una condicion de terminacion; con Chainof Responsibility, un metodo se llama a sı mismo, que (moviendopor la cadena de Strategies) llama una implementacion diferente delmetodo, etc., hasta que se alcanza una condicion de terminacion. Lacondicion de terminacion es o bien que se alcanza la parte inferiorde la cadena (en cuyo caso se devuelve un objeto por defecto; ustedpuede o no puede ser capaz de proporcionar un resultado por defectoası que usted debe ser capaz de determinar el exito o el fracaso dela cadena) o una de las Strategies tiene exito.

En lugar de llamar a un solo metodo para satisfacer una solicitud,multiples metodos de la cadena tienen la oportunidad de satisfacerla solicitud, por lo que tiene el sabor de un sistema experto. Dadoque la cadena es efectivamente una lista enlazada, puede ser creadadinamicamente, por lo que tambien podrıa pensar en ello como unamas general, declaracion switch dinamicamente construida.

En el GoF, hay una buena cantidad de discusion sobre comocrear la cadena de responsabilidad como una lista enlazada. Ahorabien, cuando nos fijamos en el patron realmente no deberıa importarcomo se mantiene la cadena; eso es un detalle de implementacion.Ya que GoF fue escrito antes de la Librerıa de plantillas estandarSTL (Standard Template Library ) fue incorporado en la mayorıade los compiladores de C ++, la razon de esto mas probable deesto: (1) no habıa ninguna lista y por lo tanto tuvieron que crearuna y (2) las estructuras de datos a menudo se ensenan como unahabilidad fundamental en el mundo academico, y la idea de que lasestructuras de datos deben ser herramientas estandar disponiblescon el lenguaje de programacion que pudo o no habersele ocurrido alos autores GoF. Yo sostengo que la implementacion de Chain of Re-sponsibility como una cadena (especıficamente, una lista enlazada)no anade nada a la solucion y puede facilmente ser implementado

99

Page 110: Traducción Thinking in Python

utilizando una lista estandar de Python, como se muestra mas abajo.Ademas, usted vera que he ido haciendo esfuerzos para separar laspartes de gestion de la cadena de la implementacion de las distintasStrategies, de modo que el codigo puede ser mas facilmente reuti-lizado.

En StrategyPattern.py, anterior, lo que probablemente se quierees encontrar automaticamente una solucion. Chain of Responsibilityproporciona una manera de hacer esto por el encadenamiento de losobjetos Strategy juntos y proporcionando un mecanismo automaticode recursividad a traves de cada uno en la cadena:

#: c06 : Cha inOfRespons ib i l i ty . py

# Carry the in fo rmat ion in to the s t r a t e g y :c l a s s Messenger : pass

# The Result ob j e c t c a r r i e s the r e s u l t data and# whether the s t r a t e g y was s u c c e s s f u l :c l a s s Result :

de f i n i t ( s e l f ) :s e l f . succeeded = 0

de f i s S u c c e s s f u l ( s e l f ) :r e turn s e l f . succeeded

de f s e t S u c c e s s f u l ( s e l f , succeeded ) :s e l f . succeeded = succeeded

c l a s s Strategy :de f c a l l ( messenger ) : passde f s t r ( s e l f ) :

r e turn ” Trying ” + s e l f . c l a s s . name \+ ” algor i thm ”

# Manage the movement through the chain and# f i n d a s u c c e s s f u l r e s u l t :c l a s s ChainLink :

de f i n i t ( s e l f , chain , s t r a t e g y ) :s e l f . s t r a t e g y = s t r a t e g ys e l f . chain = chain

100

Page 111: Traducción Thinking in Python

s e l f . chain . append ( s e l f )

de f next ( s e l f ) :# Where t h i s l i n k i s in the chain :l o c a t i o n = s e l f . chain . index ( s e l f )i f not s e l f . end ( ) :

r e turn s e l f . chain [ l o c a t i o n + 1 ]

de f end ( s e l f ) :r e turn ( s e l f . chain . index ( s e l f ) + 1 >=

len ( s e l f . chain ) )de f c a l l ( s e l f , messenger ) :

r = s e l f . s t r a t e g y ( messenger )i f r . i s S u c c e s s f u l ( ) or s e l f . end ( ) : r e turn rre turn s e l f . next ( ) ( messenger )

# For t h i s example , the Messenger# and Result can be the same type :c l a s s LineData ( Result , Messenger ) :

de f i n i t ( s e l f , data ) :s e l f . data = data

de f s t r ( s e l f ) : r e turn ‘ s e l f . data ‘

c l a s s LeastSquares ( St rategy ) :de f c a l l ( s e l f , messenger ) :

p r i n t s e l fl i n e d a t a = messenger# [ Actual t e s t / c a l c u l a t i o n here ]r e s u l t = LineData ( [ 1 . 1 , 2 . 2 ] ) # Dummy datar e s u l t . s e t S u c c e s s f u l (0 )re turn r e s u l t

c l a s s NewtonsMethod ( Strategy ) :de f c a l l ( s e l f , messenger ) :

p r i n t s e l fl i n e d a t a = messenger# [ Actual t e s t / c a l c u l a t i o n here ]r e s u l t = LineData ( [ 3 . 3 , 4 . 4 ] ) # Dummy datar e s u l t . s e t S u c c e s s f u l (0 )

101

Page 112: Traducción Thinking in Python

r e turn r e s u l t

c l a s s B i s e c t i o n ( Strategy ) :de f c a l l ( s e l f , messenger ) :

p r i n t s e l fl i n e d a t a = messenger# [ Actual t e s t / c a l c u l a t i o n here ]r e s u l t = LineData ( [ 5 . 5 , 6 . 6 ] ) # Dummy datar e s u l t . s e t S u c c e s s f u l (1 )re turn r e s u l t

c l a s s ConjugateGradient ( Strategy ) :de f c a l l ( s e l f , messenger ) :

p r i n t s e l fl i n e d a t a = messenger# [ Actual t e s t / c a l c u l a t i o n here ]r e s u l t = LineData ( [ 7 . 7 , 8 . 8 ] ) # Dummy datar e s u l t . s e t S u c c e s s f u l (1 )re turn r e s u l t

s o l u t i o n s = [ ]s o l u t i o n s = [

ChainLink ( s o l u t i o n s , LeastSquares ( ) ) ,ChainLink ( s o l u t i o n s , NewtonsMethod ( ) ) ,ChainLink ( s o l u t i o n s , B i s e c t i o n ( ) ) ,ChainLink ( s o l u t i o n s , ConjugateGradient ( ) )

]

l i n e = LineData ( [1 . 0 , 2 . 0 , 1 . 0 , 2 . 0 , −1.0 ,

3 . 0 , 4 . 0 , 5 . 0 , 4 . 0] )p r i n t s o l u t i o n s [ 0 ] ( l i n e )#:˜

Ejercicios

1. Usar Command en el capitulo 3, ejercicio 1.2. Implementar Chain of Responsibility (cadena de responsabili-

102

Page 113: Traducción Thinking in Python

dad) para crear un “sistema experto” que resuelva problemas, porintentos sucesivos para una solucion, luego otra, hasta que algunacoincida. Usted debe ser capaz de anadir dinamicamente solucionespara el sistema experto. La prueba para la solucion simplementedebe ser un emparejamiento de strings, pero cuando una solucion seajusta, el sistema experto debe devolver el tipo apropiado de objetoProblemSolver.

103

Page 114: Traducción Thinking in Python

7: Cambiando la interfaz.

A veces el problema que usted esta resolviendo es tan simple como:“Yo no tengo la interfaz que quiero.” Dos de los patrones en De-sign Patterns resuelven este problema: Adapter(Adaptador) tomaun tipo y produce una interfaz a algun otro tipo. Facade (Fachada)crea una interfaz para un conjunto de clases, simplemente para pro-porcionar una manera mas comoda para hacer frente a una bib-lioteca o un paquete de recursos.

Adapter (Adaptador)

Cuando tienes this, y usted necesita that, Adapter (Adaptador) re-suelve el problema. El unico requisito es producir un that, y hay unnumero de maneras para que usted pueda lograr esta adaptacion.

#: c07 : Adapter . py# Var ia t i on s on the Adapter pattern .

c l a s s WhatIHave :de f g ( s e l f ) : passde f h( s e l f ) : pass

c l a s s WhatIWant :de f f ( s e l f ) : pass

c l a s s ProxyAdapter (WhatIWant ) :de f i n i t ( s e l f , whatIHave ) :

s e l f . whatIHave = whatIHave

de f f ( s e l f ) :# Implement behavior us ing# methods in WhatIHave :s e l f . whatIHave . g ( )s e l f . whatIHave . h ( )

c l a s s WhatIUse :de f op ( s e l f , whatIWant ) :

whatIWant . f ( )

104

Page 115: Traducción Thinking in Python

# Approach 2 : bu i ld adapter use in to op ( ) :c l a s s WhatIUse2 ( WhatIUse ) :

de f op ( s e l f , whatIHave ) :ProxyAdapter ( whatIHave ) . f ( )

# Approach 3 : bu i ld adapter i n to WhatIHave :c l a s s WhatIHave2 (WhatIHave , WhatIWant ) :

de f f ( s e l f ) :s e l f . g ( )s e l f . h ( )

# Approach 4 : use an inner c l a s s :c l a s s WhatIHave3 ( WhatIHave ) :

c l a s s InnerAdapter (WhatIWant ) :de f i n i t ( s e l f , outer ) :

s e l f . outer = outerde f f ( s e l f ) :

s e l f . outer . g ( )s e l f . outer . h ( )

de f whatIWant ( s e l f ) :r e turn WhatIHave3 . InnerAdapter ( s e l f )

whatIUse = WhatIUse ( )whatIHave = WhatIHave ( )adapt= ProxyAdapter ( whatIHave )whatIUse2 = WhatIUse2 ( )whatIHave2 = WhatIHave2 ( )whatIHave3 = WhatIHave3 ( )whatIUse . op ( adapt )# Approach 2 :whatIUse2 . op ( whatIHave )# Approach 3 :whatIUse . op ( whatIHave2 )# Approach 4 :whatIUse . op ( whatIHave3 . whatIWant ( ) )#:˜

Estoy tomando libertades con el termino “proxy” aquı, porqueen Design Patterns afirman que un proxy debe tener una interfaz

105

Page 116: Traducción Thinking in Python

identica con el objeto que es para un sustituto (surrogate). Sin em-bargo, si tiene las dos palabras juntas: “proxy adapter” tal vez seamas razonable.

Facade (Fachada)

Un principio general que aplico cuando estoy tratando de moldearlos requisitos de un objeto es ”Si algo es feo, esconderlo dentro deun objeto.” Esto es basicamente lo que logra Facade. Si usted tieneuna coleccion bastante confusa de las clases y las interacciones queel programador cliente no tiene realmente necesidad de ver, entoncesusted puede crear una interfaz que es util para el programador clientey que solo presenta lo que sea necesario.

Facade se suele implementar como fabrica abstracta Singleton.Claro, usted puede conseguir facilmente este efecto mediante lacreacion de una clase que contiene metodos de fabrica static:

# c07 : Facade . pyc l a s s A:

de f i n i t ( s e l f , x ) : passc l a s s B:

de f i n i t ( s e l f , x ) : passc l a s s C:

de f i n i t ( s e l f , x ) : pass

# Other c l a s s e s that aren ’ t exposed by the# facade go here . . .c l a s s Facade :

de f makeA( x ) : r e turn A( x )makeA = stat icmethod (makeA)de f makeB( x ) : r e turn B( x )makeB = stat icmethod (makeB)de f makeC( x ) : r e turn C( x )makeC = stat icmethod (makeC)

# The c l i e n t programmer ge t s the o b j e c t s# by c a l l i n g the s t a t i c methods :a = Facade . makeA ( 1 ) ;b = Facade . makeB ( 1 ) ;

106

Page 117: Traducción Thinking in Python

c = Facade . makeC ( 1 . 0 ) ;# : ˜

[reescribir esta seccion utilizando la investigacion del libro de Lar-man]

Ejercicios

1. Crear una clase adaptador que carga automaticamente una matrizbidimensional de objetos en un diccionario como pares clave-valor.

107

Page 118: Traducción Thinking in Python

8: Codigo de la tabla dirigido:flexibilidad de configuracion

Codigo de la tabla dirigido por el uso de clases internasanonimas

Vease el ejemplo ListPerformance en TIJ del Capıtulo 9.

Tambien GreenHouse.py

108

Page 119: Traducción Thinking in Python

10: Devoluciones de llamados

Desacoplamiento en el comportamiento del codigo.

Observer y una categorıa de retorno de llamados denominada“Despacho multiple (no en Design Patterns )” incluyendo el Visitorde Design Patterns.

Observer : Observador

Al igual que las otras formas de devolucion de llamada, este con-tiene un punto de gancho donde se puede cambiar el codigo. Ladiferencia es de naturaleza completamente dinamica del observador.A menudo se utiliza para el caso especıfico de los cambios basadosen el cambio de otro objeto de estado, pero es tambien la base dela gestion de eventos. Cada vez que desee desacoplar la fuente dela llamada desde el codigo de llamada de forma totalmente dinamica.

El patron observador resuelve un problema bastante comun: ¿Quepasa si un grupo de objetos necesita actualizar a sı mismos cuandoalgun objeto cambia de estado? Esto se puede ver en el ”modelo-vista” aspecto de MVC (modelo-vista-controlador) de Smalltalk, oel ”Documento - Ver Arquitectura” casi equivalente. Supongamosque usted tiene algunos datos (el ”documento”) y mas de una vista,decir una parcela y una vista textual. Al cambiar los datos, losdos puntos de vista deben saber actualizarse a sı mismos,y eso es loque facilita el observador. Es un problema bastante comun que susolucion se ha hecho una parte de la libreria estandar java.util.

Hay dos tipos de objetos que se utilizan para implementar elpatron de observador en Python. La clase Observable lleva unregistro de todos los que quieran ser informados cuando un cambioocurre, si el “Estado” ha cambiado o no. Cuando alguien dice “estabien, todo el mundo debe revisar y, potencialmente, actualizarse,”La clase Observable realiza esta tarea mediante una llamada almetodo notifyObservers( ) para cada uno en la lista. El metodonotifyObservers( ) es parte de la clase base Observable.

109

Page 120: Traducción Thinking in Python

En realidad, hay dos “cosas que cambian” en el patron obser-vador: la cantidad de objetos observables y el modo de ocurrenciade una actualizacion. Es decir, el patron observador permite modi-ficar ambos sin afectar el codigo subrogado.

Observer es una clase “interfaz” que solo tiene una funcionmiembro, update( ). Esta funcion es llamada por el objeto queesta siendo observado, cuando ese objeto decide que es hora de actu-alizar todos sus observadores. Los argumentos son opcionales; ustedpodrıa tener un update( ) sin argumentos y eso todavıa encajarıaen el patron observador; sin embargo, esto es mas general — per-mite al objeto observado pasar el objeto que causo la actualizacion(ya que un Observer puede ser registrado con mas de un objetoobservado) y cualquier informacion adicional si eso es util, en lugarde forzar el objeto Observer que busca alrededor para ver quienesta actualizando y para ir a buscar cualquier otra informacion quenecesita.

El ”objeto observado” que decide cuando y como hacer la actu-alizacion sera llamado Observable.

Observable tiene una bandera para indicar si se ha cambiado.En un diseno mas simple, no habrıa ninguna bandera; si algo paso,cada uno serıa notificado. La bandera le permite esperar, y solonotificar al Observers cuando usted decide sea el momento ade-cuado. Notese, sin embargo, que el control del estado de la banderaes protected, de modo que solo un heredero puede decidir lo queconstituye un cambio, y no el usuario final de la clase Observerderivada resultante.

La mayor parte del trabajo se hace en notifyObservers( ). Sila bandera changed no se ha establecido, esto no hace nada. Deotra manera, se limpia la bandera changed y luego se repiten lasllamadas a notifyObservers( ) para no perder el tiempo. Esto sehace antes de notificar a los observadores en el caso de las llamadasa update( ). Hacer cualquier cosa que causa un cambio de nuevoa este objeto Observable. Entonces se mueve a traves del set yvuelve a llamar a la funcion miembro update( ) de cada Observer.

110

Page 121: Traducción Thinking in Python

Al principio puede parecer que usted puede utilizar un objetoordinario Observable para administrar las actualizaciones. Peroesto no funciona; para obtener un efecto, usted debe heredar deObservable y en algun lugar en el codigo de la clase derivada lla-mar setChanged( ). Esta es la funcion miembro que establece labandera “changed”, lo que significa que cuando se llama notify-Observers( ) todos los observadores, de hecho, seran notificados.Cuando usted llama setChanged( ) depende de la logica de suprograma.

111

Page 122: Traducción Thinking in Python

Observando Flores

Dado que Python no tiene componentes de la librerıa estandar paraapoyar el patron observador (como hace Java), primero tenemos quecrear una. Lo mas sencillo de hacer es traducir la librerıa estandarde Java Observer y la clase Observable. Esto tambien propor-ciona la traduccion mas facil a partir de codigo Java que utiliza estaslibrerıas.

Al tratar de hacer esto, nos encontramos con un obstaculo menor,que es el hecho de que Java tiene una palabra clave synchronizedque proporciona soporte integrado para la sincronizacion hilo. Cier-tamente se podrıa lograr lo mismo a mano utilizando codigo comoel siguiente:

import thread ingc l a s s ToSynch :

de f i n i t ( s e l f ) :s e l f . mutex = thread ing . RLock ( )s e l f . va l = 1

de f aSynchronizedMethod ( s e l f ) :s e l f . mutex . acqu i r e ( )t ry :

s e l f . va l += 1return s e l f . va l

f i n a l l y :s e l f . mutex . r e l e a s e ( )

Pero esto se convierte rapidamente tedioso de escribir y de leer.Peter Norvig me proporciono una solucion mucho mas agradable:

#: u t i l : Synchron izat ion . py’ ’ ’ Simple emulat ion o f Java ’ s ’ synchronized ’keyword , from Peter Norvig . ’ ’ ’import thread ing

de f synchron ized ( method ) :de f f (∗ args ) :

s e l f = args [ 0 ]s e l f . mutex . acqu i r e ( ) ;

112

Page 123: Traducción Thinking in Python

# pr in t method . name , ’ acquired ’t ry :

r e turn apply ( method , args )f i n a l l y :

s e l f . mutex . r e l e a s e ( ) ;# pr in t method . name , ’ r e l e a s ed ’

r e turn f

de f synchron ize ( k la s s , names=None ) :””” Synchronize methods in the g iven c l a s s .Only synchron ize the methods whose names aregiven , or a l l methods i f names=None .”””i f type ( names)==type ( ’ ’ ) : names = names . s p l i t ( )f o r (name , va l ) in k l a s s . d i c t . i tems ( ) :

i f c a l l a b l e ( va l ) and name != ’ i n i t ’ and \( names == None or name in names ) :

# pr in t ” synchron i z ing ” , namek l a s s . d i c t [ name ] = synchron ized ( va l )

# You can c r e a t e your own s e l f . mutex , or i n h e r i t# from t h i s c l a s s :c l a s s Synchron izat ion :

de f i n i t ( s e l f ) :s e l f . mutex = thread ing . RLock ( )

#:˜

La funcion synchronized( ) toma un metodo y lo envuelve enuna funcion que anade la funcionalidad mutex. El metodo es lla-mado dentro de esta funcion:

r e turn apply ( method , args )

y como la sentencia return pasa a traves de la clausula finally,el mutex es liberado.

Esto es de alguna manera el patron de diseno decorador, peromucho mas facil de crear y utilizar. Todo lo que tienes que decir es:

myMethod = synchron ized (myMethod)

113

Page 124: Traducción Thinking in Python

Para rodear su metodo con un mutex.

synchronized( ) es una funcion de conveniencia que aplica syn-chronized( ) para una clase entera, o bien todos los metodos de laclase (por defecto) o metodos seleccionados que son nombrados enun string : cadena como segundo argumento.

Finalmente, para synchronized( ) funcione debe haber un self.mutexcreado en cada clase que utiliza synchronized( ). Este puede sercreado a mano por el autor de clases, pero es mas consistente parautilizar la herencia, por tanto la clase base Synchronization esproporcionada.

He aquı una prueba sencilla del modulo de Synchronization.

#: u t i l : TestSynchron izat ion . pyfrom Synchron izat ion import ∗

# To use f o r a method :c l a s s C( Synchron izat ion ) :

de f i n i t ( s e l f ) :Synchron izat ion . i n i t ( s e l f )s e l f . data = 1

de f m( s e l f ) :s e l f . data += 1return s e l f . data

m = synchron ized (m)de f f ( s e l f ) : r e turn 47de f g ( s e l f ) : r e turn ’ spam ’

# So m i s synchronized , f and g are not .c = C( )

# On the c l a s s l e v e l :c l a s s D(C) :

de f i n i t ( s e l f ) :C. i n i t ( s e l f )

# You must o v e r r i d e an un−synchron ized method# in order to synchron ize i t ( j u s t l i k e Java ) :

114

Page 125: Traducción Thinking in Python

de f f ( s e l f ) : C. f ( s e l f )

# Synchronize every ( de f ined ) method in the c l a s s :synchron i ze (D)d = D( )d . f ( ) # Synchronizedd . g ( ) # Not synchron izedd .m( ) # Synchronized ( in the base c l a s s )

c l a s s E(C) :de f i n i t ( s e l f ) :

C. i n i t ( s e l f )de f m( s e l f ) : C.m( s e l f )de f g ( s e l f ) : C. g ( s e l f )de f f ( s e l f ) : C. f ( s e l f )

# Only synchron i z e s m and g . Note that m ends up# being doubly−wrapped in synchron izat ion , which# doesn ’ t hurt anything but i s i n e f f i c i e n t :synchron i ze (E, ’m g ’ )e = E( )e . f ( )e . g ( )e .m( )#:˜

Usted debe llamar al constructor de la clase base para Synchro-nization, pero esto es todo. En la clase C puede ver el uso deSynchronized() para m, dejando f y g solos. Clase D tiene todossus metodos sincronizados en masa, y la clase E utiliza la funcionde conveniencia para sincronizar m y g. Tenga en cuenta que dadoque m termina siendo sincronizado en dos ocasiones, este entro ysalio dos veces para cada llamada, que no es muy deseable [puedehaber una correccion para este].

#: u t i l : Observer . py# Class support f o r ” obse rve r ” pattern .from Synchron izat ion import ∗

c l a s s Observer :de f update ( observable , arg ) :

115

Page 126: Traducción Thinking in Python

’ ’ ’ Ca l l ed when the observed ob j e c t i smodi f i ed . You c a l l an Observable object ’ sno t i f yObse rve r s method to n o t i f y a l l theobject ’ s ob s e rve r s o f the change . ’ ’ ’pass

c l a s s Observable ( Synchron izat ion ) :de f i n i t ( s e l f ) :

s e l f . obs = [ ]s e l f . changed = 0Synchron izat ion . i n i t ( s e l f )

de f addObserver ( s e l f , obse rve r ) :i f obse rve r not in s e l f . obs :

s e l f . obs . append ( obse rve r )

de f de l e t eObse rve r ( s e l f , obse rve r ) :s e l f . obs . remove ( obse rve r )

de f no t i f yObse rve r s ( s e l f , arg = None ) :’ ’ ’ I f ’ changed ’ i n d i c a t e s that t h i s ob j e c thas changed , n o t i f y a l l i t s observer s , thenc a l l c learChanged ( ) . Each obse rve r has i t supdate ( ) c a l l e d with two arguments : t h i sobse rvab l e ob j e c t and the g e n e r i c ’ arg ’ . ’ ’ ’

s e l f . mutex . acqu i r e ( )t ry :

i f not s e l f . changed : r e turn# Make a l o c a l copy in case o f synchronous

# ad d i t i on s o f ob s e rve r s :l o ca lAr ray = s e l f . obs [ : ]s e l f . c learChanged ( )

f i n a l l y :s e l f . mutex . r e l e a s e ( )

# Updating i s not r equ i r ed to be synchron ized :f o r obse rve r in l oca lAr ray :

obse rve r . update ( s e l f , arg )

116

Page 127: Traducción Thinking in Python

de f de l e t eObse rve r s ( s e l f ) : s e l f . obs = [ ]de f setChanged ( s e l f ) : s e l f . changed = 1de f clearChanged ( s e l f ) : s e l f . changed = 0de f hasChanged ( s e l f ) : r e turn s e l f . changedde f countObservers ( s e l f ) : r e turn l en ( s e l f . obs )

synchron i ze ( Observable ,” addObserver de l e t eObse rve r de l e t eObse rve r s ” +”setChanged clearChanged hasChanged ” +” countObservers ”)

#:˜

Usando esta librerıa, aquı esta un ejemplo de el patron obser-vador:

#: c10 : ObservedFlower . py# Demonstration o f ” obse rve r ” pattern .import syssys . path += [ ’ . . / u t i l ’ ]from Observer import Observer , Observable

c l a s s Flower :de f i n i t ( s e l f ) :

s e l f . isOpen = 0s e l f . op en Not i f i e r = Flower . OpenNot i f i e r ( s e l f )s e l f . c l o s e N o t i f i e r= Flower . C l o s e N o t i f i e r ( s e l f )

de f open ( s e l f ) : # Opens i t s p e t a l ss e l f . isOpen = 1s e l f . op en Not i f i e r . no t i f yObse rve r s ( )s e l f . c l o s e N o t i f i e r . open ( )

de f c l o s e ( s e l f ) : # Closes i t s p e t a l ss e l f . isOpen = 0s e l f . c l o s e N o t i f i e r . no t i f yObse rve r s ( )s e l f . op en Not i f i e r . c l o s e ( )

de f c l o s i n g ( s e l f ) : r e turn s e l f . c l o s e N o t i f i e rc l a s s OpenNot i f i e r ( Observable ) :de f i n i t ( s e l f , outer ) :

Observable . i n i t ( s e l f )s e l f . outer = outers e l f . alreadyOpen = 0

117

Page 128: Traducción Thinking in Python

de f no t i f yObse rve r s ( s e l f ) :i f s e l f . outer . isOpen and \not s e l f . alreadyOpen :

s e l f . setChanged ( )Observable . no t i f yObse rve r s ( s e l f )s e l f . alreadyOpen = 1

de f c l o s e ( s e l f ) :s e l f . alreadyOpen = 0

c l a s s C l o s e N o t i f i e r ( Observable ) :de f i n i t ( s e l f , outer ) :

Observable . i n i t ( s e l f )s e l f . outer = outers e l f . a l r eadyClosed = 0

de f no t i f yObse rve r s ( s e l f ) :i f not s e l f . outer . isOpen and \not s e l f . a l r eadyClosed :

s e l f . setChanged ( )Observable . no t i f yObse rve r s ( s e l f )s e l f . a l r eadyClosed = 1

de f open ( s e l f ) :a l r eadyClosed = 0

c l a s s Bee :de f i n i t ( s e l f , name ) :

s e l f . name = names e l f . openObserver = Bee . OpenObserver ( s e l f )s e l f . c l o s eObse rve r = Bee . CloseObserver ( s e l f )

# An inner c l a s s f o r obse rv ing openings :c l a s s OpenObserver ( Observer ) :

de f i n i t ( s e l f , outer ) :s e l f . outer = outer

de f update ( s e l f , observable , arg ) :p r i n t ”Bee ” + s e l f . outer . name + \

” ’ s b r e ak f a s t time ! ”# Another inne r c l a s s f o r c l o s i n g s :c l a s s CloseObserver ( Observer ) :

de f i n i t ( s e l f , outer ) :

118

Page 129: Traducción Thinking in Python

s e l f . outer = outer

de f update ( s e l f , observable , arg ) :p r i n t ”Bee ” + s e l f . outer . name + \

” ’ s bed time ! ”

c l a s s Hummingbird :de f i n i t ( s e l f , name ) :

s e l f . name = names e l f . openObserver = \

Hummingbird . OpenObserver ( s e l f )s e l f . c l o s eObse rve r = \

Hummingbird . CloseObserver ( s e l f )c l a s s OpenObserver ( Observer ) :

de f i n i t ( s e l f , outer ) :s e l f . outer = outer

de f update ( s e l f , observable , arg ) :p r i n t ”Hummingbird ” + s e l f . outer . name + \

” ’ s b r e ak f a s t time ! ”c l a s s CloseObserver ( Observer ) :

de f i n i t ( s e l f , outer ) :s e l f . outer = outer

de f update ( s e l f , observable , arg ) :p r i n t ”Hummingbird ” + s e l f . outer . name + \

” ’ s bed time ! ”

f = Flower ( )ba = Bee (” Er ic ”)bb = Bee (” Er ic 0 . 5” )ha = Hummingbird (”A”)hb = Hummingbird (”B”)f . op en No t i f i e r . addObserver ( ha . openObserver )f . op en No t i f i e r . addObserver (hb . openObserver )f . op en No t i f i e r . addObserver ( ba . openObserver )f . op en No t i f i e r . addObserver (bb . openObserver )f . c l o s e N o t i f i e r . addObserver ( ha . c l o s eObse rve r )f . c l o s e N o t i f i e r . addObserver (hb . c l o s eObse rve r )f . c l o s e N o t i f i e r . addObserver ( ba . c l o s eObse rve r )f . c l o s e N o t i f i e r . addObserver (bb . c l o s eObse rve r )

119

Page 130: Traducción Thinking in Python

# Hummingbird 2 dec ide s to s l e e p in :f . op en No t i f i e r . de l e t eObse rve r (hb . openObserver )# A change that i n t e r e s t s ob s e rve r s :f . open ( )f . open ( ) # It ’ s a l r eady open , no change .# Bee 1 doesn ’ t want to go to bed :f . c l o s e N o t i f i e r . de l e t eObse rve r ( ba . c l o s eObse rve r ) f . c l o s e ( )f . c l o s e ( ) # It ’ s a l r eady c l o s e d ; no changef . op en No t i f i e r . de l e t eObse rve r s ( )f . open ( )f . c l o s e ( )#:˜

Los acontecimientos de interes incluyen que una Flower puedeabrir o cerrar. Debido al uso del idioma de la clase interna, estosdos eventos pueden ser fenomenos observables por separado. Open-Notifier y CloseNotifier ambos heredan de Observable, ası quetienen acceso a setChanged( ) y pueden ser entregados a todo loque necesita un Observable.

El lenguaje de la clase interna tambien es muy util para definirmas de un tipo de Observer, en Bee y Hummingbird, ya quetanto las clases pueden querer observar independientemente aber-turas Flower y cierres. Observe como el lenguaje de la clase internaofrece algo que tiene la mayor parte de los beneficios de la herencia(la capacidad de acceder a los datos de private en la clase externa,por ejemplo) sin las mismas restricciones.

En main( ), se puede ver uno de los beneficios principales de delpatron observador: la capacidad de cambiar el comportamiento entiempo de ejecucion mediante el registro de forma dinamica y anularel registro Observers con Observables.

Si usted estudia el codigo de arriba veras que OpenNotifier yCloseNotifier utiliza la interfaz Observable basica. Esto significaque usted podrıa heredar otras clases Observable completamentediferentes; la unica conexion de Observable que tiene con Flower,es la interfaz Observable.

120

Page 131: Traducción Thinking in Python

Un ejemplo visual de Observers

El siguiente ejemplo es similar al ejemplo ColorBoxes en el Capıtulo14 del libro Thinking in Java, 2nd Edicion. Las cajas se colocan enuna cuadrıcula en la pantalla y cada uno se inicializa a un coloraleatorio. En adicion, cada caja implements de la interfaz Ob-server y es registrada con un objeto Observable.Al hacer clic enuna caja, todas las otras cajas son notificados de que un cambiose ha hecho porque el objeto Observable llama automaticamenteal metodo update( ) de cada objeto Observer. Dentro de estemetodo, las comprobaciones de caja muestran si es adyacente a laque se ha hecho clic, y si es ası, cambia de color para que coincidacon dicha caja.22

# c10 : BoxObserver . py# Demonstration o f Observer pattern us ing# Java ’ s bu i l t−in obse rve r c l a s s e s .

# You must i n h e r i t a type o f Observable :c l a s s BoxObservable ( Observable ) :

de f no t i f yObse rve r s ( s e l f , Object b ) :# Otherwise i t won ’ t propagate changes :setChanged ( )super . no t i f yObse rve r s (b)

c l a s s BoxObserver ( JFrame ) :Observable n o t i f i e r = BoxObservable ( )de f i n i t ( s e l f , i n t g r i d ) :

s e t T i t l e (” Demonstrates Observer pattern ”)Container cp = getContentPane ( )cp . setLayout ( GridLayout ( gr id , g r i d ) )f o r ( i n t x = 0 x < g r id x++)

f o r ( i n t y = 0 y < g r id y++)cp . add (OCBox(x , y , n o t i f i e r ) )

de f main ( s e l f , S t r ing [ ] a rgs ) :i n t g r id = 8

22[este ejemplo no se ha convertido. Vease mas adelante una version que tiene la interfazgrafica de usuario, pero no los observadores, en PythonCard.]

121

Page 132: Traducción Thinking in Python

i f ( a rgs . l ength > 0)g r id = I n t eg e r . pa r s e In t ( args [ 0 ] )

JFrame f = BoxObserver ( g r id )f . s e t S i z e (500 , 400)f . s e t V i s i b l e (1 )# JDK 1 . 3 :f . s e tDe fau l tC lo seOperat i on (EXIT ON CLOSE)# Add a WindowAdapter i f you have JDK 1.2

c l a s s OCBox( JPanel ) implements Observer :Observable n o t i f i e ri n t x , y # Locat ions in g r idColor cColor = newColor ( )s t a t i c f i n a l Color [ ] c o l o r s =:

Color . black , Color . blue , Color . cyan ,Color . darkGray , Color . gray , Color . green ,Color . l ightGray , Color . magenta ,Color . orange , Color . pink , Color . red ,Color . white , Color . ye l low

s t a t i c f i n a l Color newColor ( ) :r e turn c o l o r s [

( i n t ) ( Math . random ( ) ∗ c o l o r s . l ength )]

de f i n i t ( s e l f , i n t x , i n t y , Observablen o t i f i e r ) :

s e l f . x = xs e l f . y = yn o t i f i e r . addObserver ( s e l f )s e l f . n o t i f i e r = n o t i f i e raddMouseListener (ML( ) )

de f paintComponent ( s e l f , Graphics g ) :super . paintComponent ( g )g . s e tCo lo r ( cColor )Dimension s = g e t S i z e ( )g . f i l l R e c t (0 , 0 , s . width , s . he ight )

122

Page 133: Traducción Thinking in Python

c l a s s ML( MouseAdapter ) :de f mousePressed ( s e l f , MouseEvent e ) :

n o t i f i e r . no t i f yObse rve r s (OCBox. s e l f )

de f update ( s e l f , Observable o , Object arg ) :OCBox c l i c k e d = (OCBox) argi f ( nextTo ( c l i c k e d ) ) :

cColor = c l i c k e d . cColorr epa in t ( )

p r i v a t e f i n a l boolean nextTo (OCBox b ) :r e turn Math . abs ( x − b . x ) <= 1 &&

Math . abs ( y − b . y ) <= 1# : ˜

Cuando usted ve por primera vez la documentacion en lınea paraObservable, es un poco confuso, ya que parece que se puede utilizarun objeto ordinario Observable para manejar las actualizaciones.Pero esto no funciona; intentalo dentro de BoxObserver, crea unobjeto Observable en lugar de un objeto BoxObserver y observelo que ocurre: nada. Para conseguir un efecto, debe heredar deObservable y en alguna parte de su codigo la clase derivada lla-mada setChanged( ). Este es el metodo que establece el cambio debandera , lo que significa que cuando se llama notifyObservers( )todos los observadores, de hecho, seran notificados. En el ejemploanterior, setChanged( ) es simplemente llamado dentro de noti-fyObservers( ), pero podrıa utilizar cualquier criterio que deseepara decidir cuando llamar setChanged( ).

BoxObserver contiene un solo objeto Observable llamado no-tifier, y cada vez que se crea un objeto OCBox, esta vinculada anotifier. En OCBox al hacer clic con el mouse, el metodo noti-fyObservers( ) es llamado, pasando el objeto seleccionado comoun argumento para que todas las cajas reciban el mensaje (en sumetodo update( ) ) y sabiendo quien fue seleccionado decidan elcambio. Usando una combinacion de codigo en notifyObservers() y update( ) se puede trabajar algunos esquemas bastante com-plejos.

123

Page 134: Traducción Thinking in Python

Podrıa parecer que la forma en que los observadores son notifi-cados debe ser congelada en tiempo de compilacion en el metodonotifyObservers( ). Ahora bien, si se mira mas de cerca el codigoanterior usted vera que el unico lugar en BoxObserver o OCBox,cuando es consciente de que usted esta trabajando con una BoxOb-servable, se esta en el punto de la creacion del objeto Observable— de ahı en adelante todo lo que utiliza la interfaz basica Observ-able. Esto significa que usted podrıa heredar otras clases Observ-able e intercambiarlas en tiempo de ejecucion si desea cambiar elcomportamiento de notificacion luego.

Aquı esta una version de lo anterior que no utiliza el patron Ob-servador23, y colocado aquı como un punto de partida para unatraduccion que sı incluye Observador:

#: c10 : BoxObserver . py””” Written by Kevin A l t i s as a f i r s t −cut f o rconver t ing BoxObserver to Python . The Observerhasn ’ t been i n t e g r a t e d yet .To run t h i s program , you must :I n s t a l l WxPython fromhttp ://www. wxpython . org /download . phpI n s t a l l PythonCard . See :http :// pythoncard . s o u r c e f o r g e . net”””

from PythonCardPrototype import log , modelimport random

GRID = 8

c l a s s ColorBoxesTest ( model . Background ) :de f on openBackground ( s e l f , ta rget , event ) :

s e l f . document = [ ]f o r row in range (GRID) :

l i n e = [ ]f o r column in range (GRID) :

l i n e . append ( s e l f . createBox ( row , column ) )

23escrito por Kevin Altis usando PythonCard

124

Page 135: Traducción Thinking in Python

s e l f . document . append ( l i n e [ : ] )de f createBox ( s e l f , row , column ) :

c o l o r s = [ ’ black ’ , ’ blue ’ , ’ cyan ’ ,’ darkGray ’ , ’ gray ’ , ’ green ’ ,’ l ightGray ’ , ’ magenta ’ ,’ orange ’ , ’ pink ’ , ’ red ’ ,’ white ’ , ’ ye l low ’ ]width , he ight = s e l f . panel . GetSizeTuple ( )boxWidth = width / GRIDboxHeight = he ight / GRIDlog . i n f o (” width : ” + s t r ( width ) +

” he ight : ” + s t r ( he ight ) )l og . i n f o (” boxWidth : ” + s t r ( boxWidth ) +

” boxHeight : ” + s t r ( boxHeight ) )# use an empty image , though some other# widgets would work j u s t as we l lboxDesc = ’ type ’ : ’ Image ’ ,

’ s i z e ’ : ( boxWidth , boxHeight ) , ’ f i l e ’ : ’ ’ name = ’ box−\%d−%d ’ % ( row , column )# There i s probably a 1 o f f e r r o r in the# c a l c u l a t i o n below s i n c e the boxes should# probably have a s l i g h t l y d i f f e r e n t o f f s e t# to prevent ove r l ap sboxDesc [ ’ po s i t i on ’ ] =

( column ∗ boxWidth , row ∗ boxHeight )boxDesc [ ’ name ’ ] = nameboxDesc [ ’ backgroundColor ’ ] =

random . cho i c e ( c o l o r s )s e l f . components [ name ] = boxDescre turn s e l f . components [ name ]

de f changeNeighbors ( s e l f , row , column , c o l o r ) :

# This a lgor i thm w i l l r e s u l t in changing the# c o l o r o f some boxes more than once , so an# OOP s o l u t i o n where only ne ighbors are asked# to change or boxes check to see i f they are# ne ighbors be f o r e changing would be b e t t e r# per the o r i g i n a l example does the whole g r id

125

Page 136: Traducción Thinking in Python

# need to change i t s s t a t e at once l i k e in a# L i f e program? should the c o l o r change# in the propogat ion o f another n o t i f i c a t i o n# event ?

f o r r in range (max(0 , row − 1) ,min (GRID, row + 2 ) ) :

f o r c in range (max(0 , column − 1) ,min (GRID, column + 2 ) ) :

s e l f . document [ r ] [ c ] . backgroundColor=c o l o r

# t h i s i s a background handler , so i t i sn ’ t# s p e c i f i c to a s i n g l e widget . Image widgets# don ’ t have a mouseClick event (wxCommandEvent# in wxPython )de f on mouseUp ( s e l f , ta rget , event ) :

p r e f i x , row , column = t a r g e t . name . s p l i t ( ’− ’)s e l f . changeNeighbors ( i n t ( row ) , i n t ( column ) ,

t a r g e t . backgroundColor )

i f name == ’ main ’ :app = model . PythonCardApp ( ColorBoxesTest )app . MainLoop ( )

#:˜

Este es el archivo de recursos para ejecutar el programa (verPythonCard para mas detalles):

#: c10 : BoxObserver . r s r c . py ’ stack ’ : ’ type ’ : ’ Stack ’ ,

’name ’ : ’ BoxObserver ’ ,’ backgrounds ’ : [ ’ type ’ : ’ Background ’ ,

’name ’ : ’ bgBoxObserver ’ ,’ t i t l e ’ : ’ Demonstrates Observer pattern ’ ,’ po s i t i on ’ : ( 5 , 5 ) ,’ s i z e ’ : ( 5 0 0 , 400) ,’ components ’ : [

] # end components

126

Page 137: Traducción Thinking in Python

# end background] # end backgrounds #:˜

Ejercicios

1. Utilizando el enfoque en Synchronization.py, crear una her-ramienta que ajuste automaticamente todos los metodos en unaclase para proporcionar una rastreo de ejecucion, de manera quese puede ver el nombre del metodo y cuando entro y salio.

2. Crear un diseno minimalista Observador-observable en dosclases. Basta con crear el mınimo en las dos clases, luego demostrarsu diseno mediante la creacion de un Observable y muchos Ob-servers, y hacer que el Observable para actualizar los Observers.

3. Modifica BoxObserver.py para convertirlo en un juego sen-cillo. Si alguno de los cuadrados que rodean el que usted selecciono,es parte de un parche contiguo del mismo color, entonces todas loscuadros en ese parche se cambian al color que ha hecho clic. Puedeconfigurar el juego para la competencia entre los jugadores o parano perder de vista el numero de clics que un solo jugador utilizapara convertir el campo en un solo color. Tambien puede restringirel color de un jugador a la primera que haya elegido.

127

Page 138: Traducción Thinking in Python

11 : Despacho Multiple

Cuando se trata de multiples tipos que estan interactuando, unprograma puede tener desordenes particulares. Por ejemplo, con-sidere un sistema que analiza y ejecuta expresiones matematicas.Usted requiere ser capaz de decir Number + Number, Number* Number, etc., donde Number es la clase base para una familiade objetos numericos. Pero cuando usted dice a + b, y no conoceel tipo exacto de alguno de ellos, ası que ¿como se puede conseguirque interactuen correctamente?

La respuesta comienza con algo que probablemente no piensas:Python solo realiza despacho individual. Es decir, si esta realizandouna operacion en mas de un objeto cuyo tipo es desconocido, Pythonpuede invocar el mecanismo de enlace dinamico a uno solo de esostipos. Esto no resuelve el problema, por lo que termina detectandoalgunos tipos manualmente y produciendo con eficacia su propiocomportamiento de enlace dinamico.

La solucion es llamada despacho multiple (multiple dispatching).Recuerde que el polimorfismo puede ocurrir solo a traves de llamadasa funciones miembro, ası que si quiere que haya un doble envıo, debehaber dos llamadas a la funcion miembro: la primera para determi-nar el primer elemento desconocido, y la segunda para determinar elsegundo elemento desconocido. Con despacho multiple, usted debetener una llamada a un metodo polimorfico para determinar cadauno de los tipos. Generalmente, va a gestionar una configuracion talque una sola llamada de funcion miembro produce mas de una lla-mada dinamica a la funcion miembro y por lo tanto determina masde un tipo en el proceso. Para obtener este efecto, usted necesitatrabajar con mas de una llamada a un metodo polimorfico: ustednecesitara una llamada para cada despacho. Los metodos en el sigu-iente ejemplo se llaman compete( ) y eval( ), y son ambos miem-bros del mismo tipo. (En este caso habra solo dos despachos, quese conocen como doble despacho). Si esta trabajando con dos jer-arquıas de tipos diferentes que estan interactuando, entonces ustedhabra de tener una llamada a un metodo polimorfico en cada jer-arquıa.

128

Page 139: Traducción Thinking in Python

Aquı esta un ejemplo de despacho multiple:

#: c11 : PaperSc i ssorsRock . py# Demonstration o f mu l t ip l e d i spa t ch ing .from f u t u r e import gene ra to r simport random

# An enumeration type :c l a s s Outcome :

de f i n i t ( s e l f , value , name ) :s e l f . va lue = values e l f . name = name

de f s t r ( s e l f ) : r e turn s e l f . namede f e q ( s e l f , o ther ) :

r e turn s e l f . va lue == other . va lue

Outcome .WIN = Outcome (0 , ”win ”)Outcome .LOSE = Outcome (1 , ” l o s e ”)Outcome .DRAW = Outcome (2 , ”draw ”)

c l a s s Item ( ob j e c t ) :de f s t r ( s e l f ) :

r e turn s e l f . c l a s s . name

c l a s s Paper ( Item ) :de f compete ( s e l f , item ) :

# F i r s t d i spatch : s e l f was Paperre turn item . evalPaper ( s e l f )

de f evalPaper ( s e l f , item ) :# Item was Paper , we ’ re in Paperre turn Outcome .DRAW

def e v a l S c i s s o r s ( s e l f , item ) :# Item was S c i s s o r s , we ’ re in Paperre turn Outcome .WIN

def evalRock ( s e l f , item ) :# Item was Rock , we ’ re in Paperre turn Outcome .LOSE

c l a s s S c i s s o r s ( Item ) :

129

Page 140: Traducción Thinking in Python

de f compete ( s e l f , item ) :# F i r s t d i spatch : s e l f was S c i s s o r sre turn item . e v a l S c i s s o r s ( s e l f )

de f evalPaper ( s e l f , item ) :# Item was Paper , we ’ re in S c i s s o r sre turn Outcome .LOSE

def e v a l S c i s s o r s ( s e l f , item ) :# Item was S c i s s o r s , we ’ re in S c i s s o r sre turn Outcome .DRAW

def evalRock ( s e l f , item ) :# Item was Rock , we ’ re in S c i s s o r sre turn Outcome .WIN

c l a s s Rock ( Item ) :de f compete ( s e l f , item ) :

# F i r s t d i spatch : s e l f was Rockre turn item . evalRock ( s e l f )

de f evalPaper ( s e l f , item ) :# Item was Paper , we ’ re in Rockre turn Outcome .WIN

def e v a l S c i s s o r s ( s e l f , item ) :# Item was S c i s s o r s , we ’ re in Rockre turn Outcome .LOSE

def evalRock ( s e l f , item ) :# Item was Rock , we ’ re in Rockre turn Outcome .DRAW

def match ( item1 , item2 ) :p r i n t ”\%s <−−> \%s : \%s ” \% (

item1 , item2 , item1 . compete ( item2 ) )# Generate the items :de f itemPairGen (n ) :

# Create a l i s t o f i n s t a n c e s o f a l l Items :Items = Item . s u b c l a s s e s ( )f o r i in range (n ) :

y i e l d ( random . cho i c e ( Items ) ( ) ,random . cho i c e ( Items ) ( ) )

f o r item1 , item2 in itemPairGen ( 2 0 ) :match ( item1 , item2 )

130

Page 141: Traducción Thinking in Python

#:˜

Esta fue una traduccion bastante literal de la version de Java, yuna de las cosas que usted puede notar es que la informacion sobrelas distintas combinaciones se codifica en cada tipo de Item. En re-alidad, termina siendo una especie de tabla excepto que se extiendea traves de todas las clases. Esto no es muy facil de mantener sialguna vez espera modificar el comportamiento o para anadir unanueva clase Item. En su lugar, puede ser mas sensible a hacer latabla explıcita, ası:

#: c11 : PaperSc issorsRock2 . py# Mult ip l e d i spa t ch ing us ing a tab l efrom f u t u r e import gene ra to r simport random

c l a s s Outcome :de f i n i t ( s e l f , value , name ) :

s e l f . va lue = values e l f . name = name

de f s t r ( s e l f ) : r e turn s e l f . namede f e q ( s e l f , o ther ) :

r e turn s e l f . va lue == other . va lue

Outcome .WIN = Outcome (0 , ”win ”)Outcome .LOSE = Outcome (1 , ” l o s e ”)Outcome .DRAW = Outcome (2 , ”draw ”)

c l a s s Item ( ob j e c t ) :de f compete ( s e l f , item ) :

# Use a tup l e f o r t ab l e lookup :re turn outcome [ s e l f . c l a s s , item . c l a s s ]

de f s t r ( s e l f ) :r e turn s e l f . c l a s s . name

c l a s s Paper ( Item ) : passc l a s s S c i s s o r s ( Item ) : passc l a s s Rock ( Item ) : passoutcome =

( Paper , Rock ) : Outcome .WIN,( Paper , S c i s s o r s ) : Outcome .LOSE,

131

Page 142: Traducción Thinking in Python

( Paper , Paper ) : Outcome .DRAW,( S c i s s o r s , Paper ) : Outcome .WIN,( S c i s s o r s , Rock ) : Outcome .LOSE,( S c i s s o r s , S c i s s o r s ) : Outcome .DRAW,(Rock , S c i s s o r s ) : Outcome .WIN,(Rock , Paper ) : Outcome .LOSE,(Rock , Rock ) : Outcome .DRAW,

de f match ( item1 , item2 ) :

p r i n t ”\%s <−−> \%s : \%s ” % (item1 , item2 , item1 . compete ( item2 ) )

# Generate the items :de f itemPairGen (n ) :

# Create a l i s t o f i n s t a n c e s o f a l l Items :Items = Item . s u b c l a s s e s ( )f o r i in range (n ) :

y i e l d ( random . cho i c e ( Items ) ( ) ,random . cho i c e ( Items ) ( ) )

f o r item1 , item2 in itemPairGen ( 2 0 ) :match ( item1 , item2 )

#:˜

Es un tributo a la flexibilidad de los diccionarios que una tupla sepuede utilizar como una clave tan facilmente como un solo objeto.

132

Page 143: Traducción Thinking in Python

Visitor, un tipo de despacho multiple

La suposicion es que usted tiene una jerarquıa primaria de clasesque es fija; tal vez es de otro proveedor y no puede hacer cambios enesa jerarquıa. Sin embargo, usted tiene como anadir nuevos metodospolimorficos a esa jerarquıa, lo que significa que normalmente habraque anadir algo a la interfaz de la clase base. Ası que el dilema esque usted necesita agregar metodos a la clase base, pero no se puedetocar la clase base. ¿Como se obtiene esto?.

El patron de diseno que resuelve este tipo de problemas se llamaun ”visitor” (visitante) (el definitivo en el libro Design Patterns),y se basa en el esquema de despacho doble mostrado en la ultimaseccion.

El patron visitor le permite extender la interfaz del tipo primariomediante la creacion de una jerarquıa de clases por separado detipo Visitor para virtualizar las operaciones realizadas en el tipoprimario. Los objetos del tipo primario simplemente ”aceptan” alpatron visitor, a continuacion, llaman a la funcion miembro de vis-itor enlazado dinamicamente.

#: c11 : F l owe rV i s i t o r s . py# Demonstration o f ” v i s i t o r ” pattern .from f u t u r e import gene ra to r simport random

# The Flower h i e ra r chy cannot be changed :c l a s s Flower ( ob j e c t ) :

de f accept ( s e l f , v i s i t o r ) :v i s i t o r . v i s i t ( s e l f )

de f p o l l i n a t e ( s e l f , p o l l i n a t o r ) :p r i n t s e l f , ” p o l l i n a t e d by ” , p o l l i n a t o r

de f eat ( s e l f , e a t e r ) :p r i n t s e l f , ” eaten by ” , e a t e r

de f s t r ( s e l f ) :r e turn s e l f . c l a s s . name

c l a s s Glad io lus ( Flower ) : pass

133

Page 144: Traducción Thinking in Python

c l a s s Runuculus ( Flower ) : passc l a s s Chrysanthemum ( Flower ) : passc l a s s V i s i t o r :

de f s t r ( s e l f ) :r e turn s e l f . c l a s s . name

c l a s s Bug( V i s i t o r ) : passc l a s s P o l l i n a t o r (Bug ) : passc l a s s Predator (Bug ) : pass

# Add the a b i l i t y to do ”Bee” a c t i v i t i e s :c l a s s Bee ( P o l l i n a t o r ) :

de f v i s i t ( s e l f , f l owe r ) :f l owe r . p o l l i n a t e ( s e l f )

# Add the a b i l i t y to do ”Fly” a c t i v i t i e s :c l a s s Fly ( P o l l i n a t o r ) :

de f v i s i t ( s e l f , f l owe r ) :f l owe r . p o l l i n a t e ( s e l f )

# Add the a b i l i t y to do ”Worm” a c t i v i t i e s :c l a s s Worm( Predator ) :

de f v i s i t ( s e l f , f l owe r ) :f l owe r . eat ( s e l f )

de f f lowerGen (n ) :f l w r s = Flower . s u b c l a s s e s ( )f o r i in range (n ) :

y i e l d random . cho i c e ( f l w r s ) ( )

# It ’ s almost as i f I had a method to Perform# var i ous ”Bug” ope ra t i on s on a l l Flowers :bee = Bee ( )f l y = Fly ( )worm = Worm( )f o r f l owe r in flowerGen ( 1 0 ) :

f l owe r . accept ( bee )f l owe r . accept ( f l y )f l owe r . accept (worm)

#:˜

134

Page 145: Traducción Thinking in Python

Ejercicios

1. Crear un entorno empresarial de modelado con tres tipos de In-habitant : Dwarf (para Ingenieros), Elf (para los comerciantes)y Troll (para los administradores). Ahora cree una clase llamadaProject que crea los diferentes habitantes y les lleva a interact( )entre sı utilizando despacho multiple.

2. Modificar el ejemplo de arriba para hacer las interacciones masdetalladas. Cada Inhabitant puede producir al azar un Weaponusando getWeapon( ): un Dwarf usa Jargon o Play, un Elf usaInventFeature o SellImaginaryProduct, y un Troll usa Edicty Schedule. Usted debe decidir que armas ”ganan” y ”pierden” encada interaccion (como en PaperScissorsRock.py). Agregar unafuncion miembro battle( ) a Project que lleva dos Inhabitantsy coinciden unos contra los otros. Ahora cree una funcion miem-bro meeting( ) para Project que crea grupos de Dwarf, Elf yManager y batallas contra los grupos entre sı hasta que solo losmiembros de un grupo se quedan de pie. Estos son los ”ganadores”.

3. Modificar PaperScissorsRock.py para reemplazar el dobledespacho con una busqueda en la tabla. La forma mas sencilla dehacerlo es crear un Map de Maps, con la clave de cada Map yla clase de cada objeto. Entonces usted puede hacer la busquedadiciendo:

((Map)map.get(o1.getClass())).get(o2.getClass()).Observe lo facil que es volver a configurar el sistema. ¿Cuando estoes mas apropiado al utilizar este enfoque vs. la codificacion com-pleja de los despachos dinamicos? ¿Se puede crear un sistema quetenga la sencillez sintactica de uso del despacho dinamico, para queutilice una busqueda en la tabla?

4. Modificar Ejercicio 2 para utilizar la tecnica de tabla de con-sulta descrito en el Ejercicio 3.

135

Page 146: Traducción Thinking in Python

12 : Patron Refactorizacion

En este capıtulo se analizara el proceso de resolver un problema me-diante la aplicacion de patrones de diseno de una forma evolutiva.Es decir, un primer diseno de corte sera utilizado para la solucioninicial, y luego esta solucion sera examinada y diversos patrones dediseno se aplicaran al problema (algunos de los cuales funcionaran yotros no). La pregunta clave que siempre se pregunto en la busquedade soluciones mejoradas es “¿que va a cambiar?”

Este proceso es similar a lo que Martin Fowler habla en su libroRefactoring:Improving the Design of Existing Code24 (a pesar de quetiende a hablar de piezas de codigo mas de disenos a nivel de patron).Se comienza con una solucion, y luego cuando se descubre que noes continuo en la satisfaccion de sus necesidades, lo arregla. Porsupuesto, esta es una tendencia natural, pero en la programacioninformatica que ha sido muy difıcil de lograr con programas de pro-cedimiento, y la aceptacion de la idea de que podemos refactorizarcodigo y anadir diseno al cuerpo de prueba como la programacionorientada a objetos es ”una cosa buena.”

Simulando el reciclador de basura

La naturaleza de este problema es que la basura se lanza sin clasi-ficar en un solo compartimiento, por lo que la informacion de tipoespecıfico se pierde. Pero mas tarde, la informacion de tipo especıficodebe ser recuperada para ordenar adecuadamente la basura. En lasolucion inicial, RTTI(descrito en el capıtulo 12 de Thinking in Java,Segunda edicion) es utilizado.

Esto no es un diseno trivial, ya que tiene una restriccion anadida.Eso es lo que hace que sea interesante — se parece mas a los proble-mas desordenados que es probable que encuentre en su trabajo. Larestriccion adicional es que la basura llega a la planta de reciclajedel vertedero sanitario. El programa debe modelar la clasificacionde esa basura. Aquı es donde entra en juego RTTI : usted tiene unmonton de piezas anonimas de basura, y el programa se da cuenta

24Addison-Wesley, 1999.

136

Page 147: Traducción Thinking in Python

exactamente de que tipo son.

# c12 : r e c y c l e a : RecycleA . py# Recyc l ing with RTTI .

c l a s s Trash :p r i v a t e double weightde f i n i t ( s e l f , double wt ) : weight = wtabs t r a c t double getValue ( )double getWeight ( ) : r e turn weight# Sums the value o f Trash in a bin :s t a t i c void sumValue ( I t e r a t o r i t ) :

double va l = 0 .0 fwhi l e ( i t . hasNext ( ) ) :

# One kind o f RTTI :# A dynamical ly−checked ca s tTrash t = ( Trash ) i t . next ( )# Polymorphism in ac t i on :va l += t . getWeight ( ) ∗ t . getValue ( )p r i n t (

” weight o f ” +# Using RTTI to get type# in fo rmat ion about the c l a s s :t . g e tC la s s ( ) . getName ( ) +” = ” + t . getWeight ( ) )

p r i n t ” Total va lue = ” + val

c l a s s Aluminum( Trash ) :s t a t i c double va l = 1 .67 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )double getValue ( ) : r e turn va ls t a t i c void setValue ( double newval ) :

va l = newval

c l a s s Paper ( Trash ) :s t a t i c double va l = 0 .10 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )double getValue ( ) : r e turn va l

137

Page 148: Traducción Thinking in Python

s t a t i c void setValue ( double newval ) :va l = newval

c l a s s Glass ( Trash ) :s t a t i c double va l = 0 .23 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )double getValue ( ) : r e turn va ls t a t i c void setValue ( double newval ) :

va l = newval

c l a s s RecycleA ( UnitTest ) :C o l l e c t i o n

bin = ArrayList ( ) ,g l a s sB in = ArrayList ( ) ,paperBin = ArrayList ( ) ,a lBin = ArrayList ( )

de f i n i t ( s e l f ) :# F i l l up the Trash bin :f o r ( i n t i = 0 i < 30 i++)

switch ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :case 0 :

bin . add (newAluminum(Math . random ( ) ∗ 100))

breakcase 1 :

bin . add (newPaper (Math . random ( ) ∗ 100))

breakcase 2 :

bin . add (newGlass (Math . random ( ) ∗ 100))

de f t e s t ( s e l f ) :I t e r a t o r s o r t e r = bin . i t e r a t o r ( )# Sort the Trash :whi l e ( s o r t e r . hasNext ( ) ) :

Object t = s o r t e r . next ( )# RTTI to show c l a s s membership :i f ( t i n s t a n c e o f Aluminum)

138

Page 149: Traducción Thinking in Python

alBin . add ( t )i f ( t i n s t a n c e o f Paper )

paperBin . add ( t )i f ( t i n s t a n c e o f Glass )

g l a s sB in . add ( t )

Trash . sumValue ( a lBin . i t e r a t o r ( ) )Trash . sumValue ( paperBin . i t e r a t o r ( ) )Trash . sumValue ( g l a s sB in . i t e r a t o r ( ) )Trash . sumValue ( bin . i t e r a t o r ( ) )

de f main ( s e l f , S t r ing args [ ] ) :RecycleA ( ) . t e s t ( )

# : ˜

En los listados de codigo fuente disponibles para este libro, estearchivo se colocara en el subdirectorio recyclea que se ramificadesde el subdirectorio c12 (para el Capıtulo 12). La herramientade desembalaje se encarga de colocarlo en el subdirectorio correcto.La razon para hacer esto es que este capıtulo reescribe este ejemploparticular, un numero de veces y poniendo cada version en su propiodirectorio (utilizando el paquete por defecto en cada directorio paraque al ser invocado, el programa sea facil), los nombres de clase noentran en conflicto.

Varios objetos ArrayList se crean para mantener referenciasTrash. Claro, ArrayLists en realidad tendra Objects vacıos (nosostendran nada en absoluto). La razon por la que tienen Trash (oalgo derivado de Trash) es solo porque usted ha sido cuidadoso deno poner en nada, excepto Trash. Si usted ha puesto algo “equivo-cado” en el ArrayList, usted no conseguira ninguna compilacion —advertencias de tiempo o errores — usted descubrira solo a travesde una excepcion, el tiempo de ejecucion.

Cuando las referencias Trash son anadidas, pierden sus identi-dades especıficas y se vuelven simplemente Object references (sonupcast). Sin embargo, debido al polimorfismo el comportamientoapropiado se sigue produciendo cuando los metodos dinamicamenteenlazados son llamados a traves de la Iterator sorter, una vez que

139

Page 150: Traducción Thinking in Python

el Object resultante haya sido lanzado de nuevo a Trash. sum-Value( ) tambien toma un Iterator para realizar operaciones encada objeto en el ArrayList.

Parece una tonterıa moldear los tipos de Trash en una base decontenedor tipo de referencia base, y luego dar un giro y abatirlo.¿Por que no poner la basura en el recipiente adecuado en el primerlugar? (De hecho, se trata de todo el enigma del reciclaje). En esteprograma serıa facil reparar, pero a veces la estructura y la flexibili-dad de un sistema pueden beneficiarse enormemente de downcast-ing.

El programa cumple con los requisitos de diseno: funciona. Estopodrıa estar bien, siempre y cuando se trata de una solucion deprimera mano. Ahora bien, un programa util tiende a evolucionarcon el tiempo, por lo que se debe preguntar: “¿Que pasa si lasituacion cambia?” Por ejemplo, el carton es ahora un valioso pro-ducto reciclable, ası que como eso sera integrado en el sistema (espe-cialmente si el programa es grande y complicado). Desde la anteriorcodificacion de tipo de verificacion en la declaracion switch podrıaestar dispersa en todo el programa, usted debe ir a buscar todo esecodigo cada vez que se agrega un nuevo tipo, y si se le pasa alguna,el compilador no le dara ninguna ayuda senalando un error.

La clave para el mal uso de RTTI aquı, es que cada tipo se ponea prueba. Si usted esta buscando solo un subconjunto de tipos,porque ese subconjunto necesita un tratamiento especial, eso proba-blemente esta muy bien. Pero si usted esta buscando para cada tipodentro de una sentencia switch, entonces usted esta probablementeperdiendo un punto importante, y definitivamente hacer su codigomenos mantenible. En la siguiente seccion vamos a ver como esteprograma ha evolucionado a lo largo de varias etapas para llegar aser mucho mas flexible. Esto debe resultar un ejemplo valioso en eldiseno del programa.

140

Page 151: Traducción Thinking in Python

Mejorando el diseno

Las soluciones en Design Patterns se organizan en torno a la pre-gunta “¿Que va a cambiar a medida que evoluciona este programa?”Esta suele ser la pregunta mas importante que usted puede pregun-tar acerca de cualquier diseno. Si usted puede construir su sistemaen torno a la respuesta, los resultados seran de dos vertientes: nosolo que su sistema permite un facil (y barato) mantenimiento, sinoque tambien se puedan producir componentes reutilizables, de modoque los otros sistemas se puedan construir de forma mas economica.Esta es la promesa de la programacion orientada a objetos, pero estono sucede automaticamente; se requiere el pensamiento y la vision desu parte. En esta seccion veremos como este proceso puede sucederdurante el refinamiento de un sistema.

A la pregunta “¿Que va a cambiar? para el sistema de reciclajees una respuesta comun: se anadiran mas tipos al sistema. El obje-tivo del diseno, entonces, es hacer de esta adicion de tipos lo menosdoloroso posible. En el programa de reciclaje, nos gustarıa encap-sular todos los lugares donde se menciona la informacion de tipoespecıfico, ası (si no por otra razon) los cambios se pueden localizara esas encapsulaciones. Resulta que este proceso tambien limpia elresto del codigo considerablemente.

“Hacer mas objetos”

Esto por lo general nos lleva a los principios de la programacionorientada a objetos, la cual escuche por primera vez hablado porGrady Booch: “Si el diseno es demasiado complicado, hacer masobjetos.” Esto es ridıculamente simple y al mismo tiempo intuitivo;sin embargo es la guıa mas util que he encontrado. (Es posible ob-servar que ”hacer mas objetos” a menudo es equivalente a ”agregarotro nivel de indireccion.”) En general, si usted encuentra un lugarcon codigo desordenado, tenga en cuenta que tipo de clase limpiarıaeso. A menudo, el efecto secundario de la limpieza del codigo seraun sistema que tiene mejor estructura y es mas flexible.

Considere primero el lugar donde se crean los objetos Trash, quees una sentencia switch dentro de main():

141

Page 152: Traducción Thinking in Python

f o r ( i n t i = 0 i < 30 i++)switch ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :

case 0 :bin . add (new

Aluminum(Math . random ( ) ∗ 100))break

case 1 :bin . add (new

Paper (Math . random ( ) ∗ 100))break

case 2 :bin . add (new

Glass (Math . random ( ) ∗ 100))

Esto es definitivamente desordenado, y tambien un lugar dondeusted debe cambiar el codigo cada vez que se agrega un nuevo tipo.Si comunmente se anaden nuevos tipos, una mejor solucion es unsolo metodo que toma toda la informacion necesaria y produce unareferencia a un objeto del tipo correcto, yaproyectado a un objetode basura. En Design Patterns esto se conoce en general como unpatron creacional (de los cuales hay varios). El patron especıficoque se aplicara aquı es una variante del Factory Method : Metodode fabrica. Aquı, el metodo de fabrica es un miembro static deTrash, pero mas comunmente es un metodo que se anula en laclase derivada.

La idea del metodo de fabrica es que se le pasa la informacionesencial que necesita saber para crear su objeto, a continuacion,retroceder y esperar por la referencia (ya upcast al tipo base) paraque salga como el valor de retorno. A partir de entonces, usted trataal objeto polimorficamente. Ası, usted ni siquiera necesita saber eltipo exacto de objeto que se crea. De hecho, el metodo de fabricalo esconde de usted para evitar el mal uso accidental. Si desea uti-lizar el objeto sin polimorfismo, debe utilizar explıcitamente RTTIy difusion.

Pero hay un pequeno problema, especialmente cuando se utiliza elenfoque mas complicado (no se muestra aquı) de hacer que el metodode fabrica en la clase base y anulando en las clases derivadas. ¿Que

142

Page 153: Traducción Thinking in Python

pasa si la informacion requerida en la clase derivada requiere mas odiferentes argumentos? “la creacion de mas objetos” resuelve esteproblema. Para implementar el metodo de fabrica, la clase Trashconsigue un nuevo metodo llamado factory. Para ocultar los datoscreacionales, hay una nueva clase llamada Messenger que llevatoda la informacion necesaria para el metodo factory para crearel objeto Trash apropiado (hemos empezado haciendo referencia aMessenger como un patron de diseno, pero es bastante simple comopara que no lo pueda elevar a ese estado). Aquı esta una simpleimplementacion de Messenger:

c l a s s Messenger :i n t type# Must change t h i s to add another type :s t a t i c f i n a l i n t MAXNUM = 4double datade f i n i t ( s e l f , i n t typeNum , double va l ) :

type = typeNum % MAXNUMdata = va l

El unico trabajo de un objeto Messenger es mantener la infor-macion para el metodo factory( ). Ahora, si hay una situacion enla que factory( ) necesita informacion mas o diferente para crearun nuevo tipo de objeto Trash, la interfaz factory( ) no necesitaser cambiada. La clase Messenger puede ser cambiada mediante laadicion de nuevos datos y nuevos constructores, o en la mas tıpicamanera de las subclases en la programacion orientada a objetos.

El metodo factory( ) para este sencillo ejemplo se ve ası:

s t a t i c Trash f a c t o r y ( Messenger i ) :switch ( i . type ) :

d e f a u l t : # To qu i e t the compi le rcase 0 :

r e turn Aluminum( i . data )case 1 :

r e turn Paper ( i . data )case 2 :

r e turn Glass ( i . data )

143

Page 154: Traducción Thinking in Python

# Two l i n e s here :case 3 :

r e turn Cardboard ( i . data )

Aquı, la determinacion del tipo exacto de objeto es simple, perose puede imaginar un sistema mas complicado en el cual factory() utiliza un algoritmo elaborado. El punto es que esta ahora escon-dido en un lugar, y usted debe saber llegar a este lugar cuando seagregan nuevos tipos.

La creacion de nuevos objetos es ahora mucho mas simple enmain( ):

f o r ( i n t i = 0 i < 30 i++)bin . add (

Trash . f a c t o r y (Messenger (

( i n t ) ( Math . random ( ) ∗ Messenger .MAXNUM) ,Math . random ( ) ∗ 100 ) ) )

Se crea un objeto Messenger para pasar los datos en factory(), que a su vez produce una especie de objeto Trash en la pilay devuelve la referencia que se agrega al ArrayList bin. Claro,si cambia la cantidad y tipo de argumento, esta declaracion to-davıa necesitara ser modificada, pero que puede ser eliminada sila creacion del objeto Messenger esta automatizada. Por ejemplo,un ArrayList de argumentos puede ser pasado en el constructor deun objeto Messenger (o directamente en una llamada factory( ),para el caso). Esto requiere que los argumentos sean analizados yverificados en tiempo de ejecucion, pero proporciona la mayor flex-ibilidad.

Se puede ver en el codigo que el problema “vector de cambio”de la fabrica es responsable de resolver: si agrega nuevos tipos alsistema (el cambio), o el unico codigo que debe ser modificado estadentro de la fabrica, por lo que la fabrica aısla el efecto de ese cambio.

144

Page 155: Traducción Thinking in Python

Un patron para la creacion de prototipos

Un problema con el diseno anterior es que todavıa requiere unaubicacion central donde deben ser conocidos todos los tipos de losobjetos: dentro del metodo factory(). Si regularmente se agregannuevos tipos al sistema, el metodo factory( ) debe cambiarse paracada nuevo tipo. Cuando usted descubre algo como esto, es util paratratar de avanzar un paso mas y mover toda la informacion sobreel tipo — incluyendo su creacion — en la clase que representa esetipo. De esta manera, la unica cosa que necesita hacer para agregarun nuevo tipo al sistema es heredar una sola clase.

Para mover la informacion relativa a la creacion de tipo en cadatipo especıfico de Trash, se utilizara el patron “prototype” (del libroDesign Patterns). La idea general es que usted tiene una secuenciaprincipal de los objetos, uno de cada tipo que usted esta intere-sado en hacer. Los objetos en esta secuencia solo se utilizan parala fabricacion de nuevos objetos, utilizando una operacion que noes diferente del esquema clone( ) incorporado en clase raız Objectde Java. En este caso, vamos a nombrar el metodo de clonaciontClone( ). Cuando este listo para hacer un nuevo objeto, presumi-blemente usted tiene algun tipo de informacion que establece el tipode objeto que desea crear, a continuacion, se mueve a traves de lasecuencia maestra comparando su informacion con cualquier infor-macion apropiada que se encuentra en los objetos de prototipo enla secuencia principal. Cuando usted encuentra uno que se ajuste asus necesidades, clonarlo.

En este esquema no hay informacion modificable para la creacion.Cada objeto sabe como exponer la informacion adecuada y la formade clonarse a sı mismo. Ası, el metodo factory( ) no necesita sercambiado cuando se anade un nuevo tipo al sistema.

Un enfoque al problema del prototipado es agregar un numerode metodos para apoyar la creacion de nuevos objetos. Ahora bien,en Java 1.1 ya hay apoyo para la creacion de nuevos objetos si tieneuna referencia al objeto Class. Con Java 1.1 reflection (reflexion)(introducido en el capıtulo 12 de Thinking in Java, segunda edicion)puede llamar a un constructor, incluso si tiene solo una referenciaal objeto Class. Esta es la solucion perfecta para el problema del

145

Page 156: Traducción Thinking in Python

prototipado.

La lista de los prototipos sera representada indirectamente poruna lista de referencias a todos los objetos de Class que desea crear.En adicion, si el prototipado falla, el metodo factory( ) asumiraque es porque un objeto particular Class no estaba en la lista, yse tratara de cargarlo. Al cargar los prototipos de forma dinamicacomo este, la clase Trash no necesita saber con que tipos esta traba-jando, por lo que no necesita ninguna modificacion al agregar nuevostipos. Esto permite que sea facilmente reutilizable durante todo elresto del capıtulo.

# c12 : t ra sh : Trash . py# Base c l a s s f o r Trash r e c y c l i n g examples .

c l a s s Trash :p r i v a t e double weightde f i n i t ( s e l f , double wt ) : weight = wtde f i n i t ( s e l f ) :de f getValue ( s e l f )de f getWeight ( s e l f ) : r e turn weight# Sums the value o f Trash given an# I t e r a t o r to any conta ine r o f Trash :de f sumValue ( s e l f , I t e r a t o r i t ) :

double va l = 0 .0 fwhi l e ( i t . hasNext ( ) ) :

# One kind o f RTTI :# A dynamical ly−checked ca s tTrash t = ( Trash ) i t . next ( )va l += t . getWeight ( ) ∗ t . getValue ( )p r i n t (

” weight o f ” +# Using RTTI to get type# in fo rmat ion about the c l a s s :t . g e tC la s s ( ) . getName ( ) +” = ” + t . getWeight ( ) )

p r i n t ” Total va lue = ” + val

146

Page 157: Traducción Thinking in Python

# Remainder o f c l a s s prov ide s# support f o r prototyp ing :p r i v a t e s t a t i c L i s t trashTypes =

ArrayList ( )de f f a c t o r y ( s e l f , Messenger i n f o ) :

f o r ( i n t i = 0 i < l en ( trashTypes ) i ++):# Somehow determine the type# to create , and c r e a t e one :Class tc = ( Class ) trashTypes . get ( i )i f ( t c . getName ( ) . index ( i n f o . id ) != −1):

t ry :# Get the dynamic con s t ruc to r method# that takes a double argument :Constructor c to r = tc . getConstructor (

Class [ ] double . c l a s s )# Cal l the con s t ruc to r# to c r e a t e a ob j e c t :r e turn ( Trash ) c to r . newInstance (

Object [ ] Double ( i n f o . data ) )catch ( Exception ex ) :

ex . pr intStackTrace ( System . e r r )throw RuntimeException (

”Cannot Create Trash ”)

# Class was not in the l i s t . Try to load i t ,# but i t must be in your c l a s s path !t ry :

p r i n t ”Loading ” + i n f o . idtrashTypes . add ( Class . forName ( i n f o . id ) )

catch ( Exception e ) :e . pr intStackTrace ( System . e r r )throw RuntimeException (

” Prototype not found ”)

# Loaded s u c c e s s f u l l y .# Recurs ive c a l l should work :r e turn f a c t o r y ( i n f o )

pub l i c s t a t i c c l a s s Messenger :

147

Page 158: Traducción Thinking in Python

pub l i c S t r ing idpub l i c double datapub l i c Messenger ( S t r ing name , double va l ) :

id = namedata = va l

# : ˜

La clase basica Trash y sumValue( ) permanecen como antes.El resto de la clase soporta el patron de prototipado. Primero vedos clases internas (que se hacen static, ası que son las clases in-ternas solamente para los propositos de la organizacion de codigo)describiendo excepciones que pueden ocurrir. Esto es seguido porun ArrayList llamado trashTypes, que se utiliza para mantenerlas referencias Class.

En Trash.factory( ), el String dentro del objeto Messengerid (una version diferente de la clase Messenger que el de la dis-cusion previa) contiene el nombre del tipo de la Trash a crearse;este String es comparado con los nombres Class en la lista. Si hayuna coincidencia, entonces ese es el objeto a crear. Por supuesto,hay muchas formas para determinar que objeto desea hacer. Estese utiliza para que la informacion leıda desde un archivo se puedaconvertir en objetos.

Una vez que haya descubierto que tipo de Trash crear, a con-tinuacion, los metodos de reflexion entran en juego. El metodogetConstructor( ) toma un argumento que es un array de refer-encias Class. Este array representa los argumentos, en su debidoorden, para el constructor que usted esta buscando. Aquı, el arrayes creado de forma dinamica usando Jvaa 1.1 la sintaxis de creacionde array:

Class [ ] : double . c l a s s

Este codigo asume que cada tipo Trash tiene un constructorque toma un double (y observe que double.class es distinto deDouble.class). Tambien es posible, por una solucion mas flexible,llamar getConstructors( ), que devuelve un array con los posiblesconstructores.

148

Page 159: Traducción Thinking in Python

Lo que viene de vuelta de getConstructor( ) es una referencia aun objeto Constructor (parte de java.lang.reflect). Usted llamaal constructor de forma dinamica con el metodo newInstance( ),lo cual toma un array de Object conteniendo los argumentos reales.Este array se crea de nuevo utilizando la sintaxis de Java 1.1:

Object [ ] Double ( Messenger . data )

En este caso, sin embargo, el double debe ser colocado dentrode una clase de contenedor de modo que pueda ser parte de estearray de objetos. El proceso de llamar newInstance( ) extrae eldouble, pero se puede ver que es un poco confuso — un argumentopuede ser un double o un Double, pero cuando se hace la llamadasiempre se debe pasar en un Double. Afortunadamente, existe esteproblema solo para los tipos primitivos.

Una vez que entienda como hacerlo, el proceso de crear un nuevoobjeto dado solo una referencia Class es muy simple. Reflexiontambien le permite llamar metodos en esta misma manera dinamica.

Por supuesto, la referencia apropiada Class podrıa no estar en lalista trashTypes. En este caso, el return en el bucle interno no seejecuta y que se retirara al final. Aquı, el programa trata de recti-ficar la situacion mediante la carga del objeto Class dinamicamentey agregarlo a la lista trashTypes. Si aun ası no se puede encon-trar algo anda realmente mal, pero si la carga tiene exito, entonces elmetodo factory se llama de forma recursiva para volver a intentarlo.

Como vera, la belleza de este diseno es que el codigo no necesitaser cambiado, independientemente de las diferentes situaciones enque se utilizara (asumiendo que todas las subclases Trash contieneun constructor que toma un solo argumento double).

Subclases Trash

Para encajar en el esquema de prototipado, lo unico que se requierede cada nueva subclase de Trash es que contiene un constructor quetoma un argumento double. Java reflexion se encarga de todo lo

149

Page 160: Traducción Thinking in Python

demas.

Estos son los diferentes tipos de Trash, cada uno en su propioarchivo, pero parte del paquete Trash (de nuevo, para facilitar lareutilizacion dentro del capıtulo):

# c12 : t ra sh : Aluminum . py# The Aluminum c l a s s with prototyp ing .

c l a s s Aluminum( Trash ) :p r i v a t e s t a t i c double va l = 1 .67 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )de f getValue ( s e l f ) : r e turn va lde f setValue ( s e l f , double newVal ) :

va l = newVal

# : ˜# c12 : t ra sh : Paper . py# The Paper c l a s s with prototyp ing .

c l a s s Paper ( Trash ) :p r i v a t e s t a t i c double va l = 0 .10 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )de f getValue ( s e l f ) : r e turn va lde f setValue ( s e l f , double newVal ) :

va l = newVal

# : ˜# c12 : t ra sh : Glass . py# The Glass c l a s s with prototyp ing .

c l a s s Glass ( Trash ) :p r i v a t e s t a t i c double va l = 0 .23 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )de f getValue ( s e l f ) : r e turn va lde f setValue ( s e l f , double newVal ) :

va l = newVal

# : ˜

150

Page 161: Traducción Thinking in Python

Y aquı hay un nuevo tipo de Trash(basura):

# c12 : t ra sh : Cardboard . py# The Cardboard c l a s s with prototyp ing .

c l a s s Cardboard ( Trash ) :p r i v a t e s t a t i c double va l = 0 .23 fde f i n i t ( s e l f , double wt ) : . i n i t (wt )de f getValue ( s e l f ) : r e turn va lde f setValue ( s e l f , double newVal ) :

va l = newVal

# : ˜

Se puede ver que, aparte del constructor, no hay nada de especialen cualquiera de estas clases.

Analizar Trash desde un archivo externo

La informacion sobre los objetos Trash sera leıdo desde un archivoexterior. El archivo cuenta con toda la informacion necesaria sobrecada pieza de basura en una sola lınea en la forma de Trash:weight,como:

# c12 : t ra sh : Trash . datc12 . t ra sh . Glass : 54c12 . t ra sh . Paper : 22c12 . t ra sh . Paper : 11c12 . t ra sh . Glass : 17c12 . t ra sh . Aluminum :89c12 . t ra sh . Paper : 88c12 . t ra sh . Aluminum :76c12 . t ra sh . Cardboard :96c12 . t ra sh . Aluminum :25c12 . t ra sh . Aluminum :34c12 . t ra sh . Glass : 11c12 . t ra sh . Glass : 68c12 . t ra sh . Glass : 43c12 . t ra sh . Aluminum :27c12 . t ra sh . Cardboard :44

151

Page 162: Traducción Thinking in Python

c12 . t ra sh . Aluminum :18c12 . t ra sh . Paper : 91c12 . t ra sh . Glass : 63c12 . t ra sh . Glass : 50c12 . t ra sh . Glass : 80c12 . t ra sh . Aluminum :81c12 . t ra sh . Cardboard :12c12 . t ra sh . Glass : 12c12 . t ra sh . Glass : 54c12 . t ra sh . Aluminum :36c12 . t ra sh . Aluminum :93c12 . t ra sh . Glass : 93c12 . t ra sh . Paper : 80c12 . t ra sh . Glass : 36c12 . t ra sh . Glass : 12c12 . t ra sh . Glass : 60c12 . t ra sh . Paper : 66c12 . t ra sh . Aluminum :36c12 . t ra sh . Cardboard :22# : ˜

Tenga en cuenta que la ruta de clase debe ser incluido al dar losnombres de las clases, de lo contrario la clase no sera encontrada.

Este archivo se lee utilizando la herramienta StringList definidapreviamente, y cada lınea es recogido aparte usando el metodoString indexOf( ) para producir el ındice del ‘:’. Esto se utilizaprimero con el metodo String substring( ) para extraer el nombredel tipo de basura, y al lado para obtener el valor que se convirtio enun double con el metodo static Double.valueOf( ). El metodotrim( ) elimina los espacios en blanco en ambos extremos de unstring : cadena.

El analizador Trash es colocado en un archivo separado, ya quese reutilizara en todo este capıtulo:

# c12 : t ra sh : ParseTrash . py# Parse f i l e contents in to Trash ob j ec t s ,# p lac ing each in to a F i l l a b l e ho lder .

152

Page 163: Traducción Thinking in Python

c l a s s ParseTrash :de f f i l l B i n ( S t r ing f i l ename , F i l l a b l e bin ) :

f o r l i n e in open ( f i l ename ) . r e a d l i n e s ( ) :S t r ing type = l i n e . sub s t r i ng (0 ,

l i n e . index ( ’ : ’ ) ) . s t r i p ( )double weight = Double . valueOf (

l i n e . sub s t r i ng ( l i n e . index ( ’ : ’ ) + 1). s t r i p ( ) ) . doubleValue ( )

bin . addTrash (Trash . f a c t o r y (

Trash . Messenger ( type , weight ) ) )

# S p e c i a l case to handle C o l l e c t i o n :de f f i l l B i n ( S t r ing f i l ename , C o l l e c t i o n bin ) :

f i l l B i n ( f i l ename , F i l l a b l e C o l l e c t i o n ( bin ) )

# : ˜

En RecycleA.py, un ArrayList se utiliza para contener los ob-jetos Trash. Sin embargo, otros tipos de contenedores pueden serutilizados tambien. Para permitir esto, la primera version de fill-Bin( ) hace una referencia a un Fillable, lo cual es simplementeuna interface que soporta un metodo llamado addTrash( ):

# c12 : t ra sh : F i l l a b l e . py# Any ob j e c t that can be f i l l e d with Trash .

c l a s s F i l l a b l e :de f addTrash ( s e l f , Trash t )

# : ˜

Cualquier cosa que soporta esta interfaz se puede utilizar confillBin. Claro, Collection no implementa Fillable, por lo queno va a funcionar. Dado que Collection se utiliza en la mayorıade los ejemplos, tiene sentido anadir un segundo metodo fillBin() sobrecargado que toma un Collection. Cualquier Collection acontinuacion, se puede utilizar como un objeto Fillable usando unaclase adaptador:

# c12 : t ra sh : F i l l a b l e C o l l e c t i o n . py

153

Page 164: Traducción Thinking in Python

# Adapter that makes a C o l l e c t i o n F i l l a b l e .

c l a s s F i l l a b l e C o l l e c t i o n ( F i l l a b l e ) :p r i v a t e C o l l e c t i o n cde f i n i t ( s e l f , C o l l e c t i o n cc ) :

c = cc

de f addTrash ( s e l f , Trash t ) :c . add ( t )

# : ˜

Se puede ver que el unico trabajo de esta clase es conectar elmetodo addTrash( ) de Fillable a Collection’s add( ). Conesta clase en la mano, el metodo sobrecargado fillBin( ) se puedeutilizar con un Collection en ParseTrash.py.

pub l i c s t a t i c voidf i l l B i n ( S t r ing f i l ename , C o l l e c t i o n bin ) :

f i l l B i n ( f i l ename , F i l l a b l e C o l l e c t i o n ( bin ) )

Este enfoque funciona para cualquier clase de contenedor quese utiliza con frecuencia. Alternativamente, la clase de contenedorpuede proporcionar su propio adaptador que implementa Fillable.(Usted vera esto despues, en DynaTrash.py.)

Reciclaje con prototipos

Ahora se puede ver la version revisada de RecycleA.py utilizandola tecnica de prototipos:

# c12 : r e cyc l e ap : RecycleAP . py# Recyc l ing with RTTI and Prototypes .

c l a s s RecycleAP ( UnitTest ) :C o l l e c t i o n

bin = ArrayList ( ) ,g l a s sB in = ArrayList ( ) ,paperBin = ArrayList ( ) ,

154

Page 165: Traducción Thinking in Python

alBin = ArrayList ( )de f i n i t ( s e l f ) :

# F i l l up the Trash bin :ParseTrash . f i l l B i n (

” . . / t ra sh /Trash . dat ” , bin )

de f t e s t ( s e l f ) :I t e r a t o r s o r t e r = bin . i t e r a t o r ( )# Sort the Trash :whi l e ( s o r t e r . hasNext ( ) ) :

Object t = s o r t e r . next ( )# RTTI to show c l a s s membership :i f ( t i n s t a n c e o f Aluminum)

alBin . add ( t )i f ( t i n s t a n c e o f Paper )

paperBin . add ( t )i f ( t i n s t a n c e o f Glass )

g l a s sB in . add ( t )

Trash . sumValue ( a lBin . i t e r a t o r ( ) )Trash . sumValue ( paperBin . i t e r a t o r ( ) )Trash . sumValue ( g l a s sB in . i t e r a t o r ( ) )Trash . sumValue ( bin . i t e r a t o r ( ) )

de f main ( s e l f , S t r ing args [ ] ) :RecycleAP ( ) . t e s t ( )

# : ˜

Todos los objetos Trash, ası como las clases ParseTrash y deapoyo, ahora son parte del paquete de c12.trash, por lo que sim-plemente son importados.

El proceso de abrir el archivo de datos que contiene descripcionesTrash y el analisis de ese archivo han sido envuelto en el metodostatic ParseTrash.fillBin( ), por lo que ahora ya no es parte denuestro enfoque de diseno. Vera que en el resto del capıtulo, noimporta que se agregan nuevas clases, ParseTrash.fillBin( ) con-tinuara funcionando sin cambios, lo que indica un buen diseno.

155

Page 166: Traducción Thinking in Python

En terminos de creacion de objetos, este diseno en efecto, localizaseveramente los cambios que necesita hacer para agregar un nuevotipo al sistema. Ahora bien, hay un problema significativo en eluso de RTTI que se muestra claramente aquı. El programa parecefuncionar bien, y sin embargo, nunca se detecta algun cardboard :carton, a pesar de que cardboard esta en la lista! Esto sucede de-bido a el uso de RTTI, que busca solo los tipos que le indican quedebe buscar. La pista que RTTI esta siendo mal utilizada es quecada tipo en el sistema se esta probando, en lugar de un solo tipo osubconjunto de tipos. Como se vera mas adelante, hay maneras deutilizar polimorfismo en lugar de cuando se esta probando para cadatipo. Pero si usa RTTI mucho de esta manera, y anade un nuevotipo a su sistema, usted puede olvidar facilmente hacer los cambiosnecesarios en su programa y producir un error difıcil de encontrar.Ası que vale la pena tratar de eliminar RTTI en este caso, no solopor razones esteticas — produce codigo mas mantenible.

Haciendo abstraccion de uso

Con la creacion fuera del camino, es el momento de abordar el restodel diseno: donde se utilizan las clases. Dado que es el acto de laclasificacion en los contenedores que es particularmente feo y ex-puesto, por que no tomar ese proceso y ocultarlo dentro de unaclase? Este es el principio de ”Si debe hacer algo feo, al menos lo-calizar la fealdad dentro de una clase.” Se parece a esto:

La inicializacion de objetos TrashSorter ahora debe ser cambi-ado cada vez que un nuevo tipo de Trash se anade al modelo. Ustedpodrıa imaginar que la clase TrashSorter podrıa ser algo como esto:

c l a s s TrashSorter ( ArrayList ) :

156

Page 167: Traducción Thinking in Python

de f s o r t ( s e l f , Trash t ) : /∗ . . . ∗/Es decir, TrashSorter es un ArrayList de referencias a Ar-

rayLists de referencias Trash, y con puede instalar otro, ası:

TrashSorter t s = TrashSorter ( )t s . add ( ArrayList ( ) )

Ahora, sin embargo, sort( ) se convierte en un problema. ¿Comoel metodo estaticamentecodificado trata con el hecho de que unnuevo tipo ha sido anadido? Para solucionar esto, la informacion detipo debe ser removido de sort( ) de manera que todo lo que quenecesita hacer es llamar a un metodo generico que se ocupa de losdetalles del tipo. Esto, por supuesto, es otra manera de describirun metodo dinamicamente enlazado. Ası sort( ) simplemente semovera a traves de la secuencia y llamar a un metodo dinamicamenteenlazado para cada ArrayList. Dado que el trabajo de este metodoes tomar las piezas de basura en que esta interesado, este es llamadograb(Trash). La estructura ahora queda como:

TrashSorter necesita llamar cada metodo grab() y obtener unresultado diferente dependiendo de que tipo de Trash ArrayListactual esta sosteniendo. Es decir, cada ArrayList debe ser con-sciente del tipo que contiene. El enfoque clasico a este problemaes crear una clase ”Trash bin : Contenedor de basura” base yheredar una nueva clase derivada para cada tipo diferente que quieramantener. Si Java tenıa un mecanismo de tipo parametrizado ese

157

Page 168: Traducción Thinking in Python

probablemente serıa el enfoque mas sencillo. Pero en lugar de lacodificacion manual de todas las clases que tal mecanismo debe es-tar construyendo para nosotros, mayor observacion puede producirun mejor enfoque.

Un principio basico de diseno en programacion orientada a obje-tos es: ”Usar los miembros de datos para la variacion en el estado,utilice el polimorfismo para la variacion en el comportamiento.” Suprimer pensamiento podrıa ser que el metodo grab( ) ciertamentese comporta de manera diferente para un ArrayList que contienePaper que para uno que sostiene Glass. Pero lo que hace es estric-tamente dependiente del tipo, y nada mas. Esto podrıa interpretarsecomo un estado diferente, y dado que Java tiene una clase para rep-resentar el tipo (Class) Esto se puede utilizar para determinar eltipo de Trash que sostendra a Tbin particular .

El constructor para este Tbin requiere que le entregue la Classde su eleccion. Esto le dice al ArrayList que tipo se supone quedebe mantener. Entonces el metodo grab( ) usa Class BinTypey RTTI para ver si el objeto Trash que ha entregado coincide conel tipo que se supone que agarra.

Aquı esta una nueva version del programa:

# c12 : r e c y c l e b : RecycleB . py# Conta iners that grab o b j e c t s o f i n t e r e s t .

# A conta ine r that admits only the r i g h t type# of Trash ( e s t a b l i s h e d in the con s t ruc to r ) :c l a s s Tbin :

p r i v a t e C o l l e c t i o n l i s t = ArrayList ( )p r i v a t e Class typede f i n i t ( s e l f , Class binType ) : type = binTypede f grab ( s e l f , Trash t ) :

# Comparing c l a s s types :i f ( t . g e tC la s s ( ) . equa l s ( type ) ) :

l i s t . add ( t )re turn 1 # Object grabbed

158

Page 169: Traducción Thinking in Python

r e turn 0 # Object not grabbed

de f i t e r a t o r ( s e l f ) :r e turn l i s t . i t e r a t o r ( )

c l a s s TbinList ( ArrayList ) :de f s o r t ( s e l f , Trash t ) :

I t e r a t o r e = i t e r a t o r ( ) # I t e r a t e over s e l fwhi l e ( e . hasNext ( ) )

i f ( ( ( Tbin ) e . next ( ) ) . grab ( t ) ) re turn# Need a Tbin f o r t h i s type :

add ( Tbin ( t . g e tC la s s ( ) ) )s o r t ( t ) # Recurs ive c a l l

c l a s s RecycleB ( UnitTest ) :C o l l e c t i o n bin = ArrayList ( )TbinList t rashBins = TbinList ( )de f i n i t ( s e l f ) :

ParseTrash . f i l l B i n ( ” . . / t ra sh /Trash . dat ” , bin )

de f t e s t ( s e l f ) :I t e r a t o r i t = bin . i t e r a t o r ( )whi l e ( i t . hasNext ( ) )

t rashBins . s o r t ( ( Trash ) i t . next ( ) )I t e r a t o r e = trashBins . i t e r a t o r ( )whi l e ( e . hasNext ( ) ) :

Tbin b = ( Tbin ) e . next ( )Trash . sumValue (b . i t e r a t o r ( ) )

Trash . sumValue ( bin . i t e r a t o r ( ) )

de f main ( s e l f , S t r ing args [ ] ) :RecycleB ( ) . t e s t ( )

# : ˜

Tbin contiene una referencia Class type que establece en el con-structor que tipo debe agarrar. El metodo grab() revisa este tipocontra el objeto que se pasa. Tenga en cuenta que en este diseno,grab() solo acepta objetos Trash de este modo usted consigue la

159

Page 170: Traducción Thinking in Python

comprobacion de tipos en tiempo de compilacion del tipo base, perousted podrıa tambien apenas aceptar Object y todavıa funcionarıa.

TbinList sostiene un conjunto de referencias Tbin, ası que sort() puede iterar a traves de los Tbins cuando esta buscando unapareja para el objeto Trash lo habeis transmitido. Si este no en-cuentra una pareja, crea un nuevo Tbin para el tipo que no ha sidoenconstrado, y hace una llamada recursiva a sı mismo – la proximavez, se encontrara el nuevo bin.

Note la generalidad de este codigo: no cambia en absoluto sise anaden nuevos tipos. Si la mayor parte de su codigo no necesitacambiar cuando se anade un nuevo tipo (o algun otro cambio ocurre)entonces usted tiene un sistema facilmente extensible.

160

Page 171: Traducción Thinking in Python

Despacho multiple

El diseno anterior es ciertamente satisfactorio. La adicion de nuevostipos al sistema consiste en anadir o modificar clases distintas sincausar cambios en el codigo que se propagan por todo el sistema.En adicion, RTTI no esta ”mal utilizada” como lo estaba en Recy-cleA.py. Sin embargo, es posible ir un paso mas alla y tomar unpunto de vista purista sobre RTTI y decir que debe ser eliminadapor completo de la operacion de clasificar la basura en los contene-dores.

Para lograr esto, primero debe tomar la perspectiva de que to-das las actividades de tipo dependiente — tal como la deteccion deltipo de un pedazo de basura y ponerla en el recipiente apropiado —deben ser controladas a traves del polimorfismo y enlace dinamico.

Los ejemplos anteriores primero ordenados por tipo, entonces ac-tuaron en las secuencias de elementos que eran todos de un tipoparticular. Pero cada vez que usted se encuentra eligiendo tiposparticulares, detengase y piense. Toda la idea de polimorfismo(dinamicamente enlazado con llamadas a metodos) es encargarsede la informacion de tipo especıfico para usted. Ası que ¿por que labusqueda de tipos?

La respuesta es algo que probablemente no piensa: Python solorealiza despacho individual. Es decir, si esta realizando una op-eracion en mas de un objeto cuyo tipo es desconocido, Python in-vocara el mecanismo de enlace dinamico en solo uno de esos tipos.Esto no resuelve el problema, ası que usted termina la deteccionde algunos tipos manualmente y produciendo eficazmente su propiocomportamiento de enlace dinamico.

La solucion es llamada multiple dispatching : Despacho multiplelo cual significa la creacion de una configuracion tal que una unicallamada al metodo produce mas de una llamada a un metodo dinamicoy por lo tanto determina mas de un tipo en el proceso. Para con-seguir este efecto, usted necesita trabajar con mas de una jerarquıade tipos: usted necesitara una jerarquıa de tipos para cada envıo.El siguiente ejemplo trabaja con dos jerarquıas: la familia Trashexistente y una jerarquıa de los tipos de contenedores de basura en

161

Page 172: Traducción Thinking in Python

que la basura sera colocada. Esta segunda jerarquıa no siempre esevidente y en este caso esto necesitaba ser creado con el fin de pro-ducir despacho multiple (en este caso habra solo dos despachos, locual hace referencia como double dispatching : despacho doble).

162

Page 173: Traducción Thinking in Python

La implementacion del doble despacho

Recuerde que el polimorfismo puede ocurrir solo a traves de lla-madas a metodos, ası que si quiere que se produzca el despachodoble, deben existir dos llamadas a metodos: uno utilizado paradeterminar el tipo dentro de cada jerarquıa. En la jerarquıa Trashhabra un nuevo metodo llamado addToBin(), que toma un argu-mento de un array de TypedBin. Utiliza este array para recorrery tratar de agregarse a sı misma a a la papelera apropiada, y aquıes donde usted vera el doble despacho.

La nueva jerarquıa es TypedBin, y que contiene su propio metodollamado add() que tambien es utilizado polimorficamente. Pero aquıesta un giro adicional: add() esta sobrecargado para tomar argu-mentos de los diferentes tipos de trash : basura. Ası que una parteesencial del esquema de doble despacho tambien implica una so-brecarga. El rediseno del programa produce un dilema: ahora esnecesario para la clase base Trash contener un metodo addToBin(). Un enfoque consiste en copiar todo el codigo y cambiar la clasebase. Otro enfoque, que puede tomar cuando usted no tiene el con-trol del codigo fuente, es poner el metodo addToBin( ) dentro deun interface, dejar Trash solo, y heredar nuevos tipos especıficosde Aluminum, Paper, Glass, y Cardboard. Este es el enfoque

163

Page 174: Traducción Thinking in Python

que se tomara aquı.

La mayorıa de las clases en este diseno debe ser public, por loque se colocan en sus propios archivos. Aquı esta la interfaz:

# c12 : doub led i spatch : TypedBinMember . py# An c l a s s f o r adding the double# d i spa tch ing method to the t ra sh h i e ra r chy# without modifying the o r i g i n a l h i e ra r chy .

c l a s s TypedBinMember :# The method :boolean addToBin ( TypedBin [ ] tb )

# : ˜

En cada subtipo particular de Aluminum, Paper, Glass ,and Cardboard, el metodo addToBin( ) en la interfaz interfaceTypedBinMember es implementado, pero parece que el codigo esexactamente el mismo en cada caso:

# c12 : doub led i spatch : DDAluminum . py# Aluminum f o r double d i spa t ch ing .

c l a s s DDAluminum(Aluminum)implements TypedBinMember :

de f i n i t ( s e l f , double wt ) : . i n i t (wt )de f addToBin ( s e l f , TypedBin [ ] tb ) :

f o r ( i n t i = 0 i < tb . l ength i++)i f ( tb [ i ] . add ( s e l f ) )

r e turn 1re turn 0

# : ˜# c12 : doub led i spatch : DDPaper . py# Paper f o r double d i spa t ch ing .

c l a s s DDPaper( Paper )implements TypedBinMember :

de f i n i t ( s e l f , double wt ) : . i n i t (wt )

164

Page 175: Traducción Thinking in Python

de f addToBin ( s e l f , TypedBin [ ] tb ) :f o r ( i n t i = 0 i < tb . l ength i++)

i f ( tb [ i ] . add ( s e l f ) )r e turn 1

re turn 0

# : ˜# c12 : doub led i spatch : DDGlass . py# Glass f o r double d i spa t ch ing .

c l a s s DDGlass ( Glass )implements TypedBinMember :

de f i n i t ( s e l f , double wt ) : . i n i t (wt )de f addToBin ( s e l f , TypedBin [ ] tb ) :

f o r ( i n t i = 0 i < tb . l ength i++)i f ( tb [ i ] . add ( s e l f ) )

r e turn 1re turn 0

# : ˜# c12 : doub led i spatch : DDCardboard . py# Cardboard f o r double d i spa t ch ing .

c l a s s DDCardboard ( Cardboard )implements TypedBinMember :

de f i n i t ( s e l f , double wt ) : . i n i t (wt )de f addToBin ( s e l f , TypedBin [ ] tb ) :

f o r ( i n t i = 0 i < tb . l ength i++)i f ( tb [ i ] . add ( s e l f ) )

r e turn 1re turn 0

# : ˜

El codigo en cada addToBin( ) llama add( ) para cada objetoTypedBin en el array. Pero note el argumento: this. El tipo dethis es diferente para cada subclase de Trash, por lo que el codigoes diferente. (Aunque este codigo se beneficiara si un mecanismo detipo parametrizado es alguna vez agregado a Java.) Ası que esta

165

Page 176: Traducción Thinking in Python

es la primera parte del doble despacho, porque una vez que estadentro de este metodo usted sabe que es Aluminum, o Paper,etc. Durante la llamada a add( ), esta informacion se pasa a travesdel tipo de this. El compilador resuelve la llamada a la versioncorrecta sobrecargada de add( ). Pero puesto que tb[i] produceuna referencia al tipo base TypedBin, esta llamada va a terminarllamando a un metodo diferente dependiendo del tipo de Typed-Bin que esta actualmente seleccionado. Ese es el segundo despacho.

Aquı esta la clase base para TypedBin:

# c12 : doub led i spatch : TypedBin . py# A conta ine r f o r the second d i spatch .

c l a s s TypedBin :C o l l e c t i o n c = ArrayList ( )de f addIt ( s e l f , Trash t ) :

c . add ( t )re turn 1

de f i t e r a t o r ( s e l f ) :r e turn c . i t e r a t o r ( )

de f add ( s e l f , DDAluminum a ) :r e turn 0

de f add ( s e l f , DDPaper a ) :r e turn 0

de f add ( s e l f , DDGlass a ) :r e turn 0

de f add ( s e l f , DDCardboard a ) :r e turn 0

# : ˜

Puede ver que todos los metodos sobrecargados add( ) retornanfalse. Si el metodo no esta sobrecargado en una clase derivada,continuara retornando false, y el llamador (addToBin( ), en este

166

Page 177: Traducción Thinking in Python

caso) asumira que el objeto actual Trash no se ha anadido con exitoa un contenedor, y continuar buscando el contenedor correcto.

En cada una de las subclases de TypedBin, solo un metodo so-brecargado es anulado, de acuerdo con el tipo de bin que esta siendocreado. Por ejemplo, CardboardBin anula add(DDCardboard).El metodo anulado agrega el objeto trash : basura a su contenedor yretorna true, mientras todo el resto de los metodos add( ) en Card-boardBin continua para devolver false, ya que no se han anulado.Este es otro caso en el que un mecanismo de tipo parametrizadoen Java permitirıa la generacion automatica de codigo. (Con C++templates, usted no tendrıa que escribir explıcitamente las sub-clases o colocar el metodo addToBin( ) en Trash.)

Puesto que para este ejemplo los tipos de basura se han person-alizado y colocado en un directorio diferente, usted necesitara unarchivo de datos de basura diferente para hacer que funcione. Aquıesta un posible DDTrash.dat:

# c12 : doub led i spatch : DDTrash . datDDGlass : 54DDPaper : 22DDPaper : 11DDGlass : 17DDAluminum:89DDPaper : 88DDAluminum:76DDCardboard :96DDAluminum:25DDAluminum:34DDGlass : 11DDGlass : 68DDGlass : 43DDAluminum:27DDCardboard :44DDAluminum:18DDPaper : 91DDGlass : 63DDGlass : 50

167

Page 178: Traducción Thinking in Python

DDGlass : 80DDAluminum:81DDCardboard :12DDGlass : 12DDGlass : 54DDAluminum:36DDAluminum:93DDGlass : 93DDPaper : 80DDGlass : 36DDGlass : 12DDGlass : 60DDPaper : 66DDAluminum:36DDCardboard :22# : ˜

Aquı esta el resto del programa:

# c12 : doub led i spatch : DoubleDispatch . py# Using mul t ip l e d i spa t ch ing to handle more# than one unknown type during a method c a l l .

c l a s s AluminumBin ( TypedBin ) :de f add ( s e l f , DDAluminum a ) :

r e turn addIt ( a )

c l a s s PaperBin ( TypedBin ) :de f add ( s e l f , DDPaper a ) :

r e turn addIt ( a )

c l a s s GlassBin ( TypedBin ) :de f add ( s e l f , DDGlass a ) :

r e turn addIt ( a )

c l a s s CardboardBin ( TypedBin ) :de f add ( s e l f , DDCardboard a ) :

r e turn addIt ( a )

c l a s s TrashBinSet :

168

Page 179: Traducción Thinking in Python

p r i v a t e TypedBin [ ] b inSet =:AluminumBin ( ) ,PaperBin ( ) ,GlassBin ( ) ,CardboardBin ( )

de f s o r t In toB in s ( s e l f , C o l l e c t i o n bin ) :I t e r a t o r e = bin . i t e r a t o r ( )whi l e ( e . hasNext ( ) ) :

TypedBinMember t =(TypedBinMember ) e . next ( )

i f ( ! t . addToBin ( b inSet ) )System . e r r . p r i n t l n (” Couldn ’ t add ” + t )

pub l i c TypedBin [ ] b inSet ( ) : r e turn binSet

c l a s s DoubleDispatch ( UnitTest ) :C o l l e c t i o n bin = ArrayList ( )TrashBinSet b ins = TrashBinSet ( )de f i n i t ( s e l f ) :

# ParseTrash s t i l l works , without changes :ParseTrash . f i l l B i n (”DDTrash . dat ” , bin )

de f t e s t ( s e l f ) :# Sort from the master bin in to# the i n d i v i d u a l l y−typed b ins :b ins . s o r t In toB in s ( bin )TypedBin [ ] tb = bins . b inSet ( )# Perform sumValue f o r each bin . . .f o r ( i n t i = 0 i < tb . l ength i++)

Trash . sumValue ( tb [ i ] . c . i t e r a t o r ( ) )# . . . and f o r the master binTrash . sumValue ( bin . i t e r a t o r ( ) )

de f main ( s e l f , S t r ing args [ ] ) :DoubleDispatch ( ) . t e s t ( )

# : ˜

TrashBinSet encapsula todos los diferentes tipos de Typed-

169

Page 180: Traducción Thinking in Python

Bins, junto con el metodo sortIntoBins( ), que es donde todo eldoble despacho toma lugar. Usted puede ver que una vez que laestructura esta configurada, la clasificacion en los distintos Type-dBins es muy facil. En adicion, la eficiencia de dos llamadas almetodo dinamico es probablemente mejor que cualquier otra formausted podrıa ordenar.

Note la facilidad de uso de este sistema en main( ), ası como lacompleta independencia de cualquier informacion de tipo especıficodentro de main( ). Todos los otros metodos que hablan solo a lainterfaz de la clase base Trash seran igualmente invulnerable a loscambios en los tipos Trash.

Los cambios necesarios para agregar un nuevo tipo son relati-vamente aislados: modifica TypedBin, heredar el nuevo tipo deTrash con su metodo addToBin( ), luego heredar un nuevo Type-dBin (esto es realmente solo una copia y sencilla edicion), y porultimo anadir un nuevo tipo en la inicializacion agregada de Trash-BinSet.

El patron Visitor (Visitante)

Ahora considerar la aplicacion de un patron de diseno que tiene unobjetivo completamente diferente al problema de clasificacion de ba-sura.

Para este patron, ya no estamos preocupados con la optimizacionde la adicion de nuevos tipos de Trash para el sistema. Cierta-mente, este patron hace que la adicion de un nuevo tipo de Trashmas complicado. El supuesto es que usted tiene una jerarquıa declases primaria que es fija; quizas es de otro proveedor y no puedesrealizar cambios en esa jerarquıa. Sin embargo, usted tenıa comoanadir nuevos metodos polimorficos a esa jerarquıa, lo cual significaque normalmente tenıa que anadir algo a la interfaz de la clase base.Ası el dilema es que usted necesita anadir metodos a la clase base,pero no puede tocar la clase base. ¿Como se obtiene alrededor deesto?

170

Page 181: Traducción Thinking in Python

El patron de diseno que resuelve este tipo de problema es lla-mado un ”visitor : visitante” (la final en el libro Design Patterns: Patrones de Diseno), y se basa en el esquema de despacho doblemostrado en la ultima seccion.

El patron visitor : visitante le permite extender la interfaz deltipo primario mediante la creacion de una jerarquıa de clases porseparado de tipo Visitor para virtualizar las operaciones realizadasen el tipo primario. Los objetos del tipo primario simplemente”aceptan” el visitante, a continuacion, llaman el visitante del metododinamicamente enlazado. Se ve ası:

171

Page 182: Traducción Thinking in Python

Ahora, si v es una referencia Visitable para un objeto Alu-minum, el codigo:

P r i c e V i s i t o r pv = P r i c e V i s i t o r ( )v . accept ( pv )

utiliza despacho doble para causar dos llamadas a metodos polimorficos:el primero para seleccionar la version de Aluminum de accept( ),y el segundo dentro de accept( ) cuando la version especifica de

172

Page 183: Traducción Thinking in Python

visit( ) es llamada de forma dinamica usando la clase base Visitorreferencia v.

Esta configuracion significa que la nueva funcionalidad puede seranadido al sistema en forma de nuevas subclases de Visitor. Lajerarquıa Trash no necesita ser tocada. Este es el principal benefi-cio del patron visitante: usted puede agregar nueva funcionalidadpolimorfica a una jerarquıa de clases sin tocar esa jerarquıa (una vezque los metodos accept( ) se han instalado). Tenga en cuenta queel beneficio es util aquı, pero no es exactamente lo que empezamosa lograr, ası que a primera vista podrıa decidir que esta no es lasolucion deseada.

Pero mire una cosa que se ha logrado: la solucion visitante Evitala clasificacion de la secuencia Trash maestro en secuencias escritasindividuales. Ası, usted puede dejar todo en la unica secuenciamaestra y simplemente pasar a traves de esa secuencia utilizando elvisitante apropiado para lograr el objetivo. Aunque este compor-tamiento parece ser un efecto secundario del visitante, Esto nos dalo que queremos (evitando RTTI).

El despacho doble en el patron visitante se ocupa de determinartanto el tipo de Trash y el tipo de Visitor. En el siguiente ejem-plo, hay dos implementaciones de Visitor: PriceVisitor tantopara determinar y resumir el precio, y WeightVisitor hacer unseguimiento de los pesos.

Usted puede ver todo esto implementado en la nueva y mejoradaversion del programa de reciclaje.

Al igual que con DoubleDispatch.py, la clase Trash se dejasolo y una nueva interfaz es creada para agregar el metodo accept():

# c12 : t r a s h v i s i t o r : V i s i t a b l e . py# An c l a s s to add v i s i t o r f u n c t i o n a l i t y# to the Trash h i e ra r chy without# modifying the base c l a s s .

173

Page 184: Traducción Thinking in Python

c l a s s V i s i t a b l e :# The method :de f accept ( s e l f , V i s i t o r v )

# : ˜

Dado que no hay nada concreto en la clase base Visitor, se puedecrear como una interface:

# c12 : t r a s h v i s i t o r : V i s i t o r . py# The base c l a s s f o r v i s i t o r s .

c l a s s V i s i t o r :de f v i s i t ( s e l f , Aluminum a )de f v i s i t ( s e l f , Paper p)de f v i s i t ( s e l f , Glass g )de f v i s i t ( s e l f , Cardboard c )

# : ˜

Un decorador reflexivo

En este punto, usted podrıa seguir el mismo criterio que se utilizopara el despacho doble y crear nuevos subtipos de Aluminum, Pa-per, Glass, y Cardboard que implementan el metodo accept( ).Por ejemplo, el nuevo Visitable Aluminum se verıa ası:

# c12 : t r a s h v i s i t o r : VAluminum . py# Taking the prev ious approach o f c r e a t i n g a# s p e c i a l i z e d Aluminum f o r the v i s i t o r pattern .

c l a s s VAluminum(Aluminum)implements V i s i t a b l e :

de f i n i t ( s e l f , double wt ) : . i n i t (wt )de f accept ( s e l f , V i s i t o r v ) :

v . v i s i t ( s e l f )

# : ˜

Sin embargo, Parece que estamos encontrando una ”explosionde interfaces:” Trash basico, versiones especiales para el despa-cho doble, y ahora las versiones mas especiales para los visitantes.

174

Page 185: Traducción Thinking in Python

Claro, esta ”explosion de interfaces” es arbitraria — uno podrıasimplemente poner los metodos adicionales de la clase Trash. Siignoramos que en lugar podemos ver la oportunidad de utilizar elpatron Decorador : Parece como que deberıa ser posible crear unDecorador que puede ser envuelto alrededor de un objeto ordinarioTrash y producira la misma interfaz que Trash y agrega el metodoextra accept( ). De hecho, es un ejemplo perfecto del valor de Dec-orador.

El doble despacho crea un problema, no obstante. Como se basaen la sobrecarga de ambos accept( ) y visit( ), esto parecerıa re-querir codigo especializado para cada version diferente del metodoaccept( ). Con las plantillas de C ++, esto serıa bastante facil delograr (ya que las plantillas generan automaticamente codigo de tipoespecializado) pero Python no tiene tal mecanismo — al menos noparece. Sin embargo, reflexion le permite determinar la informacionde tipo en tiempo de ejecucion, y llegar a resolver muchos proble-mas que parecerıan requerir plantillas (aunque no tan simplemente).Aquı esta el decorador que hace el truco25:newpage

# c12 : t r a s h v i s i t o r : V i s i t ab l eDeco ra to r . py# A decora to r that adapts the g e n e r i c Trash# c l a s s e s to the v i s i t o r pattern .c l a s s V i s i t ab l eDeco ra to rextends Trash implements V i s i t a b l e :

p r i v a t e Trash de l e ga t ep r i v a t e Method d i spatchde f i n i t ( s e l f , Trash t ) :

d e l e ga t e = ttry :

d i spatch = V i s i t o r . c l a s s . getMethod (” v i s i t ” , Class [ ] : t . g e tC la s s ( )

)catch ( Exception ex ) :

ex . pr intStackTrace ( )de f getValue ( s e l f ) :

r e turn de l e ga t e . getValue ( )

25Esta fue una solucion creada por Jaroslav Tulach en una clase de diseno de patrones quedi en Praga

175

Page 186: Traducción Thinking in Python

de f getWeight ( s e l f ) :r e turn de l e ga t e . getWeight ( )

de f accept ( s e l f , V i s i t o r v ) :t ry :

d i spatch . invoke (v , Object [ ] de l e ga t e )catch ( Exception ex ) :

ex . pr intStackTrace ( )# : ˜

[[Descripcion del uso de Reflexion]]

La unica otra herramienta que necesitamos es un nuevo tipo deadaptador Fillable que automaticamente decora los objetos a me-dida que se crean a partir del archivo original Trash.dat. Pero estobien podrıa ser un decorador de sı mismo, la decoracion de cualquiertipo de Fillable:

# c12 : t r a s h v i s i t o r : F i l l a b l e V i s i t o r . py# Adapter Decorator that adds the v i s i t a b l e# decora to r as the Trash o b j e c t s are# being c rea ted .

c l a s s F i l l a b l e V i s i t o rimplements F i l l a b l e :

p r i v a t e F i l l a b l e fde f i n i t ( s e l f , F i l l a b l e f f ) : f = f fde f addTrash ( s e l f , Trash t ) :

f . addTrash ( V i s i t ab l eDeco ra to r ( t ) )# : ˜

Ahora usted puede envolver alrededor de cualquier tipo de Fillableexistente, o cualquier otros nuevos que aun no se han creado.

El resto del programa crea tipos Visitor especıficos y los envıaa traves de una lista unica de objetos Trash:

# c12 : t r a s h v i s i t o r : TrashVi s i to r . py# The ” v i s i t o r ” pattern with V i s i t a b l e D e c o r a t o r s .

# S p e c i f i c group o f a lgor i thms packaged

176

Page 187: Traducción Thinking in Python

# in each implementation o f V i s i t o r :c l a s s P r i c e V i s i t o r ( V i s i t o r ) :

p r i v a t e double alSum # Aluminump r i v a t e double pSum # Paperp r i v a t e double gSum # Glassp r i v a t e double cSum # Cardboardde f v i s i t ( s e l f , Aluminum a l ) :

double v = a l . getWeight ( ) ∗ a l . getValue ( )p r i n t ” value o f Aluminum= ” + valSum += v

def v i s i t ( s e l f , Paper p ) :double v = p . getWeight ( ) ∗ p . getValue ( )p r i n t ” value o f Paper= ” + vpSum += v

def v i s i t ( s e l f , Glass g ) :double v = g . getWeight ( ) ∗ g . getValue ( )p r i n t ” value o f Glass= ” + vgSum += v

def v i s i t ( s e l f , Cardboard c ) :double v = c . getWeight ( ) ∗ c . getValue ( )p r i n t ” value o f Cardboard = ” + vcSum += v

def t o t a l ( s e l f ) :p r i n t (

” Total Aluminum : $” + alSum +”\n Total Paper : $” + pSum +”\nTotal Glass : $” + gSum +”\nTotal Cardboard : $” + cSum +”\nTotal : $” +

( alSum + pSum + gSum + cSum ) )

c l a s s WeightVis i tor ( V i s i t o r ) :p r i v a t e double alSum # Aluminump r i v a t e double pSum # Paperp r i v a t e double gSum # Glass

177

Page 188: Traducción Thinking in Python

p r i v a t e double cSum # Cardboard

de f v i s i t ( s e l f , Aluminum a l ) :alSum += a l . getWeight ( )p r i n t (” weight o f Aluminum = ”

+ a l . getWeight ( ) )

de f v i s i t ( s e l f , Paper p ) :pSum += p . getWeight ( )p r i n t (” weight o f Paper = ”

+ p . getWeight ( ) )

de f v i s i t ( s e l f , Glass g ) :gSum += g . getWeight ( )p r i n t (” weight o f Glass = ”

+ g . getWeight ( ) )

de f v i s i t ( s e l f , Cardboard c ) :cSum += c . getWeight ( )p r i n t (” weight o f Cardboard = ”

+ c . getWeight ( ) )

de f t o t a l ( s e l f ) :p r i n t (

” Total weight Aluminum : ” + alSum +”\nTotal weight Paper : ” + pSum +”\nTotal weight Glass : ” + gSum +”\nTotal weight Cardboard : ” + cSum +”\nTotal weight : ” +

( alSum + pSum + gSum + cSum ) )

c l a s s TrashVi s i to r ( UnitTest ) :C o l l e c t i o n bin = ArrayList ( )P r i c e V i s i t o r pv = P r i c e V i s i t o r ( )WeightVis i tor wv = WeightVis i tor ( )de f i n i t ( s e l f ) :

ParseTrash . f i l l B i n ( ” . . / t ra sh /Trash . dat ” ,F i l l a b l e V i s i t o r (

F i l l a b l e C o l l e c t i o n ( bin ) ) )

178

Page 189: Traducción Thinking in Python

de f t e s t ( s e l f ) :I t e r a t o r i t = bin . i t e r a t o r ( )whi l e ( i t . hasNext ( ) ) :

V i s i t a b l e v = ( V i s i t a b l e ) i t . next ( )v . accept ( pv )v . accept (wv)

pv . t o t a l ( )wv . t o t a l ( )

de f main ( s e l f , S t r ing args [ ] ) :TrashVi s i to r ( ) . t e s t ( )

# : ˜

En Test( ), observe como se anade la visitabilidad simplementecreando un tipo diferente de bin usando el decorador. Observetambien que el adaptador FillableCollection tiene la aparienciade ser utilizado como decorador (para ArrayList) en esta situacion.Ahora bien, cambia completamente la interfaz del ArrayList, vistoque la definicion de Decorador es que la interfaz de la clase decoradaaun debe estar allı despues de la decoracion.

Tenga en cuenta que la forma del codigo del cliente (que se mues-tra en la clase Test) ha cambiado de nuevo, a partir de los enfoquesoriginales al problema. Ahora solo hay un solo bin Trash. Los dosobjetos Visitor son aceptados en cada elemento de la secuencia, yrealizan sus operaciones. Los visitantes mantienen sus propios datosinternos para concordar los pesos y precios totales.

Finalmente, no hay identificacion de tipo en tiempo de ejecucionque no sea el molde inevitable a Trash al tirar cosas fuera de la se-cuencia. Esto, tambien, podrıa ser eliminado con la implementacionde tipos parametrizados en Java.

Una manera en que usted puede distinguir esta solucion de lasolucion de despacho doble descrita anteriormente es tener en cuentaque, en la solucion del doble despacho, solamente uno de los metodossobrecargados, add( ), fue anulado cuando se creo cada subclase,

179

Page 190: Traducción Thinking in Python

mientras que aquı cada uno de los metodos visit( ) sobrecargadoses anulado en cada subclase de Visitor.

¿Mas acoplamiento?

Hay mucho mas codigo aquı, y hay acoplamiento definitivo entre lajerarquıa Trash y la jerarquıa Visitor. Ahora bien, tambien hayalta cohesion dentro de los respectivos conjuntos de clases: cadauno de ellos hacen una sola cosa (Trash describe Basura, mientrasque Visitor describe las acciones realizadas en Trash), que es unindicador de un buen diseno. Claro, en este caso funciona bien solosi esta agregando nuevos Visitors, pero se pone en el camino alagregar nuevos tipos de Trash.

Bajo acoplamiento entre clases y alta cohesion dentro de unaclase es sin duda un objetivo de diseno importante. Aplicado sinpensar, sin embargo, puede impedirle el logro de un diseno mas ele-gante.Parece que algunas clases, inevitablemente, tienen una ciertaintimidad con cada uno. Estos a menudo ocurren en parejas quequizas podrıan ser llamados couplets : coplas ; por ejemplo, los con-tenedores y los iteradores. El par anterior Trash-Visitor pareceser otro como couplet.

¿RTTI considerado danino?

Varios disenos en este capıtulo intentan eliminar RTTI, lo cualpodrıa darle la impresion de que se “considera perjudicial” (la con-denacion utilizado para pobres, malogrado goto, que por lo tantonunca fue puesto en Java). Esto no es verdad; es el mal uso deRTTI, ese es el problema. La razon por la que nuestros disenoseliminan RTTI se debe a la mala aplicacion de esa caracterıstica queimpide extensibilidad, mientras que el objetivo declarado era capazde anadir un nuevo tipo al sistema con el menor impacto en circun-dante codigo como sea posible. Dado que RTTI es a menudo malusado por tener que buscar todo tipo unico en su sistema, provocacodigo que no sea extensible: cuando se agrega un nuevo tipo, ustedtiene que ir a buscar por todo el codigo en el que se usa RTTI, y si

180

Page 191: Traducción Thinking in Python

se olvida de alguno usted no conseguira la ayuda del compilador.

Sin embargo, RTTI no crea automaticamente el codigo no exten-sible. Vamos a revisar el reciclador de basura una vez mas. Estavez, una nueva herramienta sera introducida, la cual yo llamo unTypeMap. Este contiene un HashMap que contiene ArrayLists,pero la interfaz es simple: usted puede add( ) un nuevo objeto,y puede get( ) un ArrayList que contiene todos los objetos deun tipo particular. Las claves para el contenido HashMap son lostipos en el ArrayList asociado. La belleza de este diseno (sugeridopor Larry O’Brien) es que el TypeMap agrega dinamicamente unnuevo par cada vez que encuentra un nuevo tipo, por lo que cadavez que anade un nuevo tipo al sistema (incluso si se agrega el nuevotipo en tiempo de ejecucion), se adapta.

Nuestro ejemplo nuevamente se basara en la estructura de lostipos Trash en package c12.Trash (y el archivo Trash.dat uti-lizado se pueden utilizar aquı sin modificar):

# c12 : dynatrash : DynaTrash . py# Using a Map o f L i s t s and RTTI# to automat i ca l l y s o r t t ra sh in to# ArrayLi s t s . This s o lu t i on , d e s p i t e the# use o f RTTI, i s e x t e n s i b l e .

# Generic TypeMap works in any s i t u a t i o n :c l a s s TypeMap :

p r i v a t e Map t = HashMap ( )de f add ( s e l f , Object o ) :

Class type = o . ge tC la s s ( )i f ( t . has key ( type ) )

( ( L i s t ) t . get ( type ) ) . add ( o )e l s e :

L i s t v = ArrayList ( )v . add ( o )t . put ( type , v )

de f get ( s e l f , Class type ) :r e turn ( L i s t ) t . get ( type )

181

Page 192: Traducción Thinking in Python

de f keys ( s e l f ) :r e turn t . keySet ( ) . i t e r a t o r ( )

# Adapter c l a s s to a l low c a l l b a c k s# from ParseTrash . f i l l B i n ( ) :c l a s s TypeMapAdapter ( F i l l a b l e ) :

TypeMap mapde f i n i t ( s e l f , TypeMap tm ) : map = tmdef addTrash ( s e l f , Trash t ) : map . add ( t )

c l a s s DynaTrash ( UnitTest ) :TypeMap bin = TypeMap( )

de f i n i t ( s e l f ) :ParseTrash . f i l l B i n ( ” . . / t ra sh /Trash . dat ” ,

TypeMapAdapter ( bin ) )

de f t e s t ( s e l f ) :I t e r a t o r keys = bin . keys ( )whi l e ( keys . hasNext ( ) )

Trash . sumValue (bin . get ( ( Class ) keys . next ( ) ) . i t e r a t o r ( ) )

de f main ( s e l f , S t r ing args [ ] ) :DynaTrash ( ) . t e s t ( )

# : ˜

Aunque potente, la definicion para TypeMap es simple. Con-tiene un HashMap, y el metodo add( ) hace la mayorıa del trabajo.Cuando usted add( ) un nuevo objeto, se extrae la referencia parael objeto Class para ese tipo. Esto se utiliza como una clave paradeterminar si un ArrayList que sostiene objetos de ese tipo ya estapresente en el HashMap. Si es ası, ese ArrayList se extrae y elobjeto se anade al ArrayList. Si no, el objeto Class y un nuevoArrayList se anaden como un par clave-valor.

Usted puede obtener un Iterator de todos los objetos Class dekeys( ), y usar cada objeto Class para buscar el correspondienteArrayList con get( ). Y eso es todo lo que hay que hacer.

182

Page 193: Traducción Thinking in Python

El metodo filler( ) es interesante porque Se aprovecha del disenode ParseTrash.fillBin( ), que no solo tratar de llenar un Ar-rayList sino cualquier cosa que implementa la interfaz Fillable consu metodo addTrash( ). Todo filler( ) necesita hacer es devolveruna referencia a una interface que implementa Fillable, y luegoesta referencia puede ser utilizado como un argumento a fillBin( )como esto:

ParseTrash . f i l l B i n (” Trash . dat ” , bin . f i l l e r ( ) )

Para producir esta referencia, una clase interna anonima (de-scrito en el capıtulo 8 de Thinking in Java, segunda edicion) esutilizada. Usted nunca necesita una clase llamada para implemen-tar Fillable, solo necesita una referencia a un objeto de esa clase,por lo que este es un uso apropiado de las clases internas anonimas.

Una cosa interesante sobre este diseno es que a pesar de que nofue creado para manejar la clasificacion, fillBin( ) esta realizandotipo cada vez que se inserta un objeto Trash dentro de bin.

Gran parte de class DynaTrash debe estar familiarizado par-tir de los ejemplos anteriores. Esta vez, en lugar de colocar losnuevos objetos Trash en un bin de tipo ArrayList, bin es de tipoTypeMap, ası que cuando la basura es arrojada en bin se ordenade inmediato por el TypeMap del mecanismo de clasificacion in-terna. Dando un paso a traves de TypeMap y operando en cadaArrayList individual se convierte en un asunto sencillo.

Como puede ver, la adicion de un nuevo tipo al sistema no afec-tara este codigo en absoluto, y el codigo en TypeMap es completa-mente independiente. Esta es ciertamente la solucion mas pequenadel problema, y podrıa decirse que el mas elegante tambien. Nodepende mucho de de RTTI, pero observe que cada par clave-valoren el HashMap esta en busca de un solo tipo. En adicion, nohay manera que usted puede ”olvidar” anadir el codigo adecuado aeste sistema cuando se agrega un nuevo tipo, ya que no hay ninguncodigo que necesite agregar.

183

Page 194: Traducción Thinking in Python

Resumen

Surgir con un diseno como TrashVisitor.py que contiene una mayorcantidad de codigo que los disenos anteriores puede parecer en unprincipio ser contraproducente. Vale la pena notar lo que estastratando de lograr con varios disenos. Los patrones de diseno engeneral se esfuerzan por separar las cosas que cambian de las cosasque permanecen igual. Las ”cosas que cambian” puede referirse amuchos tipos diferentes de cambios. Quizas el cambio ocurre porqueel programa se coloca en un nuevo entorno o porque algo en el en-torno actual cambia: (esto podrıa ser: ”El usuario quiere anadiruna nueva forma para el diagrama actualmente en la pantalla”). O,como en este caso, el cambio podrıa ser la evolucion del cuerpo delcodigo. Mientras que las versiones anteriores del ejemplo de clasi-ficacion de basura enfatizaron la adicion de nuevos tipos de Trashal sistema, TrashVisitor.py le permite anadir facilmente nuevasfuncionalidades sin molestar a la jerarquıa Trash. Hay mas codigoen TrashVisitor.py, pero la adicion de nueva funcionalidad paraVisitor es de mal gusto. Si esto es algo que sucede mucho, entoncesvale la pena el esfuerzo extra y el codigo para hacer que suceda conmas facilidad.

El descubrimiento del vector de cambio no es un asunto trivial;esto no es algo que un analista usualmente puede detectar antes deque el programa considera este su diseno inicial. La informacionnecesaria probablemente no aparecera hasta las ultimas fases delproyecto: a veces solo en las fases de diseno o de implementacionse descubre una necesidad mas profunda o mas sutil en su sistema.En el caso de la adicion de nuevos tipos (el cual fue el foco de lamayorıa de los ejemplos ”reciclar”) usted puede darse cuenta de quenecesita una jerarquıa de herencia particular solo cuando se esta enla fase de mantenimiento y de comenzar la ampliacion del sistema!

Una de las cosas mas importantes que aprendera mediante el es-tudio de los patrones de diseno parece ser un cambio de actitud delo que se ha promovido hasta ahora en este libro. Es decir: ”Pro-gramacion Orientada a Objetos es todo acerca de polimorfismo.”Esta declaracion puede producir el sindrome ”dos anos de edad, conun martillo” (todo se ve como un clavo). Dicho de otra manera, esbastante dıficil ”obtener” polimorfismo, y una vez que lo hace, trate

184

Page 195: Traducción Thinking in Python

de emitir todos sus disenos en un molde particular.

¿Que patrones de diseno dicen que la Programacion Orientada aObjetos no se trata solo de polimorfismo?. Esto se trata de ”la sep-aracion de cosas que cambian de los objetos que permanecen igual.”Polimorfismo es una manera especialmente importante para haceresto, y resulta ser util si el lenguaje de programacion apoya direc-tamente el polimorfismo (por lo que no tiene que conectarlo ustedmismo, lo que tenderıa a hacer que sea prohibitivamente caro). Perolos patrones de diseno en general muestran otras maneras de lograrel objetivo basico, y una vez que sus ojos se han abierto a esto ustedcomenzara a buscar mas disenos creativos.

Desde que el libro Design Patterns salio e hizo tal impacto, lagente ha estado buscando otros patrones. Usted puede esperar vermas de estos aparecer con el tiempo. Estos son algunos sitios re-comendados por by Jim Coplien, de fama C ++ (http://www.bell-labs.com/ cope), que es uno de los principales promotores del movimientode los patrones:

http://st-www.cs.uiuc.edu/users/patternshttp://c2.com/cgi/wikihttp://c2.com/pprhttp://www.bell-labs.com/people/cope/Patterns/Process/index.htmlhttp://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatternshttp://st-www.cs.uiuc.edu/cgi-bin/wikic/wikichttp://www.cs.wustl.edu/ schmidt/patterns.htmlhttp://www.espinc.com/patterns/overview.html

Tambien tenga en cuenta que ha habido una conferencia anualsobre los patrones de diseno, llamada PLOP, que produce unas actaspublicadas, la tercera de las cuales salieron a finales de 1997 (todaspublicadas por Addison-Wesley).

185

Page 196: Traducción Thinking in Python

Ejercicios

1. Anade la clase Plastic a TrashVisitor.py

2. Anade la clase Plastic a DynaTrash.py

3. Crear un decorador como VisitableDecorator, pero para elejemplo de despacho multiple, junto con una clase ”decorador adap-tador ” como la creada para VisitableDecorator. Construir elresto del ejemplo y demostrar que funciona.

186

Page 197: Traducción Thinking in Python

13 : Proyectos

Una serie de proyectos mas desafiantes para que usted pueda re-solver.[[Algunos de estos pueden convertirse en ejemplos en el libro, por loque en algun momento podrıa desaparecer de aquı]]

Ratas y Laberintos

Primero, crea un Blackboard(citar esta referencia) que es un objeto sobre el que cualquier personapuede registrar la informacion. Este Blackboard particular dibujaun laberinto, y es usado como informacion que vuelve sobre la es-tructura de un laberinto desde las ratas que lo estan buscando.

Ahora cree el propio laberinto. Como un laberinto real, este ob-jeto revela muy poca informacion sobre si mismo dada una coorde-nada, que le dira si hay paredes o espacios en las cuatro direccionesinmediatamente que coordinan, pero no mas. Para empezar, lea ellaberinto desde un archivo de texto pero considere la busqueda eninternet para un algoritmo que genere un laberinto. En cualquiercaso, el resultado debe ser un objeto que, dado una coordenada dellaberinto, informara paredes y espacios alrededor de esa coordenada.Ademas, debe ser capaz de preguntar por un punto de entrada allaberinto.

Finalmente, crear la clase Rat laberinto-buscar. Cada rata puedecomunicarse tanto con el Blackboard para dar la informacion actualy el laberinto para solicitar neva informacion sobre la base de laposicion actual de la rata. Sin embargo, cada vez que una rata llegaa un punto de decision donde se ramifica el laberinto, crea una nuevarata que baja por cada una de las ramas. Cada rata es conducidapor su propio hilo. Cuando una rata llega a un callejon sin salida,termina en sı despues de informar los resultados de su busqueda fi-nal al Blackboard.

El objetivo es trazar un mapa completo del laberinto, pero tambienusted debe determinar si la condicion final sera encontrada natural-mente o si el blackboard debe ser responsable de la decision.

187

Page 198: Traducción Thinking in Python

Un ejemplo de implementacion de Jeremy Meyer:

# c13 : Maze . py

c l a s s Maze( Canvas ) :p r i v a t e Vector l i n e s # a l i n e i s a char arrayp r i v a t e i n t width = −1p r i v a t e i n t he ight = −1pub l i c s t a t i c void main ( St r ing [ ] a rgs )throws IOException :

i f ( a rgs . l ength < 1 ) :p r i n t ‘ ‘ Enter f i l ename ”System . e x i t (0 )

Maze m = Maze ( )m. load ( args [ 0 ] )Frame f = Frame ( )f . s e t S i z e (m. width ∗20 , m. he ight ∗20)f . add (m)Rat r = Rat (m, 0 , 0)f . s e t V i s i b l e (1 )

de f i n i t ( s e l f ) :l i n e s = Vector ( )setBackground ( Color . l i ghtGray )

synchron ized pub l i c booleanisEmptyXY( i n t x , i n t y ) :

i f ( x < 0) x += widthi f ( y < 0) y += he ight

# Use mod ar i thmet i c to br ing ra t in l i n e :byte [ ] by =

( byte [ ] ) ( l i n e s . elementAt ( y%he ight ) )re turn by [ x%width ]==’ ’

synchron ized pub l i c voidsetXY ( i n t x , i n t y , byte newByte ) :

i f ( x < 0) x += widthi f ( y < 0) y += he ight

188

Page 199: Traducción Thinking in Python

byte [ ] by =( byte [ ] ) ( l i n e s . elementAt ( y%he ight ) )

by [ x%width ] = newByter epa in t ( )

pub l i c voidload ( S t r ing f i l ename ) throws IOException :

S t r ing cur rentL ine = n u l lBufferedReader br = BufferedReader (

Fi leReader ( f i l ename ) )f o r ( cur rentL ine = br . readLine ( )

cur rentL ine != n u l lcur rentL ine = br . readLine ( ) ) :

l i n e s . addElement ( cur rentL ine . getBytes ( ) )i f ( width < 0 | |

cur rentL ine . getBytes ( ) . l ength > width )width = currentL ine . getBytes ( ) . l ength

he ight = len ( l i n e s )br . c l o s e ( )

de f update ( s e l f , Graphics g ) : pa int ( g )pub l i c void pa int ( Graphics g ) :

i n t canvasHeight = s e l f . getBounds ( ) . he ighti n t canvasWidth = s e l f . getBounds ( ) . widthi f ( he ight < 1 | | width < 1)

re turn # nothing to doi n t width =

( ( byte [ ] ) ( l i n e s . elementAt ( 0 ) ) ) . l engthf o r ( i n t y = 0 y < l en ( l i n e s ) y++):

byte [ ] bb = ( byte [ ] ) ( l i n e s . elementAt ( y ) )f o r ( i n t x = 0 x < width x++):

switch (b [ x ] ) :case ’ ’ : # empty part o f maze

g . s e tCo lo r ( Color . l i ghtGray )g . f i l l R e c t (

x∗( canvasWidth/width ) ,y∗( canvasHeight / he ight ) ,

189

Page 200: Traducción Thinking in Python

canvasWidth/width ,canvasHeight / he ight )

breakcase ’∗ ’ : # a wal l

g . s e tCo lo r ( Color . darkGray )g . f i l l R e c t (

x∗( canvasWidth/width ) ,y∗( canvasHeight / he ight ) ,( canvasWidth/width )−1 ,( canvasHeight / he ight )−1)

breakd e f a u l t : # must be ra t

g . s e tCo lo r ( Color . red )g . f i l l O v a l ( x∗( canvasWidth/width ) ,y∗( canvasHeight / he ight ) ,canvasWidth/width ,canvasHeight / he ight )break

# : ˜# c13 : Rat . py

c l a s s Rat :s t a t i c i n t ratCount = 0p r i v a t e Maze pr i s onp r i v a t e i n t ve r tDi r = 0p r i v a t e i n t ho r i zD i r = 0p r i v a t e i n t x , yp r i v a t e i n t myRatNo = 0de f i n i t ( s e l f , Maze maze , i n t xStart , i n t

yStart ) :myRatNo = ratCount++pr in t (” Rat no . ” + myRatNo +

” ready to scur ry . ” )p r i s on = mazex = xStarty = yStartp r i s on . setXY (x , y , ( byte ) ’R’ )Thread ( ) :

190

Page 201: Traducción Thinking in Python

de f run ( s e l f ) s cur ry ( ). s t a r t ( )

de f s cur ry ( s e l f ) :# Try and maintain d i r e c t i o n i f p o s s i b l e .# Hor i zonta l backwardboolean ratCanMove = 1whi le ( ratCanMove ) :

ratCanMove = 0

# Southi f ( p r i s on . isEmptyXY(x , y + 1 ) ) :

ve r tDi r = 1 hor i zD i r = 0ratCanMove = 1

# Northi f ( p r i s on . isEmptyXY(x , y − 1) )

i f ( ratCanMove )Rat ( pr i son , x , y−1)# Rat can move already , so g ive# t h i s cho i c e to the next ra t .

e l s e :ve r tDi r = −1 hor i zD i r = 0ratCanMove = 1

# Westi f ( p r i s on . isEmptyXY(x−1, y ) )

i f ( ratCanMove )Rat ( pr i son , x−1, y )# Rat can move already , so g ive# t h i s cho i c e to the next ra t .

e l s e :ve r tDi r = 0 hor i zD i r = −1ratCanMove = 1

# Easti f ( p r i s on . isEmptyXY( x+1, y ) )

i f ( ratCanMove )Rat ( pr i son , x+1, y )

191

Page 202: Traducción Thinking in Python

# Rat can move already , so g ive# t h i s cho i c e to the next ra t .

e l s e :ve r tDi r = 0 hor i zD i r = 1ratCanMove = 1

i f ( ratCanMove ) : # Move o r i g i n a l ra t .x += hor i zD i ry += vertDi rp r i s on . setXY (x , y , ( byte ) ’R’ )# I f not then the ra t w i l l d i e .

t ry :Thread . s l e e p (2000)

catch ( Inter ruptedExcept ion i e ) :

p r i n t (” Rat no . ” + myRatNo +” can ’ t move . . dying . . aarrgggh . ” )

# : ˜

El archivo de inicializacion de laberinto:

Otros Recursos para Laberinto

Una discusion de algoritmos para crear laberintos ası como el codigofuente de Java para implementarlas :

192

Page 203: Traducción Thinking in Python

http://www.mazeworks.com/mazegen/mazegen.htm

Una discusion de algoritmos para la deteccion de colisiones yotros comportamientos de movimiento individual/grupal de los ob-jetos fısicos autonomos:

http://www.red3d.com/cwr/steer/

Decorador XML

Crear un par de decoradores para I/O Los lectores y escritores quecodifican (para el decorador Escritor) y decodificacion XML.

193