Pensando en Python PDF

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 198

Pensando

en
Python
Bruce Eckel
Presidente MindView,Inc.
September 23, 2016

Patrones de Diseño y resolución de problemas técnicos


Por favor observe que este documento esta en su forma inicial, y
aún queda mucho por hacer.

1
Contents
1 Prólogo 7

2 Introducción 8
2.1 El sı́ndrome Y2K . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Contexto y composición . . . . . . . . . . . . . . . . . . . . . . . 10

3 Un rápido curso para programadores 11


3.1 Visión General de Python . . . . . . . . . . . . . . . . . . . . . . 11
3.1.1 Construido en contenedores . . . . . . . . . . . . . . . . . 12
3.1.2 Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.3 Cadenas . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1.4 Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

4 El concepto Patrón 20
4.1 ¿Qué es un Patrón? . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2 Taxonomı́a Patrón . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3 Estructuras Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.4 Criterios de Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.5 Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.6 Clasificación de Patrones . . . . . . . . . . . . . . . . . . . . . . . 30
4.7 El desafı́o para el desarrollo . . . . . . . . . . . . . . . . . . . . . 31
4.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

5 2: Pruebas Unitarias 32
5.1 Escribir pruebas primero . . . . . . . . . . . . . . . . . . . . . . . 34
5.2 Simples pruebas de Python . . . . . . . . . . . . . . . . . . . . . 35
5.3 Un framework muy simple . . . . . . . . . . . . . . . . . . . . . . 36
5.4 Escribir pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.5 Pruebas de caja blanca y caja negra . . . . . . . . . . . . . . . . 41
5.6 Ejecución de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.7 Ejecutar Pruebas Automáticamente . . . . . . . . . . . . . . . . 48
5.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

6 3: Entornos de aplicaciones de construcción 48


6.1 Método Plantilla . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

7 4: Al frente de una implementación 50


7.1 Proxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.2 State : Estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7.3 StateMachine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
7.4 Table-Driven State Machine . . . . . . . . . . . . . . . . . . . . . 64
7.4.1 La clase State . . . . . . . . . . . . . . . . . . . . . . . . . 66
7.4.2 Condiciones para la transición . . . . . . . . . . . . . . . 66
7.4.3 Acciones de transición . . . . . . . . . . . . . . . . . . . . 67

2
7.5 La tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.5.1 La máquina básica . . . . . . . . . . . . . . . . . . . . . . 68
7.6 Simple máquina expendedora . . . . . . . . . . . . . . . . . . . . 69
7.7 Prueba de la máquina . . . . . . . . . . . . . . . . . . . . . . . . 73
7.8 Herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74

8 X: Decoradores:
Selección Tipo dinámico 76
8.1 Estructura Decorador basico . . . . . . . . . . . . . . . . . . . . . 77
8.2 Un ejemplo café . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.3 Clase para cada combinación . . . . . . . . . . . . . . . . . . . . 77
8.4 El enfoque decorador . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.5 Compromiso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.6 Otras consideraciones . . . . . . . . . . . . . . . . . . . . . . . . 86
8.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87

9 Y: Iteradores:
Algoritmos de desacoplamiento de contenedores 88
9.1 Iteradores con seguridad de tipos . . . . . . . . . . . . . . . . . . 89

10 5: Fábricas:
encapsular
la creación de objetos 90
10.1 Simple método de fábrica . . . . . . . . . . . . . . . . . . . . . . 91
10.2 Fábricas polimórficas . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.3 Fábricas abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . 96
10.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100

11 6 : Objetos de función 101


11.1 Comando: la elección de la operación en tiempo de ejecución . . 101
11.2 Estrategia: elegir el algoritmo en tiempo de ejecución . . . . . . . 103
11.3 Cadena de responsabilidad . . . . . . . . . . . . . . . . . . . . . . 106
11.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110

12 7: Cambiando la interfaz. 110


12.1 Adapter : Adaptador . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.2 Façade : Fachada . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
12.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

13 Código Tabla impulsada:


flexibilidad de configuración 114

14 Devoluciones de Llamada 115


14.1 Observer : Observador . . . . . . . . . . . . . . . . . . . . . . . . 115
14.1.1 Observando Flores . . . . . . . . . . . . . . . . . . . . . . 118
14.2 Un ejemplo visual de Observadores . . . . . . . . . . . . . . . . . 127

3
14.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

15 11 : Despacho Múltiple 133


15.1 Visitor, un tipo de despacho múltiple . . . . . . . . . . . . . . . . 139
15.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141

16 12 : Patrón Refactorización 142


16.1 Simulando el reciclador de basura . . . . . . . . . . . . . . . . . . 142
16.2 Mejorando el diseño . . . . . . . . . . . . . . . . . . . . . . . . . 147
16.2.1 “Hacer más objetos” . . . . . . . . . . . . . . . . . . . . . 147
16.3 Un patrón para la creación de prototipos . . . . . . . . . . . . . . 151
16.4 Subclases Trash . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
16.5 Analizar Trash desde un archivo externo . . . . . . . . . . . . . 157
16.6 Reciclaje con prototipos . . . . . . . . . . . . . . . . . . . . . . . 160
16.7 Haciendo abstracción de uso . . . . . . . . . . . . . . . . . . . . . 162
16.8 Despacho múltiple . . . . . . . . . . . . . . . . . . . . . . . . . . 167
16.8.1 La implementación del doble despacho . . . . . . . . . . . 169
16.9 El patrón Visitor : visitante . . . . . . . . . . . . . . . . . . . . . 176
16.10Un decorador reflexivo . . . . . . . . . . . . . . . . . . . . . . . . 180
16.10.1 Más acoplamiento? . . . . . . . . . . . . . . . . . . . . . . 186
16.11RTTI considerado dañino? . . . . . . . . . . . . . . . . . . . . . . 186
16.12Resumen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
16.13Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192

17 Proyectos 192
17.1 Ratas y Laberintos . . . . . . . . . . . . . . . . . . . . . . . . . 192
17.1.1 Otros Recursos para Laberinto . . . . . . . . . . . . . . . 198
17.2 Decorador XML . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

4
La composición de esta traducción se realizó utilizando LATEX, (gracias al
editor online ShareLatex)1 . El lector es totalmente libre de hacer correcciones
(en caso de algún error de sintaxis en la traslación de inglés a español) dentro
de la traducción en beneficio de la comunidad.

Adicionalmente, el autor del libro tiene la siguiente opinión frente a la tra-


ducción:

“Supongo que si eso es útil para usted. Ese libro nunca fue terminado y lo
que yo escribirı́a ahora es muy diferente. Pero si funciona para usted, eso está
bien.”

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

1 www.sharelatex.com

5
Grupo de trabajo académico GNU/Linux Universidad Distrital.
Semillero de Investigación en Tecnologı́a Libre.
Traducido por :
Leidy Marcela Aldana Burgos. (Estudiante de Ingenierı́a de
Sistemas)
Código Académico : 20151020019
Bogotá, Colombia.

Agradecimientos
Se reconoce la colaboración para las respectivas correcciones de esta traducción
hechas por:

Omar Leonardo Zambrano (Estudiante de Ingenierı́a Electrónica)

Diego Andrés Osorio Gutiérrez (Estudiante de Ingenierı́a de Sistemas)

6
1 Prólogo
El material en este libro inició en conjunción con el seminario de Java
que yo he dado por varios años, un par de veces con Larry O’Brien,
luego con Bill Venners. Bill y yo hemos dado muchas repeticiones de
este seminario y lo hemos cambiado a través de los años ya que am-
bos hemos aprendido más acerca de patrones y sobre dar el seminario.

En el proceso, ambos hemos producido información más que suficiente para


que cada uno de nosotros tengamos nuestros propios seminarios, un impulso que
ambos fuertemente hemos resistido porque nos hemos divertido mucho dando
juntos el seminario. Hemos dado el seminario en numerosos lugares de Es-
tados Unidos, ası́ como en Praga (donde nosotros intentamos tener una mini-
conferencia cada primavera a la vez con un número de otros seminarios). Hemos
dado ocasionalmente un seminario en el lugar, pero esto es caro y difı́cil de pro-
gramar porque nosotros somos dos.

Muchos agradecimientos a las personas que han participado en estos semi-


narios en los últimos años, y a Larry y a Bill, ya que me han ayudado a trabajar
mediante estas ideas y perfeccionarlas. Espero ser capaz de continuar para for-
mar y desarrollar este tipo de ideas a través de este libro y el seminario durante
muchos años por venir.

Este libro no parará aquı́, tampoco. Originalmente, este material era parte
de un libro de C++, luego de un libro de Java, entonces este fue separado de su
propio libro basado en Java, y finalmente después de mucho examinar, decidı́
que la mejor manera para crear inicialmente mi escrito de patrones de diseño es
escribir primero esto en Python (ya que sabemos que Python hace un lenguaje
ideal de prototipos!) y luego traducir las partes pertinentes del libro de nuevo
en la versión de Java. He tenido la experiencia antes de emitir una idea en
un lenguaje más potente, luego traducir de nuevo en otro lenguaje, y he encon-
trado que esto es mucho más fácil para obtener información y tener la idea clara.

Ası́ que Pensando en Python es, inicialmente, una traducción de Thinking in


Patterns with Java, en lugar de una introducción a Python (ya hay un montón
de introducciones finas para este espléndido lenguaje). Me parece que este
prospecto es mucho más emocionante que la idea de esforzarse a través de otro
tutorial del lenguaje (mis disculpas a aquellos que estaban esperando para esto).

7
2 Introducción
Este es un libro sobre el proyecto en el que he estado trabajando du-
rante años, ya que básicamente nunca empecé a tratar de leer Design
Patterns (Patrones de Diseño) (Gamma,Helm,Johnson y Vlissides,
Addison-Wesley, 1995), comúnmente conocida como Gang of Four 2
o solo GoF).

Hay un capı́tulo sobre los patrones de diseño en la primera edición de Think-


ing in C++, que ha evolucionado en el Volumen 2 de la segunda edición de
Thinking in C++ y usted también encontrará un capı́tulo sobre los patrones en
la primera edición de Thinking in Java. Tomé ese capı́tulo de la segunda edición
de Thinking in Java porque ese libro fue creciendo demasiado, y también porque
yo habı́a decidido escribir Thinking in Patterns. Ese libro, aún no se ha ter-
minado, se ha convertido en éste. La facilidad de expresar estas ideas más
complejas en Python, creo que, finalmente, me permitirá decirlo todo.

Este no es un libro introductorio. Estoy asumiendo que usted ha traba-


jado su camino a través de por lo menos Learning Python (por Mark Lutz y
David Ascher; OReilly, 1999) o un texto equivalente antes de empezar este libro.

Además, Supongo que tiene algo más que una comprensión de la sintaxis de
Python. Usted debe tener una buena comprensión de los objetos y lo que son
ellos, incluyendo el polimorfismo.

Por otro lado, al pasar por este libro vas a aprender mucho acerca de la
programación orientada a objetos al ver objetos utilizados en muchas situaciones
diferentes. Si su conocimiento de objetos es elemental, este se consiguirá mucho
más fuerte en el proceso de comprensión de los diseños en este libro.

2 Esta es una referencia irónica para un evento en China después de la muerte de Mao

Tze Tung, cuando cuatro personas incluyendo la viuda de Mao hicieron un juego de poder, y
fueron demonizados por el Partido Comunista de China bajo ese nombre.

8
2.1 El sı́ndrome Y2K
En un libro que tiene “técnicas de resolución de problemas” en su subtitulo,
vale la pena mencionar una de las mayores dificultades encontradas en la pro-
gramación: la optimización prematura. Cada vez traigo este concepto hacia
adelante, casi todo el mundo está de acuerdo en ello. Además, todo el mundo
parece reservar en su propia mente un caso especial “a excepción de esto que
me he enterado es un problema particular.”

La razón por la que llamo a esto el sı́ndrome Y2K debo hacerlo con ese
especial conocimiento. Los computadores son un misterio para la mayorı́a de
la gente, ası́ que cuando alguien anunció que los tontos programadores de com-
putadoras habı́an olvidado poner suficientes dı́gitos para mantener las fechas
más allá del año 1999, de repente todo el mundo se convirtió en un experto
en informática ”estas cosas no son tan difı́cil después de todo, si puedo ver
un problema tan obvio.” Por ejemplo, mi experiencia fue originalmente en in-
genierı́a informática, y empecé a cabo mediante la programación de sistemas
embebidos. Como resultado, sé que muchos sistemas embebidos no tienen idea
que fecha u hora es, e incluso si lo hacen esos datos a menudo no se utilizan
en los cálculos importantes. Y sin embargo, me dijeron en términos muy claros
que todos los sistemas embebidos iban a bloquearse en 01 de enero 2000 3 .En
lo que puedo decir el único recuerdo que se perdió en esa fecha en particular
fue el de las personas que estaban prediciendo pérdida – que es como si nunca
hubieran dicho nada de eso.

El punto es que es muy fácil caer en el hábito de pensar que el algoritmo


particular o el trozo de código que usted por casualidad entiende en parte o
totalmente, es naturalmente, será el estancamiento en su sistema, simplemente
porque puede imaginar lo que está pasando en ese trozo de código y ası́, que
usted piensa que debe ser de alguna manera mucho menos eficiente que el resto
de piezas de código que usted no conoce. Pero a menos que haya ejecutado las
pruebas reales, tı́picamente con un perfilador, realmente no se puede saber lo
que está pasando. E incluso si usted tiene razón, que una pieza de código es
muy ineficiente, recuerde que la mayorı́a de los programas gastan algo ası́ como
90% de su tiempo en menos de 10% del código en el programa, ası́ que a menos
que el trozo de código que usted está pensando sobre lo que sucede al caer en
ese 10% no va a ser importante.

”Optimización prematura es la raı́z de todo mal.” se refiere a veces como


”La ley de Knuth” (de Donald E. Knuth).
3 Estas mismas personas también estaban convencidos de que todos los ordenadores iban

a bloquearse a continuación, también. Pero como casi todo el mundo tenı́a la experiencia de
su máquina Windows estrellándose todo el tiempo sin resultados particularmente graves, esto
no parece llevar el mismo drama de la catástrofe inminente.

9
2.2 Contexto y composición
Uno de los términos que verá utilizar una y otra vez en literatura de patrones
de diseño es context. De hecho, una definición común de un patrón de diseño
es: ”Una solución a un problema en un contexto.” Los patrones GoF a menudo
tienen un ”objeto de contexto” que el programador interactúa con el cliente. En
cierto momento se me ocurrió que dichos objetos parecı́an dominar el paisaje
de muchos patrones, y ası́ comencé preguntando de qué trataban.

El objeto de contexto a menudo actúa como una pequeña fachada para ocul-
tar la complejidad del resto del patrón, y, además, a menudo será el controlador
que gestiona el funcionamiento del patrón. Inicialmente, me parecı́a que no era
realmente esencial para la implementación, uso y comprensión del patrón. Sin
embargo, Recordé una de las declaraciones más dramáticas realizadas en el GoF:
“Preferirı́a composición a la herencia.” El objeto de contexto le permite utilizar
el patrón en una composición, y eso puede ser su valor principal.

10
3 Un rápido curso para programadores
Este libro asume que usted es un programador experimentado, y es
mejor si usted ha aprendido Python a través de otro libro. Para todos
los demás, este capitulo da una rápida introducción al lenguaje.

3.1 Visión General de Python


Esta breve introducción es para el programador experimentado (que es lo que
usted deberı́a ser si esta leyendo este libro). Usted puede atribuir a la docu-
mentación completa de www.Python.org (especialmente a la página HTML in-
creı́blemente útil A Python Quick Reference), y también numerosos libros como
Learning Python por Mark Lutz y David Ascher(O’Reilly, 1999).

Python se conoce a menudo como un lenguaje de script, pero los lenguajes


de script tienden a estar limitando, especialmente en el ámbito de los problemas
que ellos resuelven. Python, por otro lado, es un lenguaje de programación que
también soporta Scripting. Es maravilloso para scripting, y puede encontrar
usted mismo la sustitución de todos sus archivos por lotes, scripts de shell, y
programas sencillos con scripts de Python. Pero es mucho más que un lenguaje
de script.

Python está diseñado para ser muy limpio para escribir y especialmente para
leer. Usted encontrará que es muy fácil leer su propio código mucho después de
que lo ha escrito, y también para leer el código de otras personas. Esto se logra
parcialmente a través de la sintaxis limpia, al punto, pero un factor mayor en la
legibilidad del código es la identación – la determinación del alcance en Python
viene determinada por la identación. Por ejemplo:

#: c01 : i f . py
response = ” yes ”
i f r e s p o n s e == ” y e s ” :
print ” affirmative ”
val = 1
print ” continuing . . . ”
#:˜
El ‘#’ denota un comentario que va hasta el final de la linea, al igual que
C++ y Java ‘//’ comenta.

La primera noticia es que la sintaxis básica de Python es C-ish como se


puede ver en la declaración if. Pero en C un if, se verá obligado a utilizar
paréntesis alrededor del condicional, mientras que no son necesarios en Python
(no reclamará si los usa de todas formas).

11
La cláusula condicional termina con dos puntos, y esto indica que lo que
sigue será un grupo de sentencias identadas, que son la parte “entonces” de la
sentencia if. En este caso hay una declaración de “imprimir” el cual envı́a el
resultado a la salida estándar, seguido de una asignación a una variable llamada
val. La declaración posterior no esta identada ası́ ya no es parte del if. Iden-
tando puede anidar a cualquier nivel, al igual que los corchetes en C ++ o Java,
pero a diferencia de esos lenguajes no hay ninguna opción (y ningún argumento)
acerca de dónde se colocan los corchetes –el compilador obliga el código de cada
uno para ser formateado de la misma manera, lo cual es una de las principales
razones de legibilidad consistente de Python.

Python normalmente tiene sólo una declaración por lı́nea (se puede poner
más separándolos con punto y coma), por lo que el punto y coma de terminación
no es necesario. Incluso desde el breve ejemplo anterior se puede ver que el
lenguaje está diseñado para ser tan simple como sea posible, y sin embargo
sigue siendo muy legible.

3.1.1 Construido en contenedores


Con lenguajes como C++ y Java, los contenedores son añadidos en las librerias
y no integros al lenguaje. En Python, la naturaleza esencial de los contenedores
para la programación es reconocido por su construcción en el núcleo del lenguaje:
ambas listas y matrices (: arrays) asociativas (mapas alias, diccionarios, tablas
hash) son tipos de datos fundamentales. Esto añade mucho a la elegancia del
lenguaje.

Además, la declaración for itera automáticamente a través de las listas y


no sólo contando a través de una secuencia de números. Tiene mucho sentido
cuando se piensa en esto, ya que casi siempre está utilizando un bucle for para
recorrer una matriz o un contenedor. Python formaliza esto automáticamente
haciendo uso de for un iterador que funciona a través de una secuencia. Aquı́
esta un ejemplo:

#: c01 : l i s t . py
l i s t = [ 1 , 3 , 5 , 7 , 9 , 11 ]
print l i s t
l i s t . append ( 1 3 )
for x in l i s t :
print x
#:˜
La primera linea crea una lista. Puede imprimir la lista y esto mostrará ex-
actamente como usted pone esto (en contraste, recuerde que yo tuve que crear
una clase especial Arrays2 en Thinking in Java, 2da Edición en orden para im-
primir arrays en Java). Las listas son como contenedores de Java – usted puede

12
añadir elementos nuevos a ellos (aquı́, es usado append()) y van a cambiar
automáticamente el tamaño de sı́ mismos. La sentencia for crea un iterador x
que toma cada valor de la lista.

Usted puede crear una lista de números con la función range(),ası́ que si
usted realmente necesita imitar de C for, se puede.

Tenga en cuenta que no hay declaraciones de tipo –los nombres de los obje-
tos aparecen simplemente, y Python infiere su tipo por la forma en que se usan.
Es como si Python está diseñado para que usted sólo necesite pulsar las teclas
que sean absolutamente necesarias. Usted encontrará después de haber traba-
jado con Python por un corto tiempo que usted ha estado utilizando una gran
cantidad de ciclos cerebrales analizando punto y coma, corchetes, y todo tipo
de otra palabrerı́a adicional que fue exigido por su lenguaje de programación
diferente de Python pero no describe en realidad lo que se suponı́a que hiciera
su programa.

3.1.2 Funciones
Para crear una función en Python, use la palabra clave def, seguido por el nom-
bre de la función y la lista de argumentos, y dos puntos para empezar el cuerpo
de la función. Aquı́ esta el primer ejemplo convertido en una función:

#: c01 : myFunction . py
d e f myFunction ( r e s p o n s e ) :
val = 0
i f r e s p o n s e == ” y e s ” :
print ” affirmative ”
val = 1
print ” continuing . . . ”
return val

p r i n t myFunction ( ” no ” )
p r i n t myFunction ( ” y e s ” )
#:˜
Observe que no hay información de tipo en la firma de la función – todo lo
que se especifica es el nombre de la función y los identificadores de argumentos,
pero no los tipos de argumentos o tipo que devuelve. Python es un lenguaje
debilmente tipado, lo que significa que pone los requisitos mı́nimos posibles en
la introducción de caracteres. Por ejemplo, usted podrı́a pasar y devolver difer-
entes tipos desde la misma función:

13
#: c01 : d i f f e r e n t R e t u r n s . py
def d i f f e r e n t R e t u r n s ( arg ) :
i f a r g == 1 :
r e t u r n ” one ”
i f a r g == ” one ” :
return 1
print differentReturns (1)
p r i n t d i f f e r e n t R e t u r n s ( ” one ” )
#:˜
Las únicas limitaciones sobre un objeto que se pasa a la función, son que
la función puede aplicar sus operaciones a ese objeto, pero aparte de eso, no
importa. Aquı́, la misma función aplica el operador ‘+’para enteros y cadenas:

#: c01 : sum . py
d e f sum ( arg1 , a r g 2 ) :
return arg1 + arg2
p r i n t sum ( 4 2 , 4 7 )
p r i n t sum ( ’ spam ’ , ” e g g s ” )
#:˜
Cuando el operador ’+’ es usado con cadenas, esto significa concatenación,
(si, Python soporta la sobrecarga de operadores, y esto hace un buen trabajo
del mismo).

3.1.3 Cadenas
El ejemplo anterior también muestra un poco sobre manejo de cadenas de
Python, 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 usted
rodea una cadena con comillas dobles puede incluir comillas simples y viceversa:

#: c01 : s t r i n g s . py
p r i n t ”That i s n ’ t a h o r s e ”
p r i n t ’ You a r e not a ” V i k i n g ” ’
p r i n t ”””You ’ r e j u s t pounding two
coconut halves to g e t he r .”””
p r i n t ’ ’ ’ ”Oh no ! ” He e x c l a i m e d .
” I t ’ s t h e 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 después de la serpiente, sino
más bien la compañı́a de comedia Monty Python, y ası́ los ejemplos están
prácticamente obligados a incluir referencias Python-esque.

14
La triple cita de sintaxis cita todo, incluyendo saltos de lı́nea. Esto hace
que sea especialmente útil para hacer las cosas como la generación de páginas
web (Python es un lenguaje CGI especialmente bueno), ya que usted puede sólo
triple citar la página completa que desee sin ninguna otra edición.

La ‘r’ justo antes significa una cadena “raw : en bruto”, que toma las bar-
ras invertidas : \\, literalmente ası́ que usted no tiene que poner en una barra
inversa extra a fin de insertar una barra invertida literal.

La sustitución en cadenas es excepcionalmente fácil, ya que Python usa de


C la sintaxis de sustitución printf(), pero para cualquier cadena en absoluto.
Usted simplemente sigue la cadena con un ‘%’ y los valores para sustituir:

#: c01 : s t r i n g F o r m a t t i n g . py
v a l = 47
p r i n t ”The number i s %d” % v a l
val2 = 63.4
s = ” v a l : %d , v a l 2 : %f ” % ( val , v a l 2 )
print s
#:˜
Como se puede ver en el segundo caso, si usted tiene más de un argumento
entre parentesis (esto forma una tupla, que es una lista que no puede ser mod-
ificado – también puede utilizar las listas regulares para múltiples argumentos,
pero tuplas son tı́picas).

Todo el formato de printf() es válido, incluyendo el control sobre el lugar


y alineación de números decimales. Python también tiene expresiones regulares
muy sofisticadas.

15
3.1.4 Clases
Como todo lo demás en Python, la definición de una clase usa una mı́nima sin-
taxis adicional. Usted usa la palabra clave class, y en el interior del cuerpo se
utiliza def para crear métodos. Aquı́ está una clase simple:

#: c01 : S i m p l e C l a s s . py
c l a s s Simple :
def i n i t ( self , str ):
p r i n t ” I n s i d e t h e Simple c o n s t r u c t o r ”
self . s = str
# Two methods :
d e f show ( s e l f ) :
print s e l f . s
d e f showMsg ( s e l f , msg ) :
p r i n t msg + ’ : ’ ,
s e l f . show ( ) # C a l l i n g a n o t h e r method

if name == ” m a i n ” :
# C r e a t e an o b j e c t :
x = Simple ( ” c o n s t r u c t o r argument ” )
x . show ( )
x . showMsg ( ”A message ” )
#:˜
Ambos métodos tienen “self” como su primer argumento. C++ y Java am-
bos tienen un primer argumento oculto en sus métodos de clase, el cual apunta
al objeto para el método que fue llamado y se puede acceder usando la pal-
abra clave this. Los métodos de Python también utilizan una referencia al
objeto actual, pero cuando usted está definiendo un método debe especificar
explı́citamente la referencia como el primer argumento. Tradicionalmente, la
referencia se llama self pero usted podrı́a utilizar cualquier identificador que
desee (si usted no utiliza self probablemente confundirá a mucha gente, sin em-
bargo). Si necesita hacer referencia a campos en el objeto u otros métodos en el
objeto, debe utilizar self en la expresión. Sin embargo, cuando usted llama un
método para un objeto como en x.show(), no entrega la referencia al objeto –
que esta hecha para usted.

Aquı́, el primer método es especial, como lo es cualquier identificador que


comienza y termina con doble guión bajo. En este caso, define el constructor,
el cual es llamado automáticamente cuando se crea el objeto, al igual que en C
++ y Java. Sin embargo, en la parte inferior del ejemplo se puede ver que la
creación de un objeto se parece a una llamada a la función utilizando el nombre
de la clase. La sintaxis disponible de Python le hace darse cuenta de que la
palabra clave new no es realmente necesario en C ++, tampoco en Java.

16
Todo el código al fondo se pone en marcha por una cláusula if, la cual
comprueba para ver si algo llamado name es equivalente a main . De
nuevo, los dobles guiones bajos indican nombres especiales. La razón de if es
que cualquier archivo también puede ser utilizado como un módulo de librerı́a
dentro de otro programa (módulos se describen en breve). En ese caso, usted
sólo quiere las clases definidas, pero usted no quiere el código en la parte infe-
rior del archivo a ejecutar. Esta declaración en particular if sólo es verdadera
cuando está ejecutando este archivo directamente; eso es, si usted dice en la
lı́nea de comandos:

Python S i m p l e C l a s s . py \ n e w l i n e
Sin embargo, si este archivo se importa como un módulo en otro programa,
no se ejecuta el código main .

Algo que es un poco sorprendente al principio es que se define campos dentro


de los métodos, y no fuera de los métodos como C ++ o Java (si crea campos
utilizando el estilo de C ++ / Java, implı́citamente se convierten en campos
estáticos). Para crear un campo de objeto, sólo lo nombra – usando self –
dentro de uno de los métodos (usualmente en el constructor, pero no siempre),
y se crea el espacio cuando se ejecuta ese método. Esto parece un poco ex-
traño viniendo de C++ o Java donde debe decidir de antemano cuánto espacio
su objeto va a ocupar, pero resulta ser una manera muy flexible para programar.

Herencia

Porque Python es débilmente tipado, esto realmente no importa sobre inter-


faces – lo único que le importa es la aplicación de las operaciones a los objetos
(de hecho, la palabra clave interface de Java serı́a desperdiciada en Python).
Esto significa que la herencia en Python es diferente de la herencia en C++
o Java, donde a menudo se hereda simplemente para establecer una interfaz
común. En Python, la única razón por la que hereda es para heredar una im-
plementación – reutilizar el código de la clase base.

Si usted va a heredar de una clase, usted debe decirle a Python para traer
esa clase en el nuevo archivo. Python controla sus espacios de nombre tan
agresivamente como lo hace Java,y de manera similar (aunque con la incli-
nación de Python para la sencillez). Cada vez que se crea un archivo, se crea
implı́citamente un módulo (que es como un paquete en Java) con el mismo nom-
bre que el archivo. Por lo tanto, no se necesitó la palabra clave package en
Python. Cuando se desea utilizar un módulo, sólo dice import y da el nombre
del módulo. Python busca el PYTHONPATH del mismo modo que Java busca
el CLASSPATH (pero por alguna razón, Python no tiene el mismo tipo de di-
ficultades al igual que Java) y lee en el archivo. Para referirse a cualquiera de
las funciones o clases dentro de un módulo, usted le da el nombre del módulo,
un perı́odo, y el nombre de la función o clase. Si usted no quiere la molestia de

17
calificar el 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 paréntesis después del
nombre de la clase heredera. Tenga en cuenta que la clase Simple, la cual
reside en el archivo (y por lo tanto, el módulo) llamado SimpleClass se pone
en este nuevo espacio de nombres utilizando una sentencia import:

#: c01 : Simple2 . py
from S i m p l e C l a s s import Simple
c l a s s Simple2 ( Simple ) :
def i n i t ( self , str ):
p r i n t ” I n s i d e Simple2 c o n s t r u c t o r ”
# You must e x p l i c i t l y c a l l
# t h e base−c l a s s c o n s t r u c t o r :
Simple . i n i t ( self , str )
def display ( s e l f ) :
s e l f . showMsg ( ” C a l l e d from d i s p l a y ( ) ” )
# O v e r r i d i n g a base−c l a s s method
d e f show ( s e l f ) :
p r i n t ” Overridden show ( ) method”
# C a l l i n g a base−c l a s s method from i n s i d e
# t h e o v e r r i d d e n method :
Simple . show ( s e l f )
class Different :
d e f show ( s e l f ) :
p r i n t ”Not d e r i v e d from Simple ”
if name == ” m a i n ” :
x = Simple2 ( ” Simple2 c o n s t r u c t o r argument ” )
x . display ()
x . show ( )
x . showMsg ( ” I n s i d e main ” )
d e f f ( o b j ) : o b j . show ( ) # One−l i n e d e f i n i t i o n
f (x)
f ( Different ())
#:˜
Simple2 se hereda de Simple, y en el constructor, el constructor de la
clase base es llamado. En display(), showMsg() puede ser llamado como un
método de self, debe calificar por completo el nombre y pasar self como el
primer argumento, como se muestra en la llamada al constructor de la clase
base. Esto también puede verse en la versión anulada de show( ).

18
En main , usted puede ver (cuando corre el programa) que el construc-
tor de la clase base es llamado. También puede ver que el método showMsg()
es válido en las clases derivadas, del mismo modo que se puede esperar con la
herencia.

La clase Different también tiene un método llamado show(), pero esta


clase no es derivada de Simple. El método f() definido en main demuestra
tipificación débil: lo único que importa es que show() se puede aplicar a obj, y
no tiene ningún otro tipo de requisito. Usted puede ver que f() se puede aplicar
igualmente a un objeto de una clase derivada de Simple y uno que no lo es,
sin discriminación. Si usted es un programador de C++, deberı́a ver que el
objetivo de la función de C ++ template es exactamente esto: proporcionar
tipificación débil en un lenguaje fuertemente tipado. Por lo tanto, en Python
automáticamente obtendrá el equivalente de plantillas – sin tener que aprender
esa sintaxis y semántica particularmente difı́cil.

[[Sugerir Otros Temas para su inclusión en el capı́tulo introductorio]]

19
4 El concepto Patrón
“Los patrones de diseño ayudan a aprender de los éxitos de los demás
en lugar de sus propios fracasos”
4

Probablemente el avance más importante en el diseño orientado a objetos es


el movimiento “patrones de diseño”, crónica en Design Patterns : Diseño de pa-
trones (ibid) 5 Ese libro muestra 23 soluciones diferentes a las clases particulares
de problemas. En este libro, los conceptos básicos de los patrones de diseño se
introducirán junto con ejemplos. Esto deberı́a abrir su apetito para leer Design
Patterns : Diseño de patrones por Gamma, et. al., una fuente de lo que ahora
se ha convertido en un elemento esencial, casi obligatorio, vocabulario para los
programadores de programación orientada a objetos.

La última parte de este libro contiene un ejemplo del proceso de evolución del
diseño, comenzando con una solución inicial y moviéndose a través de la lógica
y el proceso de la evolución de la solución a los diseños más apropiados. El
programa mostrado (una simulación de clasificación de basura) ha evolucionado
con el tiempo, puede mirar en dicha evolución como un prototipo de la forma
en que su propio diseño puede comenzar como una solución adecuada a un
problema particular y evolucionar hacia un enfoque flexible para una clase de
problemas.

4.1 ¿Qué es un Patrón?


Inicialmente, usted puede pensar en un patrón como una forma especialmente
inteligente y perspicaz de la solución de una determinada clase de problemas. Es
decir, parece que muchas personas han trabajado todos los ángulos de un prob-
lema y han llegado con la solución más general y flexible para ello. El problema
podrı́a ser uno que ha visto y resuelto antes, pero su solución probablemente no
tenı́a el tipo de integridad usted verá incorporada en un patrón.

Aunque se les llama ”patrones de diseño,” ellos realmente no están atados


al ámbito del diseño. Un patrón parece estar al margen de la forma tradicional
de pensar en el análisis, diseño, e implementación. En lugar, un patrón encarna
una idea completa dentro de un programa, y por lo tanto a veces puede aparecer
en la fase de análisis o de la fase de diseño de alto nivel. Esto es interesante
porque un patrón tiene una aplicación directa en el código y por lo que podrı́a
no esperar que aparezca antes del diseño o implementación de bajo nivel (de
hecho, es posible que no se dé cuenta de que se necesita un patrón particular
hasta llegar a esas fases).

El concepto básico de un patrón también puede ser visto como el concepto


básico de diseño del programa: la adición de una capa de abstracción. Cuando
4 De Mark Johnson
5 Pero cuidado: los ejemplos están en C ++.

20
usted abstrae algo usted está aislando detalles particulares, y una de las moti-
vaciones más convincentes detrás de esto es separar las cosas que cambian de
cosas que se quedan igual. Otra manera de poner esto es que una vez usted
encuentra alguna parte de su programa que es probable que cambie por una
razón u otra, usted querrá mantener esos cambios con respecto a la propagación
de otros cambios a través de su código. Esto no sólo hace el código mucho más
económico de mantener, pero también resulta que por lo general es más simple
de entender (lo cual resulta en menores costes).

A menudo, la parte más difı́cil de desarrollar un diseño elegante y económico


de mantener, es en el descubrimiento de lo que yo llamo ”el vector del cambio.”
(Aquı́, ”vector” se refiere a la gradiente máxima y no una clase contenedora.)
Esto significa encontrar la cosa más importante que cambia en su sistema, o
dicho de otra manera, descubriendo donde su costo es mayor. Una vez que
descubra el vector del cambio, usted tiene el punto focal alrededor del cual es-
tructurar su diseño.

Ası́ que el objetivo de los patrones de diseño es aislar los cambios en su


código. Si se mira de esta manera, usted ha estado viendo algunos patrones de
diseño que ya están en este libro. Por ejemplo, la herencia puede ser pensado
como un patrón de diseño (aunque uno implementado por el compilador). Esto
le permite expresar diferencias del comportamiento (eso es lo que cambia) de los
objetos que todos tienen la misma interfaz (eso es lo que sigue siendo el mismo).
La composición también puede ser considerada un patrón, ya que le permite
cambiar — dinámica o estáticamente — los objetos que implementan la clase,
y por lo tanto la forma en que funciona la clase.

Otro patrón que aparece en Design Patterns es el iterador, el cual ha sido


implı́citamente válido en bucles for desde el comienzo del lenguaje, y fue intro-
ducido como una caracterı́stica explı́cita en Python 2.2. Un iterador le permite
ocultar la implementación particular de del contenedor como usted está pasando
a través de los elementos y seleccionando uno por uno. Ası́, puede escribir código
genérico que realiza una operación en todos los elementos en una secuencia sin
tener en cuenta la forma en que se construye la secuencia. Ası́, su código genérico
se puede utilizar con cualquier objeto que pueda producir un iterador.

4.2 Taxonomı́a Patrón


Uno de los acontecimientos que se produjeron con el aumento de patrones de
diseño es lo que podrı́a ser considerado como la “contaminación” del término –
la gente ha empezado a utilizar el término para definir casi cualquier cosa en
sinónimo de ”bueno”. Después de alguna ponderación, yo he llegado con una
especie de jerarquı́a que describe una sucesión de diferentes tipos de categorı́as:

21
1.Idioma:
Cómo escribimos código en un lenguaje particular, para hacer este tipo partic-
ular de cosas. Esto podrı́a ser algo tan común como la forma en que codifica el
proceso de paso a paso a través de una matriz en C (y no se salga del final).

2.Diseño Especifico:
la solución que se nos ocurrió para resolver este problema en particular. Esto
podrı́a ser un diseño inteligente, pero no intenta ser general.

3.Diseño Estándar:
una manera de resolver este tipo de problema. Un diseño que se ha vuelto más
general, tı́picamente a través de la reutilización.

4. Patrón de Diseño:
cómo resolver toda una clase de problema similar. Esto normalmente sólo
aparece después de la aplicación de un diseño estándar un número de veces,
y después de ver un patrón común a través de estas aplicaciones.

Siento que esto ayuda a poner las cosas en perspectiva, y para mostrar donde
algo podrı́a encajar. Sin embargo, esto no dice que uno es mejor que otro. No
tiene sentido tratar de tomar todas las soluciones de problemas y generalizarlas
a un patrón de diseño – no es un buen uso de su tiempo, y no se puede forzar el
descubrimiento de patrones de esa manera; ellos tienden a ser sutiles y aparecen
con el tiempo.

También se podrı́a argumentar a favor de la inclusión del Analysis Pattern


: Patrón Análisis y Architectural Pattern : Patrón arquitectónico en esta tax-
onomı́a.

22
4.3 Estructuras Diseño
Una de las luchas que he tenido con los patrones de diseño es su clasificación -
A menudo he encontrado el enfoque GoF a ser demasiado oscuro, y no siempre
muy servicial. Ciertamente, los patrones creacionales son bastante sencillos:
¿cómo se va a crear sus objetos? Esta es una pregunta que normalmente nece-
sita preguntarse, y el nombre que lleva directamente a ese grupo de patrones.
Pero encuentro Structural and Behavioral : Estructurales y de comportamiento
a ser distinciones mucho menos útiles. No he sido capaz de mirar un problema y
decir ”Claramente, se necesita un patrón estructural aquı́”, por lo que la clasifi-
cación no me lleva a una solución (Voy a admitir fácilmente que yo pueda estar
perdiendo algo aquı́).

He trabajado por un tiempo con este problema, primero señalando que la


estructura subyacente de algunos de los patrones GoF son similares entre sı́, y
tratando de desarrollar relaciones basadas en esa semejanza. Si bien este fue un
experimento interesante, no creo que produjo gran parte de su uso en el final,
porque el punto es resolver problemas, por lo que un enfoque útil se verá en
el problema a resolver y tratar de encontrar relaciones entre el problema y las
posibles soluciones.

Con ese fin, he empezado a intentar reunir las estructuras básicas de diseño,
y tratar de ver si hay una manera de relacionar aquellas estructuras a los diver-
sos patrones de diseño que aparecen en sistemas bien pensados. Corrientemente,
sólo estoy tratando de hacer una lista, pero eventualmente espero hacer pasos
hacia la conexión de estas estructuras con los patrones (o Puedo llegar con un en-
foque totalmente diferente – esta se encuentra todavı́a en su etapa de formación)

Aquı́ 6 está la lista actual de candidatos, solo algunos de los cuales llegarán
al final de la lista. Siéntase libre de sugerir otros, o posiblemente, las relaciones
con los patrones.

• Encapsulación: auto contención e incorporando un modelo de uso.


• Concurrencia
• Localización
• Separación
• Ocultación
• Custodiando
• Conector
• Obstáculo/valla
6 Esta lista incluye sugerencias de Kevlin Henney, David Scott, y otros.

23
• Variación en el Comportamiento
• Notificación
• Transacción
• Espejo: “Capacidad para mantener un universo paralelo(s) en el paso
con el mundo de oro”
• Sombra: “Sigue su movimiento y hace algo diferente en un medio difer-
ente” (Puede ser una variación de Proxy).

4.4 Criterios de Diseño


Cuando puse un concurso de ideas en mi boletı́n de noticias7 , una serie de sug-
erencias regresaron, lo cual resultó ser muy útil, pero diferente a la clasificación
anterior, y me di cuenta de que una lista de principios de diseño es al menos
tan importante como estructuras de diseño, pero por una razón diferente: estos
permiten hacer preguntas sobre su diseño propuesto, para aplicar las pruebas
de calidad.

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


• Hacer fáciles las cosas comunes, y posibles las cosas raras
• Consistencia: Una cosa ha llegado a ser muy claro para mı́, especial-
mente debido a Python: las normas más al azar que acumula sobre el
programador, reglas que no tienen nada que ver con la solución del prob-
lema en cuestión, más lento que el programador puede producir. Y esto
no parece ser un factor lineal, sino una exponencial.
• Ley de Demeter: también denominado ”No hables con extraños”. Un
objeto sólo debe hacer referencia a sı́ mismo, sus atributos, y los argumen-
tos de sus métodos.
• Sustracción: un diseño se termina cuando no puede llevar nada más
lejos8 .
• Simplicidad antes de generalidad:9 (Una variación de Occam’s Ra-
zor, que dice que ”la solución más simple es el mejor”). Un problema
común que encontramos en marcos es que están diseñados para ser de uso
general sin hacer referencia a los sistemas reales. Esto lleva a una increı́ble
variedad de opciones que están a menudo sin uso, mal uso o simplemente
7 Una publicación de correo electrónico gratuito. Ver www.BruceEckel.com para suscribirse.
8 Esta 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 à ajouter, mais quand il ne
reste rien à enlever,” o ”La perfección se alcanza no cuando no hay nada más que añadir, sino
cuando no hay nada más que eliminar”.
9 A partir de un correo electrónico de Kevlin Henney.

24
no es útil. Sin embargo, la mayorı́a de los desarrolladores trabajan en
sistemas especı́ficos, y la búsqueda de la generalidad no siempre sirven
bien. La mejor ruta para la generalidad es a través de la comprensión
de ejemplos especı́ficos bien definidos. Por lo tanto, este principio actúa
como el desempate entre alternativas de diseño de otro modo igualmente
viables. Por supuesto, es totalmente posible que la solución más simple es
la más general.
• La reflexividad: (mi término sugerido). Una abstracción por clase, una
clase por la abstracción. También podrı́a ser llamado Isomorfismo.
• Independencia o Ortogonalidad. Expresar ideas independientes de
forma independiente. Esto complementa Separación, Encapsulación y
Variación, y es parte del mensaje de bajo Acoplamiento-alta de Cohesión.
• Una vez y sólo una vez: Evitar la duplicación de la lógica y la estructura
donde la duplicación no es accidental, es decir, donde ambas piezas de
código expresan la misma intención por la misma razón.

En el proceso de intercambio de ideas de esta idea, Espero llegar a un


pequeño puñado de ideas fundamentales que se puede mantener en su cabeza
mientras usted analiza un problema. Ahora bien, otras ideas que vienen de
esta lista puede terminar siendo útiles como una lista de verificación mientras
camina a través y analizando su diseño.

4.5 Singleton
Posiblemente el patrón de diseño más simple es el Singleton, el cual es una man-
era de proporcionar un y sólo un objeto de un tipo particular. Para lograr esto,
usted debe tomar el control de la creación de objetos fuera de las manos del
programador. Una forma cómoda de hacerlo es delegar una sola instancia de
una clase interna privada anidada:

#: c01 : S i n g l e t o n P a t t e r n . py

c l a s s OnlyOne :
class OnlyOne :
def i n i t ( s e l f , arg ) :
s e l f . val = arg
def str ( self ):
return ‘ s e l f ‘ + s e l f . val
i n s t a n c e = None
def i n i t ( s e l f , arg ) :
i f not OnlyOne . i n s t a n c e :
OnlyOne . i n s t a n c e = OnlyOne . OnlyOne ( a r g )
else :

25
OnlyOne . i n s t a n c e . v a l = a r g
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i n s t a n c e , name )

x = OnlyOne ( ’ s a u s a g e ’ )
print x
y = OnlyOne ( ’ eggs ’ )
print y
z = OnlyOne ( ’ spam ’ )
print z
print x
print y
print ‘x ‘
print ‘y ‘
print ‘z ‘
output = ’ ’ ’
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>s a u s a g e
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>e g g s
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076C54C>
< m a i n . OnlyOne i n s t a n c e a t 0076DAAC>
< m a i n . OnlyOne i n s t a n c e a t 0076AA3C>
’’’
#:˜
Debido a que la clase interna se llama con un doble subrayado, este es pri-
vado por lo que el usuario no puede acceder directamente a ella. La clase interna
contiene todos los métodos que normalmente se ponen en la clase si no se va
a ser un singleton, y luego se envuelve en la clase externa la cual controla la
creación mediante el uso de su constructor. La primera vez que usted crea un
OnlyOne, inicializa instance, pero después de eso sólo le ignora.

El acceso viene a través de la delegación, usando el método getattr ( )


para redireccionar las llamadas a la instancia única. Se puede ver en la salida
que a pesar de que parece que se han creado múltiples objetos, el mismo objeto
OnlyOne se utiliza para ambos. Las instancias de OnlyOne son distintas
pero todas ellas de proxy para el mismo objeto OnlyOne.

Tenga en cuenta que el enfoque anterior no le restringe a la creación de


un solo objeto. Esta es también una técnica para crear un grupo limitado de
objetos. En esa situación, sin embargo, usted puede ser confrontado con el
problema de compartir objetos en el grupo. Si esto es un problema, puede crear
una solución involucrando una salida y el registro de los objetos compartidos.

26
Una variación en esta técnica utiliza el método de la clase new añadido
en Python 2.2:

#: c01 : NewSingleton . py

c l a s s OnlyOne ( o b j e c t ) :
class OnlyOne :
def init ( self ):
s e l f . v a l = None
def str ( self ):
return ‘ s e l f ‘ + s e l f . val
i n s t a n c e = None
def new ( c l s ) : # new always a c l a s s m e t h o d
i f not OnlyOne . i n s t a n c e :
OnlyOne . i n s t a n c e = OnlyOne . OnlyOne ( )
r e t u r n OnlyOne . i n s t a n c e
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i n s t a n c e , name )
def s e t a t t r ( s e l f , name ) :
r e t u r n s e t a t t r ( s e l f . i n s t a n c e , name )

x = OnlyOne ( )
x . val = ’ sausage ’
print x
y = OnlyOne ( )
y . v a l = ’ eggs ’
print y
z = OnlyOne ( )
z . v a l = ’ spam ’
print z
print x
print y
#<hr>
output = ’ ’ ’
< m a i n . OnlyOne instance at 0 x00798900>s a u s a g e
< m a i n . OnlyOne instance at 0 x00798900>e g g s
< m a i n . OnlyOne instance at 0 x00798900>spam
< m a i n . OnlyOne instance at 0 x00798900>spam
< m a i n . OnlyOne instance at 0 x00798900>spam
’’’
#:˜
Alex Martelli hace la observación de que lo que realmente queremos con un
Singleton es tener un único conjunto de datos de estado de todos los objetos.
Es decir, puede crear tantos objetos como desee y, siempre y cuando todos se
refieren a la misma información de estado y luego lograr el efecto de Singleton.

27
Él logra esto con lo que él llama Borg 10 , lo cual se logra configurando todas las
dict s a la misma pieza estática de almacenamiento:

#: c01 : B o r g S i n g l e t o n . py
# Alex M a r t e l l i ’ s ’ Borg ’

c l a s s Borg :
s h a r e d s t a t e = {}
def init ( self ):
self . dict = self . shared state

c l a s s S i n g l e t o n ( Borg ) :
def i n i t ( s e l f , arg ) :
Borg . init ( self )
s e l f . val = arg
def s t r ( s e l f ) : return s e l f . val

x = S i n g l e t o n ( ’ sausage ’ )
print x
y = S i n g l e t o n ( ’ eggs ’ )
print y
z = S i n g l e t o n ( ’ spam ’ )
print z
print x
print y
print ‘x ‘
print ‘y ‘
print ‘z ‘
output = ’ ’ ’
sausage
eggs
spam
spam
spam
< m a i n . S i n g l e t o n i n s t a n c e a t 0079EF2C>
< m a i n . S i n g l e t o n i n s t a n c e a t 0079E10C>
< m a i n . S i n g l e t o n i n s t a n c e a t 00798F9C>
’’’
#:˜
Esto tiene un efecto identico como SingletonPattern.py, pero este es más
elegante. En el primer caso, deben conectarse en el comportamiento Singleton a
cada una de sus clases, pero Borg está diseñado para ser reutilizado fácilmente
10 Del programa de televisión Star Trek: The Next Generation. Los Borg son un colectivo

colmena-mente: ”todos somos uno.”

28
a través de la herencia.

Otras dos formas interesantes para definir singleton11 incluyen envolviendo


una clase y utilizando metaclases. El primer enfoque podrı́a ser pensado como
un decorador de clase (decoradores se definirán más adelante en el libro), porque
lleva la clase de interés y añade funcionalidad a ella envolviéndola en otra clase:

#: c01 : S i n g l e t o n D e c o r a t o r . py

class SingletonDecorator :
def i n i t ( self , klass ):
self . klass = klass
s e l f . i n s t a n c e = None
def c a l l ( s e l f , ∗ a r g s , ∗ ∗ kwds ) :
i f s e l f . i n s t a n c e == None :
s e l f . i n s t a n c e = s e l f . k l a s s ( ∗ a r g s , ∗ ∗ kwds )
return s e l f . instance

c l a s s foo : pass
foo = SingletonDecorator ( foo )

x=f o o ( )
y=f o o ( )
z=f o o ( )
x . val = ’ sausage ’
y . v a l = ’ eggs ’
z . v a l = ’ spam ’
print x . val
print y . val
print z . val
print x is y is z
#:˜
[[ Descripción ]]

El segundo enfoque utiliza metaclases, un tema que aún no entiendo pero el


cual se ve muy interesante y poderoso ciertamente (tenga en cuenta que Python
2.2 ha mejorado / simplificado la sintaxis metaclase, y por lo que este ejemplo
puede cambiar):

#: c01 : S i n g l e t o n M e t a C l a s s . py
c l a s s S i n g l e t o n M e t a C l a s s ( type ) :
def i n i t ( c l s , name , b a s e s , d i c t ) :
super ( SingletonMetaClass , c l s )\
11 Sugerido por Chih Chung Chang.

29
. i n i t ( name , b a s e s , d i c t )
original new = c l s . new
d e f my new ( c l s , ∗ a r g s , ∗ ∗ kwds ) :
i f c l s . i n s t a n c e == None :
cls . instance = \
o r i g i n a l n e w ( c l s , ∗ a r g s , ∗ ∗ kwds )
return c l s . instance
c l s . i n s t a n c e = None
c l s . new = s t a t i c m e t h o d ( my new )

c l a s s bar ( o b j e c t ) :
metaclass = SingletonMetaClass
def i n i t ( s e l f , val ) :
s e l f . val = val
def str ( self ):
return ‘ s e l f ‘ + s e l f . val

x=bar ( ’ s a u s a g e ’ )
y=bar ( ’ eggs ’ )
z=bar ( ’ spam ’ )
print x
print y
print z
print x is y is z
#:˜
[[Descripción prolongada, detallada, informativa de lo que son metaclases y
cómo funcionan, por arte de magia insertado aquı́]]

Ejercicio
Modificar BorgSingleton.py para que utilice un método new () de clase.

4.6 Clasificación de Patrones


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

1. Creacional: cómo se puede crear un objeto. Esto a menudo involucra


el aislamiento de los detalles de la creación de objetos por lo que su código no
depende de qué tipos de objetos existen y por lo tanto no tiene que ser cambiado
cuando agrega un nuevo tipo de objeto. El ya mencionado Singleton es clasifi-
cado como un patrón creacional, y más tarde en este libro usted verá ejemplos
de Factory Method y Prototype.

30
2. Estructural: diseñando objetos para satisfacer determinadas restric-
ciones del proyecto. Estos funcionan con la forma en que los objetos están
conectados con otros objetos para asegurar que los cambios en el sistema no
requieren cambios en esas conexiones.

3. Comportamental: objetos que manejan tipos particulares de acciones


dentro de un programa. Estos encapsulan procesos que usted desea realizar,
tales como la interpretación de un lenguaje, el cumplimiento de una solicitud,
movimiento a través de una secuencia (como en un iterador), o implementando
un algoritmo. Este libro contiene ejemplos de los patrones Observer : Obser-
vador y Visitor : visitante.

El libro Design Patterns tiene una sección por cada uno de sus 23 patrones
junto con uno o más ejemplos para cada uno, normalmente en C ++, pero a
veces en Smalltalk. (Usted encontrará que esto no importa demasiado puesto
que puedes traducir fácilmente los conceptos de cualquier lenguaje en Python.)
Este libro no repetirá todos los patrones mostrados en Design Patterns ya que el
libro se destaca por su cuenta y deberı́a ser estudiado por separado. En lugar de
ello, este libro dará algunos ejemplos que deberı́a proporcionarle una sensación
decente para lo que son los patrones y por qué son tan importantes.

Después de años de mirar estas cosas, ello comenzó a ocurrir para mi que
los patrones utilizan para sı́ mismos principios básicos de organización, distintos
de (y más fundamental que) los descritos en Design Patterns. Estos princip-
ios se basan en la estructura de las implementaciones, que es donde he visto
grandes similitudes entre los patrones (más que aquellos expresados en Design
Patterns). Aunque nosotros generalmente tratamos de evitar la implementación
en favor de la interfaz, he encontrado que a menudo es más fácil que pensar, y
especialmente para aprender acerca de los patrones en términos de estos prin-
cipios estructurales. Este libro tratará de presentar los patrones basados en su
estructura en lugar de las categorı́as presentadas en Design Patterns.

4.7 El desafı́o para el desarrollo


Problemas del desarrollo, el proceso UML, Programación extrema.

¿Es la evaluación valiosa? La Capacidad de la inmadurez del modelo:

Wiki Página: http://c2.com/cgi-bin/wiki?CapabilityImMaturityModel

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

Investigación programación en parejas:

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

4.8 Ejercicios
1. SingletonPattern.py siempre crea un objeto, incluso si nunca se ha uti-
lizado. Modifique este programa para usar lazy initialization, por lo que el
objeto singleton sólo se crea la primera vez que se necesita.

2. Usando SingletonPattern.py como punto de partida, cree una clase


que gestione una serie fija de sus propios objetos. Asuma que los objetos son
las conexiones de base de datos y usted tiene solamente una licencia para usar
una cantidad fija de estos términos en cualquier momento.

5 2: Pruebas Unitarias
Este capı́tulo no ha tenido traducción significativa todavı́a.

Una de las realizaciones recientes importantes es el valor dramático


de las pruebas unitarias.

Este es el proceso de construcción de pruebas integradas en todo


el código que usted crea, y ejecutar esas pruebas cada vez que hace
una construcción. Es como si usted está ampliando el compilador,
diciéndole más acerca de lo que se supone que su programa hace. De
esa manera, el proceso de construcción puede comprobar para algo
más que errores de sintaxis, ya que usted le enseña cómo comprobar
si hay errores semánticos también.

Los Lenguajes de programación de estilo C, y C++ en partic-


ular, han valorado tı́picamente rendimiento sobre la seguridad de
programación. La razón de que el desarrollo de programas en Java
es mucho más rápido que en C ++ es debido a la red de seguridad
de Java: caracterı́sticas como la mejor comprobación de tipos, ex-
cepciones forzadas y recolección de basura. Mediante la integración
de la unidad de pruebas en su proceso de construcción, usted está
ampliando esta red de seguridad, y el resultado es que usted puede
desarrollar más rápidamente. También puede ser más audaz en los
cambios que realice, y refactorizar más fácilmente su código cuando
se descubre el desperfector en el diseño o la implementación, y en

32
general, producir un producto mejor, más rápido.

Las pruebas unitarias no se consideran generalmente un patrón


de diseño; de hecho, podrı́an ser consideradas un ”patrón de desar-
rollo”, pero tal vez hay suficientes frases ”patrón” en el mundo ya.
Su efecto sobre el desarrollo es tan significativo que se va a utilizar
en todo este libro, y por lo tanto será introducido aquı́.

My propia experiencia con las pruebas unitarias empezó cuando


Me di cuenta que todos los programas en un libro debe ser extraı́do
de forma automática y organizado en un árbol de código fuente,
junto con makefiles apropiados (o alguna tecnologı́a equivalente) asi
que usted solo podrı́a escribir make para construir todo el árbol.
El efecto de este proceso sobre la calidad del código del libro era
tan inmediato y dramático que pronto se convirtió (en mi mente) en
un requisito para cualquier libro de programación — ¿cómo puede
confiar en el código que usted no compila? También descubrı́ que si
querı́a hacer cambios radicales, Yo podrı́a hacerlo ası́ usando buscar-
y-reemplazar en todo el libro, y también atacar el código a voluntad.
Yo sabı́a que si yo introduje una falla, el extractor de código y los
makefiles podrı́an eliminarla.

Como los programas llegaron a ser más complejos, sin embargo,


También me di cuenta de que habı́a un agujero en serio en mi sis-
tema. Siendo capaz de compilar con éxito programas es claramente
un primer paso importante, y por un libro publicado parecı́a uno
bastante revolucionario — por lo general debido a las presiones de
la publicación, es bastante tı́pico abrir al azar un libro de progra-
mación y descubrir un defecto de codificación. Ahora bien, Seguı́
recibiendo mensajes de los lectores reportando problemas semánticos
en mi código (en Thinking in Java). Estos problemas sólo podı́an
ser descubiertos mediante la ejecución del código. Naturalmente,
Entendı́ esto y habı́a tomado algunos pasos vacilantes tempranos
hacia la implementación de un sistema que realizarı́a pruebas de
ejecución automática, pero yo habı́a sucumbido a las presiones de la
publicación, todo el tiempo sabiendo que definitivamente habı́a algo
equivocado con mi proceso y que esto volverı́a a atacarme en forma
de informes de errores embarazosos (en el mundo del código abierto,
la vergüenza es uno de los principales factores de motivación hacia

33
el aumento de la calidad de su código!).

El otro problema fue que me faltaba una estructura para el sis-


tema de pruebas. Eventualmente, empecé escuchando acerca de las
pruebas unitarias y JUnit12 , lo cual proporcionó una base para una
estructura de prueba. No obstante, aunque JUnit está destinado a
hacer fácil la creación de código de prueba, querı́a ver si podı́a hac-
erlo aún más fácil, aplicando el principio de Programación Extrema
de ”Hacer la cosa más simple que podrı́a posiblemente funcionar”
como punto de partida, y luego la evolución del sistema como de-
mandas de uso (Además, querı́a tratar de reducir la cantidad de
código de prueba, en un intento de ajustarse a una mayor funcionali-
dad en menos código para presentaciones en pantalla). Este capı́tulo
es el resultado.

5.1 Escribir pruebas primero


Como yo mencioné, uno de los problemas que encontré —que la
mayorı́a de la gente encuentra, a resolver — fue someterse a las pre-
siones de la editorial y como resultado dejando caer pruebas por el
borde del camino. Esto es fácil de hacer si usted sigue adelante y
escribe el código de su programa porque hay una pequeña voz que
te dice que, después de todo, lo has conseguido funcionando ahora,
y no serı́a más interesante / útil / conveniente simplemente seguir
adelante y escribir esa otra parte (siempre podemos volver atrás y
escribir las pruebas más adelante). Como resultado, las pruebas
asumen menos importancia, como hacen a menudo en un proyecto
de desarrollo.

La respuesta a este problema, el cual encontré la primera vez


descrito en Extreme Programming Explained, es escribir las pruebas
antes de escribir el codigo. Esto puede parecer para forzar artificial-
mente la prueba a la vanguardia del proceso de desarrollo, pero lo
que realmente hace es dar pruebas de suficiente valor adicional para
que sea esencial. Si escribe las pruebas primero, usted:

1. Describir lo que se supone que el código hace, no con alguna her-


ramienta gráfica externa pero con el código que realmente coloca
12 ttp://www.junit.org

34
la especificación en términos concretos y verificables.
2. Proporcionar un ejemplo de cómo se debe utilizar el código; de
nuevo, esto es un funcionamiento, ejemplo probado, mostrando
normalmente todas las llamadas a métodos importantes, en lugar
de sólo una descripción académica de una librerı́a.
3. Proporcionar una forma de verificar cuando se termina el código
(cuando todas las pruebas se ejecutan correctamente).
Ası́, si usted escribe las pruebas primero entonces la prueba se
convierte en una herramienta de desarrollo, no sólo un paso de ver-
ificación que se puede omitir si sucede que se siente cómodo sobre
el código que acabas de escribir (un consuelo, he encontrado, que es
usualmente equivocado).

Usted puede encontrar argumentos convincentes en Extreme Pro-


gramming Explained, como ”Escribir pruebas primero” es un prin-
cipio fundamental de XP. Si usted no está convencido que necesita
adoptar cualquiera de los cambios sugeridos por XP, tenga en cuenta
que conforme a los estudios del Instituto de Ingenierı́a de Software
(SEI), casi el 70% de las organizaciones de software se ha quedado
atascado en los dos primeros niveles de escala de sofisticación del
SEI: caos, y un poco mejor que el caos. Si usted cambia nada más,
añadir pruebas automatizadas.

5.2 Simples pruebas de Python


Comprobación de validez de una prueba rápida de los programas en
este libro, y anexar la salida de cada programa (como un string :
una cadena) a su listado:

#: SanityCheck . py
import s t r i n g , glob , os
# Do not i n c l u d e t he f o l l o w i n g i n t h e automatic
# tests :
e x c l u d e = ( ” SanityCheck . py ” , ” BoxObserver . py ” , )

d e f v i s i t o r ( arg , dirname , names ) :


d i r = os . getcwd ( )

35
os . c h d i r ( dirname )
try :
pyprogs = [ p f o r p i n g l o b . g l o b ( ’ ∗ . py ’ )
i f p not i n e x c l u d e ]
i f not pyprogs : r e t u r n
p r i n t ’ [ ’ + os . getcwd ( ) + ’ ] ’
f o r program i n pyprogs :
p r i n t ’ \ t ’ , program
os . system ( ” python %s > tmp” % program )
f i l e = open ( program ) . r ead ( )
output = open ( ’ tmp ’ ) . read ( )
# Append output i f i t ’ s not a l r e a d y t h e r e :
i f f i l e . f i n d ( ” output = ’ ’ ’ ” ) == −1 and \
l e n ( 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 ’ ) . r ead ( ) + ” ’ ’ ’ \ n”
open ( program , ’ w ’ ) . w r i t e ( f i l e )
finally :
os . c h d i r ( d i r )

if name == ” m a i n ” :
os . path . walk ( ’ . ’ , v i s i t o r , None )
#:˜
Sólo tiene que ejecutar esto desde el directorio raı́z de los lista-
dos de código para el libro; ello descenderá en cada subdirectorio
y ejecutar el programa allı́. Una forma sencilla de comprobar las
cosas es redirigir la salida estándar a un archivo, entonces, si hay
cualquier error serán la única cosa que aparece en la consola durante
la ejecución del programa.

5.3 Un framework muy simple


Como mencioné, un objetivo primario de este código es hacer la
escritura de código de pruebas unitarias muy simple, incluso más
simple que con JUnit. Como otras necesidades se descubren du-
rante el uso de este sistema, a continuación, que la funcionalidad se
puede añadir, pero para empezar con el marco sólo va a proporcionar

36
una manera de crear y ejecutar fácilmente y pruebas, e informar fra-
caso si algo se rompe (el éxito no producirá resultados distintos de
salida normal que puede ocurrir durante la ejecución de la prueba).
Mi uso previsto de este marco es en makefiles, y make aborta si
hay un valor de retorno distinto de cero de la ejecución de un co-
mando. El proceso de construcción consistirá en la compilación de
los programas y la ejecución de pruebas unitarias, y si make recibe
a través de todo el camino exitosamente, entonces el sistema será
validado, de lo contrario, se anulará en el lugar de la falta, para que
pueda proporcionar cualquier granularidad que necesita escribiendo
el mayor número de pruebas como quiera, cada una cubriendo tanto
o tan poco como usted encuentra necesario.

En algún sentido, este marco proporciona un lugar alternativo


para todas aquellas declaraciones de ”imprimir” que he escrito y
posteriormente borrados a través de los años.

Para crear un conjunto de pruebas, usted comienza haciendo una


clase interna static dentro de la clase que desea probar (su código
de prueba también puede probar otras clases; usted decide). Este
código de prueba se distingue heredando de UnitTest:

# t e s t : UnitTest . py
# The b a s i c u n 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 String testID
s t a t i c List e r r o r s = ArrayList ()
# Override cleanup () i f t e s t object
# 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 t h a t must be c l e a n e d up :
def cleanup ( s e l f ) :
# Verify the truth of a c on d it io n :
p r o t e c t e d f i n a l v o i d a f f i r m ( b o o l e a n c o n d i t i o n ){
i f ( ! condition )
e r r o r s . add ( ” f a i l e d : ” + t e s t I D )

# :˜

37
El único método de prueba [[Hasta ahora]] es affirm( )13 , el
cual es protected de modo que pueda ser utilizado de la clase que
hereda. Todo lo que este método hace es verificar que algo es true.
Si no, añade un error a la lista, informando que la prueba actual
(establecida por la static testID, que es fijado por el programa de
pruebas de funcionamiento que deberá ver dentro de poco) ha fra-
casado. Aunque esto no es una gran cantidad de información — es
posible que también desee tener el número de lı́nea, lo que podrı́a ser
extraı́do de una excepción — puede ser suficiente para la mayorı́a
de las situaciones.

A diferencia de JUnit, (que usa los métodos setUp( ) y tear-


Down()), los objetos de prueba se construirán usando la construcción
ordinaria Python. Usted define los objetos de prueba mediante la
creación de ellos como miembros de la clase ordinaria de la clase de
prueba, y un nuevo objeto de clase de prueba se creará para cada
método de ensayo (evitando ası́ cualquier problema que pueda ocur-
rir debido a efectos secundarios entre las pruebas). Ocasionalmente,
la creación de un objeto de prueba asignará recursos sin memoria,
en cuyo caso debe anular cleanup( ) para liberar esos recursos.

5.4 Escribir pruebas


Escribir pruebas llega a ser muy simple. Aquı́ está un ejemplo que
crea la clase interna necesaria static y realiza pruebas triviales:

# c02 : TestDemo . py
# Creating 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 = 0
p r i v a t e i n t i d = ++objCounter
p u b l i c TestDemo ( S t r i n g s ) :
p r i n t ( s + ” : count = ” + i d )

def close ( s e l f ) :
13 Yo habı́a llamado originalmente esta assert(), pero esa palabra llegó a ser reservada en

el JDK 1.4 cuando se añadieron las afirmaciones al lenguaje.

38
p r i n t ( ” C l e a n i n g up : ” + i d )

d e f someCondition ( s e l f ) : r e t u r n 1
p u b 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 ” )
def cleanup ( s e l f ) :
test2 . close ()
test1 . close ()

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

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

# Causes t he b u i l d t o h a l t :
#! p u b l i c v o i d t e s t 3 ( ) : a f f i r m ( 0 )

# :˜
El método test3() está comentado, porque, como verá, hace que
la acumulación automática de código fuente de árboles de este libro
se detuviera.

Usted puede nombrar su a su clase interna como quiera; el único


factor importante es extends UnitTest. También puede incluir
cualquier código de apoyo necesario en otros métodos. Sólo los
métodos public que toman ningún argumento y retorno void serán
tratados como pruebas (Los nombres de estos métodos no son también
limitados).

La clase de prueba anterior crea dos instancias de TestDemo.


El constructor TestDemo imprime algo, para que podamos ver que
está siendo llamado. Usted podrı́a también definir un constructor
por defecto (el único tipo que es utilizado por el marco de prueba),
aunque ninguno es necesario aquı́. La clase TestDemo tiene un

39
método close() que sugiere que se utiliza como parte de la limpieza
del objeto, ası́ este es llamado en el método reemplazado cleanup()
en Test.

Los métodos de prueba utilizan el método affirm( ) para validar


expresiones, y si hay un fallo de la información se almacena y se im-
prime después se ejecutan todas las pruebas. Claro, los argumentos
affirm() son usualmente más complicados que este; usted verá más
ejemplos a lo largo del resto de este libro.

Observe que en testB(), el campo private objCounter es ac-


cesible para el código de prueba — esto es proque Test tiene los
permisos de una clase interna.

Se puede ver que escribir código de prueba requiere muy poco es-
fuerzo adicional, y ningún conocimiento distinto del utilizado para
escribir las clases ordinarias.

Para ejecutar las pruebas, utilice RunUnitTests.py (que será


presentado dentro de poco). El comando para el código anterior se
ve ası́:

java com.bruceeckel.test.RunUnitTests TestDemo

Esto produce el siguiente resultado:

t e s t 1 : count = 1
t e s t 2 : count = 2 r a t h e r than p u t t i n g i t i n and s t r i p p i n g i t out as i s
TestDemo . t e s t A
C l e a n i n g up : 2
C l e a n i n g up : 1
t e s t 1 : count = 3
t e s t 2 : count = 4
TestDemo . t e s t B
C l e a n i n g up : 4
C l e a n i n g up : 3
Todo el ruido de salida es tan lejos como el éxito o el fracaso de
la unidad de pruebas se refiere. Sólo si una o más de la unidad de

40
pruebas fallan el programa devuelve un valor distinto de cero para
terminar el proceso de make después se producen los mensajes de
error. Por lo tanto, se puede optar por producir una salida o no,
como se adapte a sus necesidades, y la clase de prueba llega a ser
un buen lugar para poner cualquier código de impresión que pueda
necesitar — si usted hace esto, se tiende a mantener dicho código
alrededor en lugar de ponerlo dentro y despojarlo afuera como se
hace normalmente con código de seguimiento.

Si es necesario agregar una prueba para una clase derivada de


uno que ya tiene una clase de prueba, no hay problema, como se
puede ver aquı́:

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

c l a s s TestDemo2 ( TestDemo ) :
p u b l i c TestDemo2 ( S t r i n g s ) : . i n i t ( s )
# You can even use t h e same name
# as th e t e s t c l a s s i n t h e base c l a s s :
p u b l i c s t a t i c c l a s s Test ( UnitTest ) :
def testA ( s e l f ) :
print ‘ ‘ TestDemo2 . t e s t A ”
a f f i r m ( 1 + 1 == 2 )

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

# :˜
Incluso el nombre de la clase interna puede ser el mismo. En el
código anterior, todas las afirmaciones son siempre verdaderas por
lo que las pruebas nunca fallarán.

5.5 Pruebas de caja blanca y caja negra


Los ejemplos de prueba de unidad hasta el momento son los que
tradicionalmente se llaman white-box tests : pruebas de caja blanca.

41
Esto significa que el código de prueba tiene un acceso completo a la
parte interna de la clase que está siendo probado (por lo que podrı́a
ser llamado más apropiadamente las pruebas ”caja transparente”).
Pruebas de caja blanca sucede automáticamente cuando usted hace
la clase de prueba de unidad como una clase interna de la clase que
está probando, ya que las clases internas tienen automáticamente
acceso a todos sus elementos de clase exteriores, incluso los que son
private.

Una forma posiblemente más común de la prueba es la black-


box testing : prueba de caja negra, que se refiere al tratamiento
de la clase que se está probando como una caja impenetrable. No
se puede ver el funcionamiento interno; sólo se puede acceder a las
partes public de la clase. Ası́, las pruebas de caja negra corre-
sponden más estrechamente a las pruebas funcionales, para verificar
los métodos que el programador-cliente va a utilizar. En adición,
las pruebas de caja negra proporcionan una hoja de instrucciones
mı́nimas para el programador-cliente – en ausencia de toda otra doc-
umentación, las pruebas de caja negra al menos demuestran cómo
hacer llamadas básicas a los métodos de la clase public.

Para realizar las pruebas de caja negra utilizando el marco de


la unidad de pruebas presentado en este libro, todo lo que necesita
hacer es crear su clase de prueba como una clase global en lugar
de una clase interna. Todas las demás reglas son las mismas (por
ejemplo, la clase de prueba de unidad debe ser public, y derivado
de UnitTest).

Hay otra advertencia, que también proporcionará un pequeño


repaso de los paquetes Java. Si usted quiere ser completamente rig-
uroso, debe poner su clase de prueba de caja negra en un directorio
independiente de la clase puesta a prueba, de lo contrario, tendrá
acceso paquete a los elementos de la clase siendo probada. Es decir,
usted será capaz de acceder a los elementos protected y friendly
de la clase siendo probada. Aqui está un ejemplo:

# c02 : T e s t a b l e . py

c l a s s Testable :

42
p ri v at e void f1 ( ) :
d e f f 2 ( s e l f ) : # ” F r i e n d l y ” : package a c c e s s
d e f f 3 ( s e l f ) : # Also package a c c e s s
def f4 ( s e l f ) :
# :˜
Normalmente, el único método que podrı́a ser accesible directa-
mente para el programador-cliente es f4(). Sin embargo, si usted
pone su prueba de caja negra en el mismo directorio, automáticamente
se convierte en parte de un mismo paquete (en este caso, el paquete
por defecto ya que no se especifica ninguno) y entonces tiene un
acceso inapropiado:

# c02 : TooMuchAccess . py

c l a s s TooMuchAccess ( UnitTest ) :
Testable t s t = Testable ()
def test1 ( 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 en su
propio 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 estar
en su propio paquete, de modo que pueda ser importado (tenga en
cuenta que también es posible importar una clase ”paquete-menos”,
dando el nombre de clase en la declaración import y asegurando
que la clase está en su CLASSPATH):

# c02 : t e s t a b l e : T e s t a b l e . py
package c02 . t e s t a b l e

c l a s s Testable :
p ri v at e void f1 ( ) :
d e f f 2 ( s e l f ) : # ” F r i e n d l y ” : package a c c e s s
d e f f 3 ( s e l f ) : # Also package a c c e s s

43
def f4 ( s e l f ) :
# :˜
Aquı́ está la prueba de la caja-negra en su propio paquete, mostrando
como solamente los métodos públicos pueden ser llamados:

# c02 : t e s t : BlackBoxTest . py

c l a s s BlackBoxTest ( UnitTest ) :
Testable t s t = Testable ()
def test1 ( s e l f ) :
#! t s t . f 2 ( ) # Nope !
#! t s t . f 3 ( ) # Nope !
t s t . f 4 ( ) # Only p u b 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 métodos disponibles. De modo que
hace que un buen ejemplo de programación. Claro, es más fácil
desde el punto de vista de codificación para simplemente hacer una
clase interna, y a menos que sea apasionado sobre la necesidad es-
pecı́fica de pruebas de caja negra es posible que sólo quiera seguir
adelante y utilizar las clases internas (con el conocimiento que si
usted necesita que más tarde puede extraer las clases internas en
clases de prueba de caja negra separadas, sin demasiado esfuerzo).

5.6 Ejecución de Pruebas


El programa que ejecuta las pruebas hace un uso significativo de re-
flexión por lo que la escritura de las pruebas puede ser simple para
el programador cliente.

# t e s t : RunUnitTests . py
# D i s c o v e r i n g th e u n i t t e s t
# c l a s s and running each t e s t .

c l a s s RunUnitTests :

44
public s t a t i c void
r e q u i r e ( b o o l e a n r e q u i r e m e n t , S t r i n g errmsg ) :
i f ( ! requirement ) :
System . e r r . p r i n t l n ( errmsg )
System . e x i t ( 1 )

d e f main ( s e l f , S t r i n g [ ] a r g s ) :
r e q u i r e ( a r g s . l e n g t h == 1 ,
” Usage : RunUnitTests q u a l i f i e d −c l a s s ” )
try :
C l a s s c = C l a s s . forName ( a r g s [ 0 ] )
# Only f i n d s th e i n n e r c l a s s e s
# d e c l a r e d i n th e c u r r e n t c l a s s :
Class [ ] c l a s s e s = c . getDeclaredClasses ()
C l a s s ut = n u l l
f o r ( i n t j = 0 j < c l a s s e s . l e n g t h j ++):
# Skip i n n e r c l a s s e s t h a t a r e
# not d e r i v e d from UnitTest :
i f ( ! UnitTest . c l a s s .
isAssignableFrom ( c l a s s e s [ j ] ) )
continue
ut = c l a s s e s [ j ]
break # Finds th e f i r s t t e s t c l a s s o n l y

# I f i t found an i n n e r c l a s s ,
# t h a t c l a s s must be s t a t i c :
i f ( ut != n u l l )
require (
M o d i f i e r . i s S t a t i c ( ut . g e t M o d i f i e r s ( ) ) ,
” i n n e 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 th e i n n e r 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 s A s s i g n a b l e F r o m ( c ) )
ut = c
r e q u i r e ( ut != n u l l ,
”No UnitTest c l a s s found ” )
require (

45
M o d i f i e r . i s P u b l i c ( ut . g e t M o d i f i e r s ( ) ) ,
” UnitTest c l a s s must be p u b l i c ” )
Method [ ] methods = ut . getDeclaredMethods ( )
f o r ( i n t k = 0 k < methods . l e n g t h k++):
Method m = methods [ k ]
# I g n o r e o v e r r i d d e n UnitTest methods :
i f (m. getName ( ) . e q u a l s ( ” c l e a n u p ” ) )
continue
# Only p u b l i c methods with no
# arguments and v o i d r e t u r n
# t y p e s w i l l be used as t e s t code :
i f (m. getParameterTypes ( ) . l e n g t h == 0 &&
m. getReturnType ( ) == v o i d . c l a s s &&
M o d i f i e r . i s P u b l i c (m. g e t M o d i f i e r s ( ) ) ) :
# The name o f th e t e s t i s
# used i n e r r o r messages :
UnitTest . t e s t I D = m. getName ( )
# A i n s t a n c e o f t he
# t e s t o b j e c t i s c r e a t e d and
# c l e a n e d up f o r each t e s t :
Object t e s t = ut . n ew I ns t an c e ( )
m. i n v o k e ( t e s t , Object [ 0 ] )
( ( UnitTest ) t e s t ) . c l e a n u p ( )

c a t c h ( Ex cep ti on e ) :
e . p r i n t S t a c k T r a c e ( System . e r r )
# Any e x c e p t i o n w i l l r e t u r n a nonzero
# v a l u e t o t h e c o n s o l e , so t h a t
# ’ make ’ w i l l a b o r t :
System . e r r . p r i n t l n ( ” Aborting make ” )
System . e x i t ( 1 )

# A f t e r a l l t e s t s i n t h i s c l a s s a r e run ,
# d i s p l a y any r e s u l t s . I f t h e r e were e r r o r s ,
# a b o r t ’ make ’ by r e t u r n i n g a nonzero v a l u e .
if ( 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 ( )
w h i l e ( i t . hasNext ( ) )
System . e r r . p r i n t l n ( i t . next ( ) )

46
System . e x i t ( 1 )

# :˜

47
5.7 Ejecutar Pruebas Automáticamente
5.8 Ejercicios
1. Instalar el código fuente árbol de este libro y asegurar que usted
tenga una utilidad make instalada en su sistema.

2. Modificar TestDemo.java mediante la adición de una nueva


prueba que produzca una excepción. Escriba make y observar los
resultados.

3. Modifique sus soluciones a los ejercicios en el capı́tulo 1,


añadiendo las pruebas unitarias. Escribe makefiles que incorporen
las pruebas unitarias.

6 3: Entornos de aplicaciones de construcción


Un entorno de aplicación le permite heredar de una clase o conjunto
de clases y crear una nueva aplicación, reutilizando la mayor parte
del código en las clases existentes y anular uno o más métodos con
el fin de personalizar la aplicación a sus necesidades. Un concepto
fundamental en el entorno de aplicación es el Template Method :
Método Plantilla el que normalmente se oculta debajo de las cubier-
tas e impulsa la aplicación llamando a los diversos métodos en la
clase base (algunos de los cuales usted ha anulado con el fin de crear
la aplicación).

Por ejemplo, cuando se crea un applet está utilizando un en-


torno de aplicación: hereda de JApplet y y luego anular init().
El mecanismo applet (que es un método plantilla) se encarga del
resto mediante la elaboración de la pantalla, el manejo del ciclo de
eventos, cambiar el tamaño, etc.

6.1 Método Plantilla


Una caracterı́stica importante del Método Plantilla es que está definido
en la clase base y no puede ser cambiado. Este algunas veces es un
método Privado pero este es siempre prácticamente final. Llama
otros métodos de la clase base (los que usted anula) con el fin de

48
hacer su trabajo, pero se le suele llamar sólo como parte de un
proceso de inicialización (y por tanto el programador-cliente no es
necesariamente capaz de llamarlo directamente).

#: c03 : TemplateMethod . py
# Simple d e m o n s t r a t i o n o f Template Method .

c l a s s ApplicationFramework :
def init ( self ):
s e l f . templateMethod ( )
def templateMethod ( s e l f ) :
f o r i i n range ( 5 ) :
s e l f . customize1 ()
s e l f . customize2 ()

# Create a ” a p p l i c a t i o n ” :
c l a s s MyApp( ApplicationFramework ) :
def customize1 ( s e l f ) :
p r i n t ”Nudge , nudge , wink , wink ! ” ,
def 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-
ización necesaria y después de iniciar el ”motor” (el método plan-
tilla) que ejecuta la aplicación (en una aplicación GUI, este ”mo-
tor” serı́a el bucle principal del evento). El programador cliente
simplemente proporciona definiciones para customize1() y cus-
tomize2() y la ”aplicación” esta listo para funcionar.

Veremos Template Method : Método plantilla otras numerosas


veces a lo largo del libro.

6.2 Ejercicios
1. Crear un entorno que tome una lista de nombres de archivo en
la lı́nea de comandos. Este abre cada archivo, excepto el último
para la lectura, y el último para la escritura. El entorno procesará

49
cada archivo de entrada utilizando una polı́tica indeterminada y
escribir la salida al último archivo. Heredar para personalizar
este entorno para crear dos aplicaciones separadas:
1) Convierte todas las letras en cada archivo a mayúsculas.
2) Busca los archivos de las palabras dadas en el primer archivo.

7 4: Al frente de una implementación


Tanto Proxy y State : Estado proporcionan una clase sustituta
que se utiliza en el código; la clase real que hace el trabajo se es-
conde detrás de esta clase sustituta. Cuando llama un método en
el surrogate : sustituto, este simplemente gira y llama al método
en la implementación de la clase. Estos dos patrones son tan sim-
ilares que el Proxy es simplemente un caso especial de State. Uno
es probado a solo agrupar a los dos juntos en un patrón llamado
Surrogate : sustituto, pero el término ”proxy” tiene un significado
antiguo y especializado, que probablemente explica la razón de los
dos patrones diferentes.

La idea básica es simple: de una clase base, el sustituto se deriva


junto con la clase o clases que proporcionan la implementación real:

Cuando se crea un objeto sustituto, se da una implementación a


la cual enviar todas las llamadas a métodos.

Estructuralmente, la diferencia entre Proxy y State es simple: un


Proxy tiene una sola implementación, mientras State tiene más de
uno. La aplicación de los patrones se considera (en Design Patterns

50
: Patrones de Diseño) que es distinta: Proxy es usado para controlar
el acceso a esta implementación, mientras State le permite cambiar
la implementación de forma dinámica. Sin embargo, si expande su
noción de ”controlando el acceso a la implementación”, entonces los
dos encajan pulcramente juntos.

7.1 Proxy
Si implementamos Proxy siguiendo el diagrama anterir, se ve ası́:
#: c04 : ProxyDemo . py
# Simple d e m o n s t r a t i o n o f t he Proxy p a t t e r n .

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

c l a s s Proxy :
def init ( self ):
s e l f . i m p l e m e n t a t i o n = Implementation ( )
# Pass method c a l l s t o t he i m p l e m e n t a t i o n :
def f ( s e l f ) : s e l f . implementation . f ()
def g ( s e l f ) : s e l f . implementation . g ()
def h( s e l f ) : s e l f . implementation . h ()

p = Proxy ( )
p. f ( ) ; p. g ( ) ; p. h()
#:˜
No es necesario que Implementation tenga la misma interfaz
que Proxy; siempre y cuando Proxy es de alguna manera “speak-
ing for” : ”Hablar por” la clase que está refiriéndose al método llama
a continuación, la idea básica es satisfecha (tenga en cuenta que esta
declaración está en contradicción con la definición de Proxy en GoF).
Sin embargo, es conveniente tener una interfaz común para que Im-
plementation se vea obligado a cumplir con todos los métodos que

51
Proxy necesita llamar.

Por supuesto, en Python tenemos un mecanismo de delegación


integrado, lo que hace que el Proxy aún más simple de implementar:

#: c04 : ProxyDemo2 . py
# Simple d e m o n s t r a t i o n o f t he Proxy p a t t e r n .

c l a s s Implementation2 :
def f ( s e l f ) :
p r i n t ” Implementation . f ( ) ”
def g( s e l f ) :
p r i n t ” Implementation . g ( ) ”
def h( s e l f ) :
p r i n t ” Implementation . h ( ) ”

c l a s s Proxy2 :
def init ( self ):
s e l f . i m p l e m e n t a t i o n = Implementation2 ( )
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i m p l e m e n t a t i o n , name )

p = Proxy2 ( )
p. f (); p.g(); p.h();
#:˜
La belleza de la utilización de getattr ( ) es que Proxy2 es
completamente genérico, y no vinculada a cualquier implementación
particular (en Java, un ”proxy dinámico” bastante complicado ha
sido inventado para lograr esto mismo).

52
7.2 State : Estado
El patrón State añade más implementaciones a Proxy, junto con una
manera de cambiar de una implementación a otra durante tiempo
de vida del sustituto:

#: c04 : StateDemo . py
# Simple d e m o n s t r a t i o n o f t he S t a t e p a t t e r n .

c l a s s State d :
def i n i t ( s e l f , imp ) :
s e l f . i m p l e m e n t a t i o n = imp
d e f changeImp ( s e l f , newImp ) :
s e l f . i m p l e m e n t a t i o n = newImp
# D e l e g a t e c a l l s t o t he i m p l e m e n t a t i o n :
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i m p l e m e n t a t i o n , name )

c l a s s Implementation1 :
def f ( s e l f ) :
p r i n t ” F i d d l e de dum , F i d d l e de dee , ”
def g( s e l f ) :
p r i n t ” E r i c t he h a l f a bee . ”
def h( s e l f ) :
p r i n t ”Ho ho ho , t e e hee hee , ”

c l a s s Implementation2 :
def f ( s e l f ) :
p r i n t ”We’ r e Knights o f th e Round Table . ”
def g( s e l f ) :
p r i n t ”We dance whene ’ e r we ’ r e a b l e . ”
def h( s e l f ) :
p r i n t ”We do r o u t i n e s and c h o r u s s c e n e s ”

d e f run ( b ) :
b. f ()
b. g ()
b . h()
b. g ()

53
b = S t a t e d ( Implementation1 ( ) )
run ( b )
b . changeImp ( Implementation2 ( ) )
run ( b )
#:˜
Se puede ver que la primera implementación se usa para una
parte, a continuación, la segunda implementación se intercambia y
ese es utilizado.

La diferencia entre Proxy y State está en los problemas que se


resuelven. Los usos comunes para Proxy como se describe en Design
Patterns : patrones de diseño son:
1. Proxy remoto. Este proxy para un objeto en un espacio de
dirección diferente. Se crea un proxy remoto de forma automática
por el compilador RMI rmic ya que crea stubs y esqueletos.
2. Proxy virtual.Esto proporciona ”inicialización relajada” para
crear objetos costosos en la demanda.
3. Proxy de Protección. 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 cuando
se accede al objeto proxy. Por ejemplo, o para llevar un registro
de el número de referencias que se realizan para un objeto en
particular, con el fin de implementar el lenguaje copy-on-write :
copiar en escritura y prevenir objeto aliasing. Un ejemplo sencillo
es hacer el seguimiento de el número de llamadas a un método
en particular.

Usted podrı́a mirar a una referencia de Python como un tipo de


proxy de protección, ya que controla el acceso al objeto real en el
montón (y asegura, por ejemplo, que no utilice una referencia nula).

[[reescribir esto: en Design Patterns : Diseño de Partrones, Proxy


y State no son vistos como relacionados entre sı́ porque los dos se les
da (lo que considero arbitrario) diferentes estructuras. State, en par-
ticular, utiliza una jerarquı́a de implementación separada pero esto

54
me parece innecesario a menos que usted haya decidido que la imple-
mentación no está bajo su control (ciertamente una posibilidad, pero
si usted es dueño de todo el código no parece haber ninguna razón
para no beneficiarse de la elegancia y amabilidad de la clase base in-
dividual). En adición, Proxy no necesita utilizar la misma clase base
para su implementación, siempre y cuando el objeto proxy está con-
trolando acceso al objetarlo “frente” a favor. Independientemente
de los detalles, en ambos Proxy y State un sustituto está pasando
la llamada al método a través de un objeto de implementación.]]

55
7.3 StateMachine
Mientras State : Estado tiene una manera de permitir que el pro-
gramador cliente cambie la implementación, StateMachine impone
una estructura para cambiar automáticamente la implementación
de un objeto al siguiente. La implementación actual representa el
estado en que un sistema está, y el sistema se comporta de manera
diferente de un estado a otro (ya que utiliza State). Basicamente,
esta es una ”state machine : máquina de estados” usando objetos.

El código que mueve el sistema de un estado a otro es a menudo


un Template Method : Método Plantilla, como se ve en el siguiente
entorno para una máquina básica estatal.

Cada estado puede ser run( ) para cumplir con su compor-


tamiento, y (en este diseño) también puede pasarlo a un objeto ”de
entrada” por lo que le puede decir qué nuevo estado para avanzar
basado en eso ”de entrada”. La distinción clave entre este diseño y
el siguiente es que aquı́, cada objeto State decide lo que otros esta-
dos pueden avanzar , basado en la “input : entrada”, mientras que
en el posterior diseño de todas las transiciones de estado se llevan
a cabo en una sola tabla. Otra forma de decirlo es que aquı́, cada
objeto State tiene su propia pequeña tabla State, y en el diseño
posterior hay una sola tabla directora de transición de estado para
todo el sistema.

#: c04 : s t a t e m a c h i n e : S t a t e . py
# A S t a t e has an o p e r a t i o n , and can be moved
# i n t o t he next S t a t e g i v e n an Input :

c l a s s State :
d e f run ( s e l f ) :
a s s e r t 1 , ” run not implemented ”
d e f next ( s e l f , i n p u t ) :
a s s e r t 1 , ” next not implemented ”
#:˜
Esta clase es clase es claramente innecesaria, pero que nos per-
mite decir que algo es un objeto State en el código, y proporcionar
un mensaje de error ligeramente diferente cuando no se implemen-

56
tan todos los métodos. Podrı́amos haber conseguido básicamente el
mismo efecto diciendo:

c l a s s State : pass
Porque todavı́a conseguirı́amos excepciones si run o next() fueran
llamados por un tipo derivado, y no habı́an sido implementados.

El StateMachine hace un seguimiento de la situación actual, el


cual es inicializado por el constructor. El método runAll() toma
una lista de objetos Input. Este método no sólo avazna al siguiente
estado, sino que también llama run( ) para cada objeto state : es-
tado – por lo tanto se puede ver que es una expansión de la idea del
patrón State, ya que run( ) hace algo diferente dependiendo del
estado en que el sistema está.

#: c04 : s t a t e m a c h i n e : StateMachine . py
# Takes a l i s t o f I n p u t s t o move from S t a t e t o
# S t a t e u s i n g a t e m p l a t e method .

c l a s s StateMachine :
def init ( self , initialState ):
s e l f . currentState = i n i t i a l S t a t e
s e l f . c u r r e n t S t a t e . run ( )
# Template method :
def runAll ( s e l f , inputs ) :
for i in inputs :
print i
s e l f . c u r r e n t S t a t e = s e l f . c u r r e n t S t a t e . next ( i )
s e l f . c u r r e n t S t a t e . run ( )
#:˜
También he tratado runAll( ) como un método plantilla. Esto
es tı́pico, pero ciertamente no es necesario – posiblemente podrı́a
querer anularlo, pero por lo general el cambio de comportamiento
se producirá en State de run( ) en su lugar

En este punto se ha completado el marco básico para este es-


tilo de StateMachine (donde cada estado decide los próximos esta-
dos). Como ejemplo, voy a utilizar una ratonera de fantası́a que

57
puede moverse a través de varios estados en el proceso de atrapar
un ratón14 . Las clases ratón y la información se almacenan en el
paquete mouse, incluyendo una clase en representación de todas
los posibles movimientos que un ratón puede hacer, que serán los
entradas a la state machine: máquina de estados:

#: c04 : mouse : MouseAction . py

c l a s s MouseAction :
def i n i t ( self , action ) :
s e l f . action = action
def s t r ( s e l f ) : return s e l f . action
def cmp ( s e l f , other ) :
r e t u r n cmp( s e l f . a c t i o n , o t h e r . a c t i o n )
# N e c e s s a r y when cmp or eq i s defined
# i n o r d e r t o make t h i s c l a s s u s a b l e as a
# d i c t i o n a r y key :
def hash ( se lf ):
r e t u r n hash ( s e l f . a c t i o n )

# 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 . a p p e a r s = MouseAction ( ” mouse a p p e a r s ” )
MouseAction . runsAway = MouseAction ( ” mouse runs away ” )
MouseAction . e n t e r s = MouseAction ( ” mouse e n t e r s t r a p ” )
MouseAction . e s c a p e s = MouseAction ( ” mouse e s c a p e s ” )
MouseAction . trapped = MouseAction ( ” mouse trapped ” )
MouseAction . removed = MouseAction ( ” mouse removed ” )
#:˜
Usted observará que cmp ( ) se ha reemplazado para imple-
mentar una comparación entre los valores de acción. También, cada
posible jugada de un ratón se enumera como un objeto de Mouse-
Action, todos los cuales son los campos estáticos en MouseAction.

Para la creación de código de prueba, una secuencia de entradas


de mouse está provisto de un archivo de texto:

# :! c04 : mouse : MouseMoves . t x t


14 Ningún ratón fue perjudicado en la creación de este ejemplo.

58
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse escapes
mouse appears
mouse enters trap
mouse trapped
mouse removed
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse trapped
mouse removed
#:˜
Con estas herramientas en su lugar, ahora es posible crear la
primera versión del programa mousetrap : ratonera. Cada subclase
State define su comportamiento run( ) y también establece su sigu-
iente estado con una cláusula if-else:

#: c04 : mousetrap1 : MouseTrapTest . py


# S t a t e Machine p a t t e r n u s i n g ’ i f ’ s t a t e m e n t s
# t o d e t e r m i n e th e next s t a t e .
import s t r i n g , s y s
s y s . path += [ ’ . . / s t a t e m a c h i n e ’ , ’ . . / mouse ’ ]
from S t a t e import S t a t e
from StateMachine import StateMachine
from 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 ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Waiting : B r o a d c a s t i n g c h e e s e s m e l l ”

d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . a p p e a r s :
r e t u r n MouseTrap . l u r i n g
r e t u r n MouseTrap . w a i t i n g

59
c l a s s Luring ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Luring : P r e s e n t i n g Cheese , door open ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . runsAway :
r e t u r n MouseTrap . w a i t i n g
i f i n p u t == MouseAction . e n t e r s :
r e t u r n MouseTrap . t r a p p i n g
r e t u r n MouseTrap . l u r i n g

c l a s s Trapping ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Trapping : C l o s i n g door ”

d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . e s c a p e s :
r e t u r n MouseTrap . w a i t i n g
i f i n p u t == MouseAction . trapped :
r e t u r n MouseTrap . h o l d i n g
r e t u r n MouseTrap . t r a p p i n g

c l a s s Holding ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Holding : Mouse caught ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . removed :
r e t u r n MouseTrap . w a i t i n g
r e t u r n MouseTrap . h o l d i n g

c l a s s MouseTrap ( StateMachine ) :
def init ( self ):
# I n i t i a l state
StateMachine . i n i t ( s e l f , MouseTrap . w a i t i n g )

# Static variable i n i t i a l i z a t i o n :
MouseTrap . w a i t i n g = Waiting ( )
MouseTrap . l u r i n g = Luring ( )
MouseTrap . t r a p p i n g = Trapping ( )

60
MouseTrap . h o l d i n g = Holding ( )

moves = map( s t r i n g . s t r i p ,
open ( ” . . / mouse/MouseMoves . t x t ” ) . r e a d l i n e s ( ) )
MouseTrap ( ) . r u n A l l (map( MouseAction , moves ) )
#:˜
La clase StateMachine simplemente define todos los posibles
estados como objetos estáticos, y también establece el estado ini-
cial. UnitTest crea un MouseTrap y luego prueba con todas las
entradas de un MouseMoveList.

Mientras el uso de las sentencias if dentro de los métodos next(


) es perfectamente razonable, la gestión de un gran número de ellos
podrı́a llegar a ser difı́cil. Otro enfoque es crear tablas dentro de
cada objeto State definiendo los diversos estados próximos basados
en la entrada.

Inicialmente, esto parece que deberı́a ser bastante simple. Usted


debe ser capaz de definir una tabla estática en cada subclase State
que define las transiciones en términos de los otros objetos State.
Sin embargo, resulta que este enfoque genera dependencias de ini-
cialización cı́clicas. Para resolver el problema, He tenido que retrasar
la inicialización de las tablas hasta la primera vez que se llama al
método next( ) para un objeto en particular State. Inicialmente,
los métodos next()

La clase StateT es una implementación de State ((de modo que


la misma clase StateMachine puede ser utilizado en el ejemplo an-
terior) que añade un Map y un método para inicializar el mapa a
partir de una matriz de dos dimensiones. El ”método next()” tiene
una implementación de la clase base que debe ser llamado desde el
”método next() clase derivada anulada”, después de que se ponen
a prueba para un ”null Map” (y inicializarlo si es nulo):

#: c04 : mousetrap2 : MouseTrap2Test . py


# A b e t t e r mousetrap u s i n g t a b l e s
import s t r i n g , s y s
s y s . path += [ ’ . . / s t a t e m a c h i n e ’ , ’ . . / mouse ’ ]
from S t a t e import S t a t e

61
from StateMachine import StateMachine
from MouseAction import MouseAction

c l a s s StateT ( S t a t e ) :
def init ( self ):
s e l f . t r a n s i t i o n s = None
d e f next ( s e l f , i n p u t ) :
i f s e l f . t r a n s i t i o n s . has key ( input ) :
return s e l f . t r a n s i t i o n s [ input ]
else :
r a i s e ” Input not s u p p o r t e d f o r c u r r e n t s t a t e ”
c l a s s Waiting ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Waiting : B r o a d c a s t i n g c h e e s e s m e l l ”
d e f next ( s e l f , i n p u t ) :
# 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 :
self . transitions = {
MouseAction . a p p e a r s : MouseTrap . l u r i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Luring ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Luring : P r e s e n t i n g Cheese , door open ”
d e f next ( s e l f , i n p u t ) :
# 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 :
self . transitions = {
MouseAction . e n t e r s : MouseTrap . t r a p p i n g ,
MouseAction . runsAway : MouseTrap . w a i t i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Trapping ( StateT ) :
d e f run ( s e l f ) :

p r i n t ” Trapping : C l o s i n g door ”
d e f next ( s e l f , i n p u t ) :
# 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 :

62
self . transitions = {
MouseAction . e s c a p e s : MouseTrap . w a i t i n g ,
MouseAction . trapped : MouseTrap . h o l d i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Holding ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Holding : Mouse caught ”
d e f next ( s e l f , i n p u t ) :
# 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 :
self . transitions = {
MouseAction . removed : MouseTrap . w a i t i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s MouseTrap ( StateMachine ) :
def init ( self ):
# I n i t i a l state
StateMachine . i n i t ( s e l f , MouseTrap . w a i t i n g )
# Static variable i n i t i a l i z a t i o n :
MouseTrap . w a i t i n g = Waiting ( )
MouseTrap . l u r i n g = Luring ( )
MouseTrap . t r a p p i n g = Trapping ( )
MouseTrap . h o l d i n g = Holding ( )
moves = map( s t r i n g . s t r i p ,
open ( ” . . / mouse/MouseMoves . t x t ” ) . r e a d l i n e s ( ) )
mouseMoves = map( MouseAction , moves )
MouseTrap ( ) . r u n A l l ( mouseMoves )
#:˜
El resto del código es idéntico – la diferencia está en los métodos
next() y la clase StateT.

Si usted tiene que crear y mantener una gran cantidad de clases


State, este enfoque es una mejora, ya que es más fácil de leer de
forma rápida y comprender las transiciones de estado de mirar la
tabla.

63
7.4 Table-Driven State Machine
La ventaja del diseño anterior es que toda la información acerca de
un estado, incluyendo la información de transición de estado, se en-
cuentra dentro de la clase propio Estado. Esto es generalmente un
buen principio de diseño.

Sin embargo, en una state machine : máquina de estados pura,


la máquina puede ser completamente representada por una única
tabla de transición de estados. Esto tiene la ventaja de localizar
toda la información sobre la máquina de estados en un solo lugar, lo
que significa que usted puede con mayor facilidad crear y mantener
la tabla basada en un diagrama de transición de estados clásica.

El diagrama clásico de transición-de-estados utiliza un cı́rculo


para representar cada estado, y las lı́neas del state señalando a todos
los Estados en que state puede trasladarse. Cada lı́nea de transición
se anota con condiciones para la transición y una acción durante la
transición. Aquı́ esta lo que parece:

(Diagrama State Machine simple)

Objetivos:

• Traducción directa del diagrama de estado


• Vector del cambio: la representación diagrama de estado
• Implementación 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:
• Estados son triviales – ninguna información o funciones / datos,
sólo una identidad.
• No como el patrón State!
• La máquina regula el paso de un estado a otro.

64
• Al igual que en flyweight : peso mosca
• Cada estado puede pasar a muchos otros
• Funciones de Estado y de acción también deben ser externos a
los estados
• Centralizar la descripción en una sola tabla que contiene todas
las variaciones, para facilitar la configuración.

Ejemplo:
• State Machine y Table-Driven Code
• Implementa una máquina expendedora
• Utiliza varios, otros patrones
• Separa código común state-machine de aplicación especı́fica
(como método de plantilla)
• Cada entrada causa buscar una solución apropiada (como ca-
dena de responsabilidad)
• Pruebas y transiciones se encapsulan en objetos de función (ob-
jetos que contienen funciones)
• Restricción de Java: los métodos no son objetos de primera
clase.

65
7.4.1 La clase State

La clase State es claramente diferente de antes, ya que es en reali-


dad sólo un marcador de posición con un nombre. Por lo tanto, no
se hereda de las clases State anteriores:

# c04 : s t a t e m a c h i n e 2 : S t a t e . py

c l a s s State :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
# :˜

7.4.2 Condiciones para la transición

En el diagrama de transición de estados, una entrada se pone a


prueba para ver si satisface las condiciones necesarias para trans-
ferir al Estado bajo cuestión. Como antes, el Input es sólo una
interfaz de etiquetado:

66
# c04 : s t a t e m a c h i n e 2 : Input . py
# I n p u t s t o a s t a t e machine

c l a s s Input : p a s s
# :˜
La Condition evalúa el Input para decidir si esta fila en la tabla
es la transición correcta:

# c04 : s t a t e m a c h i n e 2 : C o n d i t i o n . py
# C o n d i t i o n f u n c t i o n o b j e c t f o r s t a t e machine

c l a s s Condition :
boolean condition ( input ) :
a s s e r t 1 , ” c o n d i t i o n ( ) not implemented ”
# :˜

7.4.3 Acciones de transición

Si Condition devuelve true, entonces se hace la transición a un


nuevo estado, y como se hace esa transición algún tipo de acción
se produce (en el diseño anterior de state machine : máquina de
estado, éste era el métodorun( )).

# c04 : s t a t e m a c h i n e 2 : T r a n s i t i o n . py
# T r a n s i t i o n f u n c t i o n o b j e c t f o r s t a t e machine

class Transition :
def 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 ”
# :˜

7.5 La tabla
Con estas clases en el lugar, podemos establecer una tabla de 3 di-
mensiones, donde cada fila describe completamente un estado. El
primer elemento en la fila es el estado actual, y el resto de los ele-
mentos son cada uno una fila indicando lo que el tipo de la entrada
puede ser, la condición que debe ser satisfecha para que este cambio

67
de estado a ser la correcta, la acción que ocurre durante la transición,
y el nuevo estado para moverse dentro. Observe que el objeto Input
no sólo se utiliza para su tipo, también es un objeto Messenger que
lleva la información a los objetos Condition y Transition :

{( C u r r e n t S t a t e , InputA ) : ( ConditionA , TransitionA , NextA ) ,


( C u r r e n t S t a t e , InputB ) : ( ConditionB , Tr ansit ionB , NextB ) ,
( C u r r e n t S t a t e , InputC ) : ( ConditionC , TransitionC , NextC ) ,
...
}

7.5.1 La máquina básica

# c04 : s t a t e m a c h i n e 2 : StateMachine . py
# A t a b l e −d r i v e n s t a t e machine

c l a s s StateMachine :
def 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 . state = initialState
s e l f . t r a n s i t i o n T a b l e = tranTable

def nextState ( s e l f , input ) :

I t e r a t o r i t =(( L i s t )map . g e t ( s t a t e ) ) . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) ) :
Object [ ] t r a n = ( Object [ ] ) i t . next ( )
i f ( i n p u t == t r a n [ 0 ] | |
i n p u t . g e t C l a s s ( ) == t r a n [ 0 ] ) :
i f ( t r a n [ 1 ] != n u l l ) :
Condition c = ( Condition ) tran [ 1 ]
i f ( ! c . condition ( input ))
c o n t i n u e #F a i l e d t e s t

i f ( t r a n [ 2 ] != n u l l )
(( Transition ) tran [ 2 ] ) . t r a n s i t i o n ( input )
stat e = ( State ) tran [ 3 ]
return

throw RuntimeException (

68
” Input not s u p p o r t e d f o r c u r r e n t s t a t e ” )

# :˜
7.6 Simple máquina expendedora

# c04 : vendingmachine : VendingMachine . py


# Demonstrates use o f StateMachine . py
import s y s
s y s . path += [ ’ . . / s t a t e m a c h i n e 2 ’ ]
import StateMachine

c l a s s State :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
State . q u i e s c e n 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 . u n a v a i l a b l e = State (” Unavailable ”)
S t a t e . wantMore = S t a t e ( ”Want More ? ” )
S t a t e . noChange = S t a t e ( ” Use Exact Change Only ” )
S t a t e . makesChange = S t a t e ( ” Machine makes change ” )

c l a s s HasChange :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name

HasChange . y e s = HasChange ( ” Has change ” )


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

c l a s s ChangeAvailable ( StateMachine ) :
def init ( self ):
StateMachine . i n i t ( S t a t e . makesChange , {
# Current s t a t e , i n p u t
( S t a t e . makesChange , HasChange . no ) :
# t e s t , t r a n s i t i o n , next s t a t e :
( n u l l , n u l l , S t a t e . noChange ) ,
( S t a t e . noChange , HasChange . y e s ) :
( n u l l , n u l l , S t a t e . noChange )
})

69
c l a s s Money :
def i n i t ( s e l f , name , v a l u e ) :
s e l f . name = name
s e l f . value = value
def s t r ( s e l f ) : r e t u r n s e l f . name
def getValue ( s e l f ) : return s e l f . value

Money . q u a r t e r = Money ( ” Quarter ” , 2 5)


Money . d o l l a r = Money ( ” D o l l a r ” , 100 )

c l a s s Quit :
def str ( s e l f ) : r e t u r n ” Quit ”

Quit . q u i t = Quit ( )

class Digit :
def i n i t ( s e l f , name , v a l u e ) :
s e l f . name = name
s e l f . value = value
def s t r ( s e l f ) : r e t u r n s e l f . name
def getValue ( s e l f ) : return s e l f . value

c l a s s F i r s t D i g i t ( Digit ) : pass
F 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 ( Digit ) : pass


S e c o n d D i g i t . one = S e c o n d D i g i t ( ” one ” , 0 )
S e c o n d D i g i t . two = S e c o n d D i g i t ( ” two ” , 1 )
SecondDigit . three = SecondDigit (” three ” , 2)
SecondDigit . four = SecondDigit (” four ” , 3)

c l a s s ItemSlot :
id = 0
def i n i t ( s e l f , price , quantity ) :
s e l f . price = price

70
s e l f . quantity = quantity
def s t r ( s e l f ) : r e t u r n ‘ I t e m S l o t . id ‘
def getPrice ( s e l f ) : return s e l f . price
def getQuantity ( s e l f ) : return s e l f . quantity
d e f d e c r Q u a n t i t y ( s e l f ) : s e l f . q u a n t i t y −= 1

c l a s s VendingMachine ( StateMachine ) :
c h a n g e A v a i l a b l e = ChangeAvailable ( )
amount = 0
FirstDigit f i r s t = null
ItemSlot [ ] [ ] items = ItemSlot [ 4 ] [ 4 ]

# Conditions :
d e f notEnough ( s e l f , i n p u t ) :
i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
r e t u r n i t e m s [ i 1 ] [ i 2 ] . g e t P r i c e ( ) > amount

def itemAvailable ( s e l f , input ) :


i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
return items [ i 1 ] [ i 2 ] . getQuantity ( ) > 0

def itemNotAvailable ( s e l f , input ) :


return ! itemAvailable . condition ( input )
#i 1 = f i r s t . g e t V a l u e ( )
#i 2 = i n p u t . g e t V a l u e ( )
#r e t u r n i t e m s [ i 1 ] [ i 2 ] . g e t Q u a n t i t y ( ) == 0

# Transitions :
def 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 ( )
ItemSlot i s = items [ i1 ] [ i2 ]
print (
” C l e a r i n g s e l e c t i o n : item ” + i s +
” costs ” + is . getPrice () +
” and has q u a n t i t y ” + i s . g e t Q u a n t i t y ( ) )
f i r s t = null

71
def dispense ( s e l f , input ) :
i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
ItemSlot i s = items [ i1 ] [ i2 ]
p r i n t ( ” D i s p e n s i n g item ” +
is + ” costs ” + is . getPrice () +
” and has q u a n t i t y ” + i s . g e t Q u a n t i t y ( ) )
items [ i 1 ] [ i 2 ] . decrQuantity ( )
p r i n t ( ” Quantity ” +
i s . getQuantity ( ) )
amount −= i s . g e t P r i c e ( )
p r i n t ( ” Amount r e m a i n i n g ” +
amount )

d e f showTotal ( s e l f , i n p u t ) :
amount += ( ( Money ) i n p u t ) . g e t V a l u e ( )
p r i n t ” Total amount = ” + amount

d e f returnChange ( s e l f , i n p u t ) :
p r i n t ” Returning ” + amount
amount = 0

d e f showDigit ( s e l f , i n p u t ) :
f i r s t = ( F i r s t D i g i t ) input
p r i n t ” F i r s t D i g i t= ”+ f i r s t

def init ( self ):


StateMachine . i n i t ( s e l f , S t a t e . q u i e s c e n t )
f o r ( i n t i = 0 i < i t e m s . l e n g t h i ++)
f o r ( i n t j = 0 j < i t e m s [ i ] . l e n g t h j ++)
i t e m s [ i ] [ j ] = I t e m S l o t ( ( j +1)∗25 , 5 )
items [ 3 ] [ 0 ] = ItemSlot (25 , 0)
b u i l d T a b l e ( Object [ ] [ ] [ ] {
: : S t a t e . q u i e s c e n t , # 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 , n u l l ,
showTotal , S t a t e . c o l l e c t i n g ,

72
: : S t a t e . c o l 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 . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: Money . c l a s s , n u l l ,
showTotal , S t a t e . c o l l e c t i n g ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
: : S t a t e . 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 . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: S e c o n d D i g i t . c l a s s , notEnough ,
clearSelection , State . c o l l e c t i n g ,
: SecondDigit . c l a s s , itemNotAvailable ,
clearSelection , State . unavailable ,
: SecondDigit . class , itemAvailable ,
d i s p e n s e , S t a t e . wantMore ,
: : S t a t e . u n a v a i l a b 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 . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
: : S t a t e . 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 . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
)
# :˜
7.7 Prueba de la máquina

# c04 : vendingmachine : VendingMachineTest . py


# Demonstrates use o f StateMachine . py

vm = VendingMachine ( )
f o r input in [

73
Money . q u a r t e r ,
Money . q u a r t e r ,
Money . d o l l a r ,
F i r s t D i g i t . A,
S e c o n d D i g i t . two ,
F i r s t D i g i t . A,
S e c o n d D i g i t . 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,
S e c o n d D i g i t . one ,
Quit . q u i t ] :
vm . n e x t S t a t e ( i n p u t )
# :˜
7.8 Herramientas
Otro enfoque, como su state machine : máquina de estado se hace
más grande, es el uso de una herramienta de automatización medi-
ante el cual configura una tabla y deja que la herramienta genere el
código state machine para usted. Esto puede ser creado por sı́ mismo
utilizando un lenguaje como Python, pero también hay, herramien-
tas libres de código abierto como Libero, en http://www.imatix.com

7.9 Ejercicios
1. Crear un ejemplo del ”proxy virtual”.

2. Crear un ejemplo del proxy ”Referencia Inteligente” donde


guarda la cuenta del número de llamadas a métodos a un objeto en
particular.

3. Crear un programa similar a ciertos sistemas DBMS que sólo


permiten un cierto número de conexiones en cualquier momento.
Para implementar esto, utilizar un sistema de singleton al igual que
controla el número de objetos ”conexión” que crea. Cuando un
usuario ha terminado con una conexión, el sistema debe ser infor-
mado de manera que pueda comprobar que la conexión volverá a
ser reutilizado. Para garantizar esto, proporcionar un objeto proxy
en lugar de una referencia a la conexión real, y diseñar el proxy de

74
manera que hará que la conexión para ser liberado de nuevo al sis-
tema.

4. Usando State, hacer una clase llamada UnpredictablePer-


son que cambia el tipo de respuesta a su método hello( ) depen-
diendo de qué tipo de Mood está dentro. Añadir un tipo adicional
de Mood llamado Prozac.

5. Crear una implementación sencilla de escritura copy-on

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

7. Crear un sistema StateMachine mediante el cual el estado


actual junto con la información de entrada determina el siguiente
estado en que el sistema estará. Para hacer esto, cada estado debe
almacenar una referencia de nuevo al objeto proxy (el controlador
de estado) de modo que pueda solicitar el cambio de estado. Use
un HashMap para crear una tabla de estados, donde la clave es
un String nombrando el nuevo estado y el valor es el nuevo objeto
de estado. Dentro de cada subclase state reemplazar un método
nextState( ) que tiene su propia tabla de transición de estados.
The input to nextState( ) debe ser una sola palabra que sale de
un archivo de texto que contiene una palabra por lı́nea.

8. Modificar el ejercicio anterior para que la state machine pueda


ser configurada mediante la creación / modificación de una sola ma-
triz multidimensional.

9- Modificar el ejercicio “mood” de la sesión anterior para que


se convierta en una state machine : máquina de estado usando
StateMachine.java

10. Crear un sistema elevador de state machine utilizando StateMa-


chine.java

11. Crear un sistema de calefacción / aire acondicionado usando


StateMachine.java

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

75
jetos, al igual que una fábrica, excepto que la función generador no
requiere ningún argumento. Cree un MouseMoveGenerator que
produce acciones correctas MouseMove como salidas cada vez que
la función generador se llama (es decir, el mouse debe moverse en la
secuencia apropiada, por lo que los movimientos posibles se basan
en el movimiento anterior – esto es otra state machine). Agregue
un método iterator( ) para producir un iterador, pero este método
debe tomar un argumento int que especifica el número de movimien-
tos a producir antes de hasNext( ) que retorna false.

8 X: Decoradores:
Selección Tipo dinámico
El uso de objetos en capas para añadir de forma dinámica
y transparente responsabilidades a los objetos individuales
se conoce como el patrón decorator:decorador.

Se utiliza cuando la subclasificación crea demasiadas (o inflexi-


bles) clases.

Todos los decoradores que envuelven alrededor del objeto original


deben tener la misma interfaz básica.

Dynamic proxy/surrogate? : Proxy / sustituto Dinámico?

Esto explica la estructura de herencia singular.

Tradeoff: la codificación es más complicado cuando se utiliza


decoradores.

76
8.1 Estructura Decorador basico

8.2 Un ejemplo café


Considere la posibilidad de bajar a la cafeterı́a local, BeanMeUp,
por un café. Nor11malmente hay muchas bebidas diferentes que
se ofrecen expresos, cafés con leche, tés, cafés helados, chocolate
caliente para nombrar unos pocos, ası́ como una serie de extras (que
cuestan extra también), tales como la crema batida o una inyección
extra de expreso. Usted también puede hacer ciertos cambios en su
bebida, sin costo adicional, como pedir café descafeinado en lugar
de café regular.

Con bastante claridad si vamos a modelar todas estas bebidas y


combinaciones, habrá diagramas de clases de tamaño variable. Ası́
que para mayor claridad nosotros sólo consideraremos un subcon-
junto de los cafés: Expreso, café vienés, Caffe Latte, Cappuccino y
Café Mocha. Incluiremos 2 extras - crema batida (”batida”) y una
inyección extra de café expreso; y tres cambios - descafeinado, leche
al vapor (”húmeda”) y espuma de leche (”seco”).

8.3 Clase para cada combinación


Una solución es crear una clase individual para cada combinación.Cada
clase describe la bebida y es responsable por el costo, etc. El menú

77
resultante es enorme, y una parte del diagrama de clases serı́a algo
como esto:

Aqui esta una de las combinaciones, una implementación simple


de un Cappuccino:

c l a s s Cappuccino :
def init ( self ):
s e l f . cost = 1
s e l f . d e s c r i p t i o n = ” Cappucino ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description
La clave para el uso de este método es encontrar la combinación
particular que desea. Ası́, una vez que haya encontrado la bebida
que le gustarı́a, aquı́ es cómo usted lo utilizarı́a, como se muestra en
la clase CoffeeShop en el siguiente código:

#: cX : d e c o r a t o r : n o d e c o r a t o r s : CoffeeShop . py
# C o f f e e example with no d e c o r a t o r s

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

78
c l a s s EspressoConPanna : p a s s

c l a s s Cappuccino :
def init ( self ):
s e l f . cost = 1
s e l f . d e s c r i p t i o n = ” Cappucino ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description

class CappuccinoDecaf : p a s s
class CappuccinoDecafWhipped : p a s s
class CappuccinoDry : p a s s
class CappuccinoDryWhipped : p a s s
class CappuccinoExtraEspresso : p a s s
class CappuccinoExtraEspressoWhipped : p a s s
class CappuccinoWhipped : p a s s

c l a s s CafeMocha : p a s s
c l a s s CafeMochaDecaf : p a s s
c l a s s CafeMochaDecafWhipped :
def init ( self ):
s e l f . cost = 1.25
self . description = \
” Cafe Mocha d e c a f whipped cream ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description

class CafeMochaExtraEspresso : p a s s
class CafeMochaExtraEspressoWhipped : p a s s
class CafeMochaWet : p a s s
class CafeMochaWetWhipped : p a s s
class CafeMochaWhipped : p a s s

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

79
class CafeLatteDecafWhipped : p a s s
class CafeLatteExtraEspresso : pass
class CafeLatteExtraEspressoWhipped : p a s s
class CafeLatteWet : p a s s
class CafeLatteWetWhipped : p a s s

c l a s s CafeLatteWhipped : p a s s
c a p p u c c i n o = Cappuccino ( )
p r i n t ( c a p p u c c i n o . g e t D e s c r i p t i o n ( ) + ” : \$” +
‘ cappuccino . getCost ( ) ‘ )

cafeMocha = CafeMochaDecafWhipped ( )
p r i n t ( cafeMocha . g e t D e s c r i p t i o n ( )
+ ” : \$” + ‘ cafeMocha . g e t C o s t ( ) ‘ )
#:˜
y aquı́ está la salida correspondiente:

Cappucino : \ $1 . 0 Cafe Mocha d e c a f whipped cream : \ $1 . 2 5


Se puede ver que la creación de la combinación particular que
desea es fácil, ya que sólo está creando una instancia de una clase.
Sin embargo, hay una serie de problemas con este enfoque. En
primer lugar, las combinaciones son fijadas estáticamente para que
cualquier combinación de un cliente quizá desee ordenar necesite ser
creado por adelantado. En segundo lugar, el menú resultante es
tan grande que la búsqueda de su combinación particular es difı́cil
y consume mucho tiempo.

8.4 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 el
cliente combine los componentes para describir un café en particular.

Con el fin de hacer esto mediante programación, utilizamos el


patrón Decorador. Un decorador añade la responsabilidad de un
componente envolviéndolo, pero el decorador se ajusta a la interfaz
del componente que encierra, por lo que la envoltura es transpar-
ente. Los Decoradores también se pueden anidar sin la pérdida de

80
esta transparencia.

Métodos invocados en el Decorador a su vez pueden invocar


métodos en el componente, y puede realizar, por supuesto, el proce-
samiento antes o después de la invocación.

Ası́ que si añadimos los métodos getTotalCost() y getDescrip-


tion() a la interfaz DrinkComponent, un Espresso se ve ası́:

c l a s s Espresso ( Decorator ) :
cost = 0.75 f
description = ” espresso ”
p u b l i c E s p r e s s o ( DrinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , component )

def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + c o s t

def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) +
description
Usted combina los componentes para crear una bebida de la sigu-
iente manera, como se muestra en el siguiente código:

#: cX : d e c o r a t o r : a l l d e c o r a t o r s : CoffeeShop . py
# C o f f e e example u s i n g d e c o r a t o r s

c l a s s DrinkComponent :

81
def getDescription ( s e l f ) :
return s e l f . c l a s s . name
def getTotalCost ( s e l f ) :
return s e l f . c l a s s . cost

c l a s s Mug( DrinkComponent ) :
cost = 0.0

c l a s s D e c o r a t o r ( DrinkComponent ) :
def i n i t ( s e l f , drinkComponent ) :
s e l f . component = drinkComponent
def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + \
DrinkComponent . g e t T o t a l C o s t ( s e l f )
def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) + \
’ ’ + DrinkComponent . g e t D e s c r i p t i o n ( s e l f )

c l a s s Espresso ( Decorator ) :
cost = 0.75
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s Decaf ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s FoamedMilk ( D e c o r a t o r ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s SteamedMilk ( D e c o r a t o r ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Whipped ( D e c o r a t o r ) :
cost = 0.25

82
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Chocolate ( Decorator ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c a p p u c c i n o = E s p r e s s o ( FoamedMilk (Mug ( ) ) )
p r i n t cappuccino . g e t D e s c r i p t i o n ( ) . s t r i p ( ) + \
” : \$” + ‘ c a p p u c c i n o . g e t T o t a l C o s t ( ) ‘

cafeMocha = E s p r e s s o ( SteamedMilk ( C h o c o l a t e (
Whipped ( Decaf (Mug ( ) ) ) ) ) )

p r i n t cafeMocha . g e t D e s c r i p t i o n ( ) . s t r i p ( ) + \
” : \$” + ‘ cafeMocha . g e t T o t a l C o s t ( ) ‘
#:˜
Este enfoque, sin duda, proporciona la mayor flexibilidad y el
menú más pequeño. Usted tiene un pequeño número de compo-
nentes para elegir, pero el montaje de la descripción del café en-
tonces se vuelve bastante arduo.

Si quiere describir un capuchino plain, se crea con

plainCap = E s p r e s s o ( FoamedMilk (Mug ( ) ) )


Creando un Café Mocha descafeinado con crema batida requiere
una descripción aún más larga.

8.5 Compromiso
El enfoque anterior toma demasiado tiempo para describir un café.
También habrá ciertas combinaciones que va a describir con regu-
laridad, y serı́a conveniente tener una forma rápida de describirlos.

El tercer enfoque es una mezcla de los 2 primeros enfoques, y


combina flexibilidad con la facilidad de uso. Este compromiso se
logra mediante la creación de un menú de tamaño razonable de op-
ciones básicas, que a menudo funcionan exactamente como son, pero

83
si querı́a decorarlos (crema batida, descafeinado etc), entonces usted
usarı́a decoradores para hacer las modificaciones. Este es el tipo de
menú que se le presenta en la mayorı́a de tiendas de café.

Aquı́ está cómo crear una selección básica, ası́ como una selección
decorada:

#: cX : d e c o r a t o r : compromise : CoffeeShop . py
# C o f f e e example with a compromise o f b a s i c
# c o m b i n a t i o n s and d e c o r a t o r s

c l a s s DrinkComponent :
def getDescription ( s e l f ) :
return s e l f . c l a s s . name
def getTotalCost ( s e l f ) :
return s e l f . c l a s s . cost

c l a s s E s p r e s s o ( DrinkComponent ) :
cost = 0.75

c l a s s EspressoConPanna ( DrinkComponent ) :
cost = 1.0

c l a s s Cappuccino ( DrinkComponent ) :
cost = 1.0

c l a s s C a f e L a t t e ( DrinkComponent ) :

84
cost = 1.0

c l a s s CafeMocha ( DrinkComponent ) :
cost = 1.25

c l a s s D e c o r a t o r ( DrinkComponent ) :
def i n i t ( s e l f , drinkComponent ) :
s e l f . component = drinkComponent
def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + \
DrinkComponent . g e t T o t a l C o s t ( s e l f )
def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) + \
’ ’ + DrinkComponent . g e t D e s c r i p t i o n ( s e l f )

c l a s s ExtraEspresso ( Decorator ) :
cost = 0.75
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s Whipped ( D e c o r a t o r ) :
cost = 0.50
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s Decaf ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s Dry ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

c l a s s Wet( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )

85
c a p p u c c i n o = Cappuccino ( )
p r i n t c a p p u c c i n o . g e t D e s c r i p t i o n ( ) + ” : \$” + \
‘ cappuccino . getTotalCost ( ) ‘

cafeMocha = Whipped ( Decaf ( CafeMocha ( ) ) )


p r i n t cafeMocha . g e t D e s c r i p t i o n ( ) + ” : \$” + \
‘ cafeMocha . g e t T o t a l C o s t ( ) ‘
#:˜
Usted puede ver que creando una selección básica es rápido y
fácil, lo cual tiene sentido ya que serán descritos con regularidad.
Describiendo una bebida decorada es más trabajo que cuando se
utiliza una clase por combinación, pero claramente menos trabajo
que cuando solo usando decoradores.

El resultado final no es demasiadas clases, ni tampoco demasi-


ados decoradores. La mayorı́a de las veces es posible alejarse sin
utilizar ningún decorador en absoluto, ası́ tenemos los beneficios de
ambos enfoques.

8.6 Otras consideraciones


¿Qué sucede si decidimos cambiar el menú en una etapa poste-
rior, tal como mediante la adición de un nuevo tipo de bebida? Si
hubiéramos utilizado la clase por enfoque de combinación, el efecto
de la adición de un ejemplo adicional como Syrup serı́a un crec-
imiento exponencial en el número de clases. Sin embargo, las impli-
caciones para todos los enfoques decorador o de compromiso son los
mismos Se crea una clase extra.

¿Qué 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 una
clase para cada combinación significa que usted necesita cambiar un
método en cada clase, y ası́ mantener muchas clases. Mediante el uso
de decoradores, el mantenimiento se reduce mediante la definición
de la lógica en un solo lugar.

86
8.7 Ejercicios
1. Añadir una clase Syrup al enfoque decorador descrito anterior-
mente. A continuación, cree un Café Latte (usted necesitará usar
la leche al vapor con un expreso) con Syrup.
2. Repita el ejercicio 1 para el enfoque de compromiso.
3. Implementar el patrón decorador para crear un restaurante de
Pizza, el cual tenga un menú de opciones, ası́ como la opción de
diseñar su propia pizza. Siga el enfoque de compromiso para crear
un menú que consiste en una Margherita, hawaianas, Regina, y
pizzas vegetarianas, con relleno (decoradores) de ajo, aceitunas,
espinacas, aguacate, queso feta y Pepperdews. Crear una pizza
hawaiana, ası́ como un Margherita decorado con espinacas, queso
feta, Pepperdews y aceitunas.

87
9 Y: Iteradores:
Algoritmos de desacoplamiento de contene-
dores
Este capı́tulo no ha tenido traducción significativa todavı́a.

Alexander Stepanov pensó durante años sobre el problema de las


técnicas de programación genéricas antes de crear el STL (junto con
Dave Musser). Llegó a la conclusión de que todos los algoritmos
están definidos en las estructuras algebraicas – lo que llamarı́amos
contenedores.
En el proceso, el se dio cuenta que los iteradores son fundamen-
tales para el uso de algoritmos, porque desacoplan los algoritmos
del tipo especifico de contenedor con que el algoritmo actualmente
podrı́a estar trabajando. Esto significa que usted puede describir
el algoritmo sin preocuparse de la secuencia particular en que está
operando. Más generalmente, cualquier código que usted escribe
utilizando iteradores es desacoplado de la estructura de datos que
el código está manipulando, y por lo tanto su código es más general
y reutilizable.

El uso de iteradores también aplı́a su código en el campo de pro-


gramación funcional, cuyo objetivo es describir lo que un programa
está haciendo a cada paso en lugar de cómo lo está haciendo. Es
decir, usted dice usted dice ”tipo” en lugar de describir el tipo. El
objetivo del STL de C ++ fue proporcionar este enfoque genérico
para la programación C ++ (cómo este enfoque exitoso será en re-
alidad, aún está por verse).

Si ha utilizado contenedores en Java (y es difı́cil escribir código


sin usarlos), usted tiene iteradores usados – en la forma del Enu-
meration en Java 1.0/1.1 y el Iterator en Java 2.0. Ası́ que usted
ya debe estar familiarizado con su uso general. Si no, consulte el
Capı́tulo 9, Holding Your Objects : Manteniendo Sus objetos, bajo
Iterators in Thinking in Java segunda edición (descargable gratuita-
mente desde www.Bruce Eckel.com).

Debido a que 2 contenedores en Java dependen en gran medida


de los iteradores, se convierten en excelentes candidatos para las

88
técnicas de programación genéricas / funcionales. Este capı́tulo ex-
plorará estas técnicas mediante la conversión de los algoritmos de
STL para Java, para su uso con la librerı́a de contenedor Java 2.

9.1 Iteradores con seguridad de tipos


En Thinking in Java, segunda edición, Muestro la creación de un
contenedor de tipo seguro que sólo aceptará un tipo particular de
objeto. Un lector, Linda Pazzaglia, pidió el otro componente de
tipo seguro obvio, un iterador que trabajarı́a con los contenedores
básicos java.util, pero imponer la restricción de que el tipo de ob-
jetos sobre los que itera sea de un tipo particular.

Si Java siempre incluye un mecanismo de plantilla, este tipo de


iterador tendrá la ventaja añadida de ser capaz de devolver un tipo
especı́fico de objeto, pero sin las plantillas se ve obligado a retornar
Objects genéricos, o requerir un poco de codificación manual para
cada tipo que desea iterar. Tomaré el enfoque anterior.

Una segunda decisión de diseño involucra el tiempo que el tipo


de objeto es determinado. Un enfoque consiste en tomar el tipo del
primer objeto que el iterador encuentra, pero esto es problemático
debido a que los contenedores pueden arreglar de nuevo los objetos
de acuerdo con un mecanismo de ordenamiento interno (tal como
una tabla hash15 ) y por lo tanto es posible obtener diferentes resul-
tados de una iteración a la siguiente. El enfoque seguro es exigir al
usuario establecer el tipo durante la construcción del iterador.

Por último, ¿cómo construir el iterador? No podemos reescribir


las librerı́as de clases Java existentes que ya producen Enumer-
ations y Iterators. Sin embargo, podemos utilizar el patrón de
diseño Decorator, y crear una clase que simplemente envuelve el
Enumeration o Iterator que se produce, generando un nuevo ob-
jeto que tiene el comportamiento de iteración que queremos (que
es, en este caso, lanzar un RuntimeException si se encuentra un
tipo incorrecto) pero con la misma interfaz que el Enumeration
15 hash: picadillo, picar.

hash table : tabla de picadillo

89
original o Iterator, de modo que se puede utilizar en los mismos
lugares (puede argumentar que esto es en realidad un patrón Proxy,
pero es más probable Decorator debido a su intención). Aquı́ está
el código:

# u t i l : T y p e d I t e r a t o r . 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 imp
p r i v a t e C l a s s type
def i n i t ( s e l f , I t e r a t o r i t , C l a s s type ) :
imp = i t
s e l f . type = type

d e f hasNext ( s e l f ) :
r e t u r n imp . hasNext ( )

d e f remove ( s e l f ) : imp . remove ( )


d e f next ( s e l f ) :
Object o b j = imp . next ( )
i f ( ! type . i s I n s t a n c e ( o b j ) )
throw C l a s s C a s t E x c e p t i o n (
” T y p e d I t e r a t o r f o r type ” + type +
” e n c o u n t e r e d type : ” + o b j . g e t C l a s s ( ) )
return obj
# :˜

10 5: Fábricas:
encapsular
la creación de objetos
Cuando descubre que es necesario agregar nuevos tipos a un sistema,
el primer paso más sensato es utilizar el polimorfismo para crear una
interfaz común a esos nuevos tipos. Esto separa el resto del código
en el sistema desde el conocimiento de los tipos especı́ficos que está
agregando. Nuevos tipos pueden añadirse sin molestar código exis-
tente ... o al menos eso parece. Al principio parecerı́a que el único
lugar que necesita cambiar el código en tal diseño es el lugar donde

90
usted hereda un nuevo tipo, pero esto no es del todo cierto. Usted
todavı́a debe crear un objeto de su nuevo tipo, y en el punto de
la creación debe especificar el constructor exacto a utilizar. Ası́, si
el código que crea objetos se distribuye a través de su aplicación,
usted tiene el mismo problema cuando añade nuevos tipos — usted
todavı́a debe perseguir todos los puntos de su código en asuntos de
tipos. Esto sucede para ser la creation : creación del tipo que im-
porta en este caso en lugar del use : uso del tipo (que es atendido
por el polimorfismo), pero el efecto es el mismo : la adición de un
nuevo tipo puede causar problemas.

La solución es forzar la creación de objetos que se produzca a


través de una factory : fábrica común antes que permitir que el
código creacional que se extendió por todo el sistema. Si todo el
código en su programa debe ir a través de esta fábrica cada vez que
necesita crear uno de sus objetos, entonces todo lo que debe hacer
cuando añada un nuevo objeto es modificar la fábrica.

Ya que cada programa orientado a objetos crea objetos, y puesto


que es muy probable que se extienda su programa mediante la
adición de nuevos tipos, sospecho que las fábricas pueden ser los
tipos más universalmente útiles de los patrones de diseño.

10.1 Simple método de fábrica


Como ejemplo, vamos a revisar el sistema Shape. Un enfoque es
hacer la fábrica de un método static de la clase base:

#: c05 : s h a p e f a c t 1 : ShapeFactory1 . py
# A s i m p l e s t a t i c f a c t o r y method .
from future import g e n e r a t o r s
import random

c l a s s Shape ( o b j e c t ) :
# Create based on c l a s s name :
d e f f a c t o r y ( type ) :
#r e t u r n e v a l ( type + ” ( ) ” )
i f type == ” C i r c l e ” : r e t u r n C i r c l e ( )

91
i f type == ” Square ” : r e t u r n Square ( )
a s s e r t 1 , ”Bad shape c r e a t i o n : ” + type
factory = staticmethod ( factory )

c l a s s C i r c l e ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” C i r c l e . draw”
def erase ( s e l f ) : print ” Circle . erase ”

c l a s s Square ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” Square . draw”
d e 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 :


d e f shapeNameGen ( n ) :
t y p e s = Shape . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d random . c h o i c e ( t y p e s ) . n a m e

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

f o r shape i n s h a p e s :
shape . draw ( )
shape . e r a s e ( )
#:˜
factory( ) toma un argumento que le permite determinar qué
tipo 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 único otro código en el sistema que necesita ser cambiado cuando
untipo nuevo de Shape es agregado (los datos de inicialización de los
objetos presumiblemente vendrán de alguna parte fuera del sistema,
y no son una matriz de codificación fija como en el ejemplo anterior).

Tenga en cuenta que este ejemplo también muestra el nuevo


Python 2.2 staticmethod( ) técnica para crear métodos estáticos
en una clase.

También he utilizado una herramienta que es nueva en Python


2.2 llamada un generator : generador. Un generador es un caso espe-

92
cial de una fábrica: es una fábrica que no toma ningún argumento
con el fin de crear un nuevo objeto. Normalmente usted entrega
alguna información a una fábrica con el fin de decirle qué tipo de
objeto para crear y cómo crearlo,pero generador tiene algún tipo de
algoritmo interno que le dice qué y cómo construir. Esto ”genera de
la nada” en vez de estar diciendo qué crear.

Ahora, esto puede no parecer consistente con el código que usted


ve arriba:

f o r i i n shapeNameGen ( 7 )
parece que hay una inicialización teniendo lugar. Aquı́ es donde
un generador es un poco extraño – cuando llama una función que
contiene una declaración yield (yield es una nueva palabra clave
que determina que una función es un generador), esa función en
realidad devuelve un objeto generador que tiene un iterador. Este
iterador se utiliza implı́citamente en la sentencia for anterior, por
lo que parece que se está iterando a través de la función generador,
no lo que devuelve. Esto se hizo para la conveniencia de uso.

Por lo tanto, el código que usted escribe es en realidad una especie


de fábrica, que crea los objetos generadores que hacen la generación
real. Usted puede utilizar el generador de forma explı́cita si quiere,
por ejemplo:

gen = shapeNameGen ( 7 )
p r i n t gen . next ( )
Ası́ que next() es el método iterador que es realmente llamado
a generar el siguiente objeto, y que no toma ningún argumento.
shapeNameGen( ) es la fábrica, y gen es el generador.

En el interior del generador de fábrica, se puede ver la llamada a


subclasses ( ), que produce una lista de referencias a cada una
de las subclases de Shape (que debe ser heredado de object para
que esto funcione). Debe tener en cuenta, sin embargo, que esto sólo
funciona para el primer nivel de herencia de Item, ası́ que si usted
fuera a heredar una nueva clase de Circle, no aparecerı́a en la lista
generada por subclasses ( ). Si necesita crear una jerarquı́a más

93
profunda de esta manera, debe recurse16 la lista subclasses ( ).

También tenga en cuenta que, en shapeNameGen( ) la declaración

t y p e s = Shape . subclasses ()
Sólo se ejecuta cuando se produce el objeto generador; cada vez
que se llama al método next( ) de este objeto generador (que, como
se señaló anteriormente, puede suceder de manera implı́cita), sólo
se ejecuta el código en el bucle for, por lo que no tiene ejecución
derrochadora (como lo harı́a si esto fuera una función ordinaria).

10.2 Fábricas polimórficas


El método estatico factory( ) en el ejemplo anterior obliga a todas
las operaciones de creación que se centran en un solo lugar, ası́ que
es el único lugar que necesita cambiar el código. Esto es ciertamente
una solución razonable, ya que arroja un cuadro alrededor del pro-
ceso de creación de objetos. Sin embargo, el libro Design Patterns
enfatiza en que la razón para el patrón de Factory Method es para
que diferentes tipos de fábricas pueden ser subclases de la fábrica
básica (el diseño anterior se menciona como un caso especial). Sin
embargo, el libro no proporciona un ejemplo, pero en su lugar jus-
tamente repite el ejemplo utilizado para el Abstract Factory (usted
verá un ejemplo de esto en la siguiente sección). Aquı́ ShapeFac-
tory1.py está modificado por lo que los métodos de fábrica están
en una clase separada como funciones virtuales. Observe también
que las clases especı́ficas de Shape se cargan dinámicamente en la
demanda:

#: c05 : s h a p e f a c t 2 : ShapeFactory2 . py
# Polymorphic f a c t o r y methods .
from future import g e n e r a t o r s
import random

c l a s s ShapeFactory :
16 utilizar la recursión en la programación, usar funciones recursivas (que se repiten) en la

creación de un programa

94
f a c t o r i e s = {}
d e f addFactory ( id , s h a p e F a c t o r y ) :
ShapeFactory . f a c t o r i e s . put [ i d ] = s h a p e F a c t o r y
addFactory = s t a t i c m e t h o d ( addFactory )
# A Template Method :
def createShape ( id ) :
i f not ShapeFactory . f a c t o r i e s . h a s k e y ( i d ) :
ShapeFactory . f a c t o r i e s [ i d ] = \
e v a l ( i d + ’ . Factory ( ) ’ )
r e t u r n ShapeFactory . f a c t o r i e s [ i d ] . c r e a t e ( )
createShape = staticmethod ( createShape )

c l a s s Shape ( o b j e c t ) : p a s s

c l a s s C i r c l e ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” C i r c l e . draw”
def erase ( s e l f ) : print ” Circle . erase ”
c l a s s Factory :
def create ( s e l f ) : return Circle ()

c l a s s Square ( Shape ) :
d e f draw ( s e l f ) :
p r i n t ” Square . draw”
def erase ( s e l f ) :
p r i n t ” Square . e r a s e ”
c l a s s Factory :
d e f c r e a t e ( s e l f ) : r e t u r n Square ( )

d e f shapeNameGen ( n ) :
t y p e s = Shape . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d random . c h o i c e ( t y p e s ) . n a m e

s h a p e s = [ ShapeFactory . c r e a t e S h a p e ( i )
f o r i i n shapeNameGen ( 7 ) ]

f o r shape i n s h a p e s :
shape . draw ( )
shape . e r a s e ( )

95
#:˜
Ahora el método de fábrica aparece en su propia clase, ShapeFac-
tory, como el método create( ). Los diferentes tipos de formas
deben crear cada uno su propia fábrica con un método create(
) para crear un objeto de su propio tipo. La creación real de for-
mas se realiza llamando ShapeFactory.createShape( ), que es un
método estático que utiliza el diccionario en ShapeFactory para
encontrar el objeto de fábrica apropiado basado en un identificador
que se le pasa. La fábrica se utiliza de inmediato para crear el objeto
shape : forma, pero se puede imaginar un problema más complejo
donde se devuelve el objeto de fábrica apropiado y luego utilizado
por la persona que llama para crear un objeto de una manera más
sofisticada. Ahora bien, parece que la mayor parte del tiempo usted
no necesita la complejidad del método de fábrica polimórfico, y un
solo método estático en la clase base (como se muestra en Shape-
Factory1.py) funcionará bien. Observe que ShapeFactory debe
ser inicializado por la carga de su diccionario con objetos de fábrica,
que tiene lugar en la cláusula de inicialización estática de cada una
de las implementaciones de forma.

10.3 Fábricas abstractas


El patrón Abstract Factory : Fábrica abstracta se parece a los ob-
jetos de fábrica que hemos visto anteriormente, con no uno, sino
varios métodos de fábrica. Cada uno de los métodos de fábrica crea
un tipo diferente de objeto. La idea es que en el punto de la creación
del objeto de fábrica, usted decide cómo se usarán todos los obje-
tos creados por esa fábrica. El ejemplo dado en Design Patterns
implementa portabilidad a través de diferentes interfaces gráficas
de usuario (GUI): crea un objeto de fábrica apropiada a la inter-
faz gráfica de usuario que se está trabajando, ya partir de entonces
cuando se pregunta por un menú, botón, control deslizante, etc. se
creará automáticamente la versión adecuada de ese tema para la
interfaz gráfica de usuario. De esta manera usted es capaz de ais-
lar, en un solo lugar, el efecto de cambiar de una interfaz gráfica de
usuario a otra.

Como otro ejemplo, supongamos que usted está creando un en-

96
torno de juego de uso general y usted quiere ser capaz de soportar
diferentes tipos de juegos. Ası́ es cómo puede parecer utilizando una
fábrica abstracta:

#: c05 : Games . py
# An example o f t he A b s t r a c t Factory p a t t e r n .

c l a s s Obstacle :
def action ( s e l f ) : pass

c l a s s Player :
def interactWith ( s e l f , obstacle ) : pass

c l a s s Kitty ( Player ) :
def interactWith ( s e l f , obstacle ) :
p r i n t ” K i t t y has e n c o u n t e r e d a ” ,
obstacle . action ()

c l a s s KungFuGuy( P l a y e r ) :
def interactWith ( s e l f , obstacle ) :
p r i n t ”KungFuGuy now b a t t l e s a ” ,
obstacle . action ()

c l a s s Puzzle ( Obstacle ) :
def action ( s e l f ) :
print ” Puzzle ”

c l a s s NastyWeapon ( O b s t a c l e ) :
def action ( s e l f ) :
p r i n t ”NastyWeapon”

# The A b s t r a c t Factory :
class GameElementFactory :
def makePlayer ( s e l f ) : p a s s
def makeObstacle ( s e l f ) : p a s s

# Concrete f a c t o r i e s :
c l a s s K i t t i e s A n d P u z z l e s ( GameElementFactory ) :
d e f makePlayer ( s e l f ) : r e t u r n K i t t y ( )

97
d e f makeObstacle ( s e l f ) : r e t u r n P u z z l e ( )

c l a s s KillAndDismember ( GameElementFactory ) :
d e f makePlayer ( s e l f ) : r e t u r n KungFuGuy ( )
d e f makeObstacle ( s e l f ) : r e t u r n NastyWeapon ( )

c l a s s GameEnvironment :
def i n i t ( self , factory ):
s e l f . factory = factory
s 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 ( )
def play ( s e l f ) :
s e l f . p . i n t e r a c t W i t h ( s e l f . ob )

g1 = GameEnvironment ( K i t t i e s A n d P u z z l e s ( ) )
g2 = GameEnvironment ( KillAndDismember ( ) )
g1 . p l a y ( )
g2 . p l a y ( )
#:˜
En este entorno, los objetos Player interactúan con los objetos
Obstacle pero hay diferentes tipos de jugadores y obstáculos, de-
pendiendo de qué tipo de juego está jugando. Usted determina el
tipo de juego al elegir un determinado GameElementFactory, y
luego el GameEnvironment controla la configuración y el desar-
rollo del juego. En este ejemplo, la configuración y el juego es muy
simple, pero esas actividades (las initial conditions : condiciones
iniciales y el state change : cambio de estado) pueden determinar
gran parte el resultado del juego. Aquı́, GameEnvironment no
está diseñado para ser heredado, aunque podrı́a muy posiblemente
tener sentido hacer eso.

Esto también contiene ejemplos de Double Dispatching : Despa-


cho doble y el Factory Method : Método de fábrica, ambos de los
cuales se explicarán más adelante.

Claro, la plataforma anterior deObstacle, Player y GameEle-


mentFactory (que fue traducido de la versión Java de este ejemplo)
es innecesaria – que sólo es requerido para lenguajess que tienen
comprobación de tipos estáticos. Siempre y cuando las clases de

98
Python 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 A b s t r a c t Factory .

c l a s s Kitty :
def interactWith ( s e l f , obstacle ) :
p r i n t ” K i t t y has e n c o u n t e r e d a ” ,
obstacle . action ()

c l a s s KungFuGuy :
def interactWith ( s e l f , obstacle ) :
p r i n t ”KungFuGuy now b a t t l e s a ” ,
obstacle . action ()

c l a s s Puzzle :
def action ( s e l f ) : print ” Puzzle ”

c l a s s NastyWeapon :
d e f a c t i o n ( 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 KittiesAndPuzzles :
d e f makePlayer ( s e l f ) : r e t u r n K i t t y ( )
d e f makeObstacle ( s e l f ) : r e t u r n P u z z l e ( )

c l a s s KillAndDismember :
d e f makePlayer ( s e l f ) : r e t u r n KungFuGuy ( )
d e f makeObstacle ( s e l f ) : r e t u r n NastyWeapon ( )

c l a s s GameEnvironment :
def i n i t ( self , factory ):
s e l f . factory = factory
s 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 ( )
def play ( s e l f ) :
s e l f . p . i n t e r a c t W i t h ( s e l f . ob )

99
g1 = GameEnvironment ( K i t t i e s A n d P u z z l e s ( ) )
g2 = GameEnvironment ( KillAndDismember ( ) )
g1 . p l a y ( )
g2 . p l a y ( )
#:˜
Otra manera de poner esto es que toda la herencia en Python es
la herencia de implementación; ya que Python hace su la compro-
bación de tipo en tiempo de ejecución, no hay necesidad de utilizar
la herencia de interfaces para que pueda upcast al tipo base.

Es posible que desee estudiar los dos ejemplos de comparación,


sin embargo. ¿La primera de ellas agrega suficiente información
útil sobre el patrón que vale la pena mantener algún aspecto de la
misma? Tal vez todo lo que necesita es ”clases de etiquetado” como
esta:

c l a s s Obstacle : pass
c l a s s Player : pass
c l a s s GameElementFactory : p a s s
A continuación, la herencia sólo sirve para indicar el tipo de las
clases derivadas.

10.4 Ejercicios
1. Agregar la clase Triangle a ShapeFactory1.py
2. Agregar la clase Triangle a ShapeFactory2.py
3. Agregar un nuevo tipo de GameEnvironment llamado Gnome-
sAndFairies a GameEnvironment.py
4. Modificar ShapeFactory2.py para que utilice una Abstract Fac-
tory para crear diferentes conjuntos de formas (por ejemplo, un tipo
particular de objeto de fábrica crea ”formas gruesas,” otra crea ”for-
mas delgadas,” pero cada objeto fábrica puede crear todas las for-
mas: cı́rculos, cuadrados, triángulos, etc.).

100
11 6 : Objetos de función
En Advanced C++:Programming Styles And Idioms (Addison-Wesley,
1992), Jim Coplien acuña el término functor que es un objeto cuyo
único propósito es encapsular una función (ya que ”Functor” tiene
un significado en matemáticas, Voy a utilizar el término más explı́cito
function object). El punto es desacoplar la elección de la función que
se llamará desde el sitio en que esa función se llama.

Este término se menciona pero no se utiliza en Design Patterns.


Sin embargo, el tema del objeto de función se repite en una serie de
patrones en ese libro.

11.1 Comando: la elección de la operación en tiempo de


ejecución
Este es el objeto de función en su sentido más puro: un método
que es un objeto 17 . Al envolver un método en un objeto, usted
puede pasarlo a otros métodos u objetos como un parámetro, para
decirles que para realizar esta operación en particular en el proceso
de cumplir con su petición.

#: c06 : CommandPattern . py

c l a s s Command :
def execute ( s e l f ) : pass

c l a s s Loony (Command ) :
def execute ( s e l f ) :
p r i n t ”You ’ r e a l o o n y . ”

c l a s s NewBrain (Command ) :
def execute ( s e l f ) :
p r i n t ”You might even need a new b r a i n . ”

c l a s s A f f o r d (Command ) :
17 En el lenguaje Python, todas las funciones son ya objetos y ası́ el patrón Comando suele

ser redundante.

101
def execute ( s e l f ) :
p r i n t ” I couldn ’ t a f f o r d a whole new b r a i n . ”

# An o b j e c t t h a t h o l d s commands :
c l a s s Macro :
def init ( self ):
s e l f . commands = [ ]
d e f add ( s e l f , command ) :
s e l f . commands . append (command )
d e f run ( s e l f ) :
f o r c i n s e l f . commands :
c . execute ()

macro = Macro ( )
macro . add ( Loony ( ) )
macro . add ( NewBrain ( ) )
macro . add ( A f f o r d ( ) )
macro . run ( )
#:˜
El punto primario de Command es para que pueda entregar
una acción deseada a un método u objeto. En el ejemplo ante-
rior, esto proporciona una manera de hacer cola en un conjunto
de acciones a realizar colectivamente. En este caso, ello le permite
crear dinámicamente un nuevo comportamiento, algo que sólo puede
hacer normalmente escribiendo nuevo código, pero en el ejemplo an-
terior se podrı́a hacer mediante la interpretación de un script (ver el
patrón Interpreter : intérprete si lo que necesita hacer se pone muy
complejo).

Design Patterns dice que ”Los comandos son un reemplazo orien-


tado a objetos para devoluciones de llamada18 .” Sin embargo, Creo
que la palabra ”vuelta” es una parte esencial del concepto de devolu-
ciones de llamada. Es decir, Creo que una devolución de llamada
en realidad se remonta al creador de la devolución de llamada. Por
otro lado, con un objeto Command normalmente se acaba de crear
y entregar a algún método o un objeto, y no está conectado de otra
forma el transcurso del tiempo con el objeto Command. Esa es
mi opinión sobre ella, de todos modos. Más adelante en este libro,
18 Página 235

102
Combino un grupo de patrones de diseño bajo el tı́tulo de ”devolu-
ciones de llamada.”

11.2 Estrategia: elegir el algoritmo en tiempo de ejecución


Strategy parece ser una familia de clases Command, todo heredado
de la misma base. Pero si nos fijamos en Command, verá que tiene
la misma estructura: una jerarquı́a de objetos de función. La difer-
encia está en la forma en que se utiliza esta jerarquı́a. Como se ve en
c12:DirList.py, usted utiliza Command para resolver un problema
particular — en este caso, seleccionando los archivos de una lista.
La ”cosa que permanece igual” es el cuerpo del método que está
siendo llamado, y la parte que varı́a es aislado en el objeto función.
Me atreverı́a a decir que Command proporciona flexibilidad. mien-
tras usted está escribiendo el programa, visto que la flexibilidad de
Strategy está en tiempo de ejecución.
Strategy también agrega un ”Contexto” que puede ser una clase
sustituta que controla la selección y uso de la estrategia de objeto
particular — al igual que State! Esto es lo que parece:

103
#: c06 : S t r a t e g y P a t t e r n . 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 s e q u e n c e o f p o i n t s :
def algorithm ( s e l f , l i n e ) : pass

# The v a r i o u s s t r a t e g i e s :
c l a s s L e a s t S q u a r e s ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 1 . 1 , 2 . 2 ] # Dummy

c l a s s NewtonsMethod ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 3 . 3 , 4 . 4 ] # Dummy

c l a s s B i s e c t i o n ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 5 . 5 , 6 . 6 ] # Dummy
c l a s s ConjugateGr adient ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 3 . 3 , 4 . 4 ] # Dummy
# The ” Context ” c o n t r o l s t he s t r a t e g y :
c l a s s MinimaSolver :
def i n i t ( self , strategy ):
s e l f . strategy = strategy
d e f minima ( s e l f , l i n e ) :
return s e l f . strategy . algorithm ( l i n e )

d e 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 ( L e a s t S q u a r e s ( ) )
line = [
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 )
#:˜

104
Observe similitud con el método de plantilla – TM afirma dis-
tinción que este tiene más de un método para llamar, hace las cosas
por partes. Ahora bien, no es probable que la estrategia de ob-
jeto tendrı́a más de una llamada al método; considerar sistema de
cumplimiento de la orden de Shalloway.con información de los paı́ses
en cada estrategia.

Ejemplo Estrategia de Python estándar: sort( ) toma un se-


gundo argumento opcional que actúa como un objeto de comparación;
esta es una estrategia.

105
11.3 Cadena de responsabilidad
Chain of Responsibility : Cadena de responsabilidad podrı́a ser pen-
sado como una generalización dinámica de recursividad utilizando
objetos Strategy. Usted hace una llamada, y cada Strategy en una
secuencia vinculada trata de satisfacer la llamada. El proceso ter-
mina cuando una de las estrategias es exitosa o termina la cadena.
En la recursividad, un método se llama a sı́ mismo una y otra vez
hasta que se alcance una condición de terminación; con Chain of
Responsibility : Cadena de responsabilidad, un método se llama a sı́
mismo, que (moviendo por la cadena de Strategies) llama una im-
plementación diferente del método, etc., hasta que se alcanza una
condición de terminación. La condición de terminación es o bien que
se alcanza la parte inferior de la cadena (en cuyo caso se devuelve
un objeto por defecto; usted puede o no puede ser capaz de pro-
porcionar un resultado por defecto ası́ que usted debe ser capaz de
determinar el éxito o el fracaso de la cadena) o una de las Strategies
: Estrategias tiene éxito.

En lugar de llamar a un solo método para satisfacer una solicitud,


múltiples métodos de la cadena tienen la oportunidad de satisfacer
la solicitud, por lo que tiene el sabor de un sistema experto. Dado
que la cadena es efectivamente una lista enlazada, puede ser creada
dinámicamente, por lo que también podrı́a pensar en ello como una
más general, declaración switch dinámicamente construida.

En el GoF, hay una buena cantidad de discusión sobre cómo crear


la cadena de responsabilidad como una lista enlazada. Ahora bien,
cuando nos fijamos en el patrón realmente no deberı́a importar cómo
se mantiene la cadena; eso es un detalle de implementación. Ya que
GoF fue escrito antes de la Librerı́a de plantillas estándar (STL :
Standard Template Library ) fue incorporado en la mayorı́a de los
compiladores de C ++, La razón de esto más probable es (1) no
habı́a ninguna lista y por lo tanto tuvieron que crear una y (2) las
estructuras de datos a menudo se enseñan como una habilidad fun-
damental en el mundo académico, y la idea de que las estructuras de
datos deben ser herramientas estándar disponibles con el lenguaje
de programación puede no haber ocurrido a los autores GoF. Yo
sostengo que la implementación de Chain of Responsibility como
una cadena (especı́ficamente, una lista enlazada) no añade nada a

106
la solución y puede fácilmente ser implementado utilizando una lista
estándar de Python, como se muestra más abajo. Además, usted
verá que he ido a algún esfuerzo para separar las partes de gestión de
la cadena de la implementación de las distintas Strategies, de modo
que el código puede ser más fácilmente reutilizado.

En StrategyPattern.py, anteriormente, lo que probablemente


quiere es encontrar automáticamente una solución. Chain of Re-
sponsibility : Cadena de Responsabilidad proporciona una manera
de hacer esto por el encadenamiento de los objetos Strategy jun-
tos y proporcionando un mecanismo para que recursivamente au-
tomáticamente a través de cada uno en la cadena:

#: c06 : C h a i n O f R e s p o n s i b i l i t y . py

# Carry th e i n f o r m a t i o n i n t o t he s t r a t e g y :
c l a s s Messenger : p a s s

# The R e s u l t o b j e c t c a r r i e s th e r e s u l t data and


# whether th e 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 :
def init ( self ):
s e l f . succeeded = 0
def i s S u c c e s s f u l ( s e l f ) :
return s e l f . succeeded
def 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 :
def c a l l ( messenger ) : p a s s
def str ( self ):
r e t u r n ” Trying ” + s e l f . c l a s s . name \
+ ” algorithm ”

# Manage t he movement through t he c h a i n and


# find a successful result :
c l a s s ChainLink :
def i n i t ( s e l f , chain , s t r a t e g y ) :
s e l f . strategy = strategy

107
s e l f . chain = chain
s e l f . c h a i n . append ( s e l f )

d e f next ( s e l f ) :
# Where t h i s l i n k i s i n th e c h a i n :
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 ( ) :
return s e l f . chain [ l o c a t i o n + 1]

d e f end ( s e l f ) :
r e t u r n ( s e l f . c h a i n . i n d e x ( s e l f ) + 1 >=
len ( s e l f . chain ))
def 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 ( ) o r s e l f . end ( ) : r e t u r n r
r e t u r n s e l f . next ( ) ( messenger )

# For t h i s example , th e Messenger


# and R e s u l t can be t h e same type :
c l a s s LineData ( Result , Messenger ) :
def i n i t ( s e l f , data ) :
s e l f . data = data
def s t r ( s e l f ) : r e t u r n ‘ s e l f . data ‘

c l a s s LeastSquares ( Strategy ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 1 . 1 , 2 . 2 ] ) # Dummy data
result . setSuccessful (0)
return r e s u l t

c l a s s NewtonsMethod ( S t r a t e g y ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 3 . 3 , 4 . 4 ] ) # Dummy data

108
result . setSuccessful (0)
return r e s u l t

c l a s s Bisection ( Strategy ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 5 . 5 , 6 . 6 ] ) # Dummy data
result . setSuccessful (1)
return r e s u l t

c l a s s ConjugateGr adient ( S t r a t e g y ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 7 . 7 , 8 . 8 ] ) # Dummy data
result . setSuccessful (1)
return r e s u l t

solutions = [ ]
solutions = [
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 , Bisection ()) ,
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
])
print solutions [ 0 ] ( line )
#:˜

109
11.4 Ejercicios
1. Usar Command en el capitulo 3, Ejercicio 1.
2. Implementar Chain of Responsibility: cadena de responsabilidad
para crear un ”sistema experto” que resuelva problemas, tratando
sucesivamente una única solución tras otro hasta que uno coincida.
Usted debe ser capaz de añadir dinámicamente soluciones para el
sistema experto. La prueba para la solución simplemente debe ser
un juego de string, pero cuando una solución se ajusta, el sistema
experto debe devolver el tipo apropiado de objeto ProblemSolver.

12 7: Cambiando la interfaz.
A veces el problema que usted está resolviendo es tan simple como:
”Yo no tengo la interfaz que quiero.” Dos de los patrones en Design
Patterns : Diseño de Patrones resuelven este problema: Adapter :
Adaptador toma un tipo y produce una interfaz a algún otro tipo.
Façade: Fachada crea una interfaz para un conjunto de clases, sim-
plemente para proporcionar una manera más cómoda para hacer
frente a una biblioteca o un paquete de recursos.

12.1 Adapter : Adaptador


Cuando tienes this, y usted necesita that, Adapter : Adaptador re-
suelve el problema. El único requisito es producir un that, y hay un
número de maneras que usted puede lograr esta adaptación.
#: c07 : Adapter . py
# V a r i a t i o n s on th e Adapter p a t t e r n .

c l a s s WhatIHave :
def g ( s e l f ) : pass
def h( s e l f ) : pass

c l a s s WhatIWant :
def f ( s e l f ) : pass

c l a s s ProxyAdapter ( WhatIWant ) :
def i n i t ( s e l f , whatIHave ) :

110
s e l f . whatIHave = whatIHave

def f ( s e l f ) :
# Implement b e h a v i o r u s i n g
# methods i n WhatIHave :
s e l f . whatIHave . g ( )
s e l f . whatIHave . h ( )

c l a s s WhatIUse :
d e f op ( s e l f , whatIWant ) :
whatIWant . f ( )

# Approach 2 : b u i l d a d a p t e r use i n t o op ( ) :
c l a s s WhatIUse2 ( WhatIUse ) :
d e f op ( s e l f , whatIHave ) :
ProxyAdapter ( whatIHave ) . f ( )

# Approach 3 : b u i l d a d a p t e r i n t o WhatIHave :
c l a s s WhatIHave2 ( WhatIHave , WhatIWant ) :
def f ( s e l f ) :
s e l f . g ()
s e l f .h()

# Approach 4 : use an i n n e r c l a s s :
c l a s s WhatIHave3 ( WhatIHave ) :
c l a s s InnerAdapter ( WhatIWant ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
def f ( s e l f ) :
s e l f . outer . g ()
s e l f . outer . h ()

d e f whatIWant ( s e l f ) :
r e t u r n WhatIHave3 . InnerAdapter ( s e l f )

whatIUse = WhatIUse ( )
whatIHave = WhatIHave ( )
adapt= ProxyAdapter ( whatIHave )
whatIUse2 = WhatIUse2 ( )

111
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 término ”proxy” aquı́, porque
en Design Patterns afirman que un proxy debe tener una interfaz
idéntica con el objeto que es para un sustituto : surrogate. Sin em-
bargo, si tiene las dos palabras juntas: ”adaptador de proxy,” tal
vez sea más razonable.

12.2 Façade : Fachada


Un principio general que aplico cuando estoy fundido en tratar de
moldear requisitos en un objeto de primer corte es ”Si algo es feo,
esconderlo dentro de un objeto.” Esto es básicamente lo que logra
Façade. Si usted tiene una colección bastante confusa de las clases
y las interacciones que el programador cliente no tiene realmente
necesidad de ver, entonces usted puede crear una interfaz que es útil
para el programador cliente y que sólo presenta lo que sea necesario.
Façade se suele implementar como fábrica abstracta singleton.
Claro, usted puede conseguir fácilmente este efecto mediante la
creación de una clase que contiene métodos de fábrica static:

# c07 : Facade . py
c l a s s A:
def i n i t ( s e l f , x ) : pass
c l a s s B:
def i n i t ( s e l f , x ) : pass
c l a s s C:
def i n i t ( s e l f , x ) : pass

# Other c l a s s e s t h a t aren ’ t exposed by th e

112
# f a c a d e go h e r e . . .
c l a s s Facade :
d e f makeA( x ) : r e t u r n A( x )
makeA = s t a t i c m e t h o d (makeA)
d e f makeB ( x ) : r e t u r n B( x )
makeB = s t a t i c m e t h o d ( makeB )
d e f makeC( x ) : r e t u r n C( x )
makeC = s t a t i c m e t h o d (makeC)
# The c l i e n t programmer g e t s t h e o b j e c t s
# by c a l l i n g t he s t a t i c methods :
a = Facade . makeA ( 1 ) ;
b = Facade . makeB ( 1 ) ;
c = Facade . makeC ( 1 . 0 ) ;
# :˜
[reescribir esta sección utilizando la investigación del libro de Lar-
man]

12.3 Ejercicios
1. Crear una clase adaptador que carga automáticamente una matriz
bidimensional de objetos en un diccionario como pares clave-valor.

113
13 Código Tabla impulsada:
flexibilidad de configuración
Código de la tabla dirigido por el uso de clases internas anónimas
Véase el ejemplo ListPerformance en TIJ del Capı́tulo 9.

También GreenHouse.py

114
14 Devoluciones de Llamada
Desacoplamiento comportamiento código.

Observer y una categorı́a de devoluciones de llamada llamado


”Despacho múltiple (no en Design Patterns )” incluyendo el Visitor
de Design Patterns.

14.1 Observer : Observador


Al igual que las otras formas de devolución de llamada, este con-
tiene un punto de gancho donde se puede cambiar el código. La
diferencia es de naturaleza completamente dinámica del observador.
A menudo se utiliza para el caso especı́fico de los cambios basados
en el cambio de otro objeto de estado, pero es también la base de
la gestión de eventos. Cada vez que desee desacoplar la fuente de
la llamada desde el código de llamada de forma totalmente dinámica.

El patrón observador resuelve un problema bastante común: ¿Qué


pasa si un grupo de objetos necesita actualizar a sı́ mismos cuando
algún objeto cambia de estado? Esto se puede ver en el ”modelo-
vista” aspecto de MVC (modelo-vista-controlador) de Smalltalk, o
el ”Documento - Ver Arquitectura” casi equivalente. Supongamos
que usted tiene algúnos datos (el “documento”) y más de una vista,
decir una parcela y una vista textual. Al cambiar los datos, los
dos puntos de vista deben saber actualizarse a sı́ mismos,y eso es lo
que facilita el observador. Es un problema bastante común que su
solución se ha hecho una parte de la libreria estándar java.util.

Hay dos tipos de objetos que se utilizan para implementar el


patrón de observador en Python. La clase Observable lleva un
registro de todos los que quieran ser informados cuando un cambio
ocurre, si el ”Estado” ha cambiado o no. Cuando alguien dice ”Está
bien, todo el mundo debe revisar y, potencialmente, actualizarse,”
La clase Observable realiza esta tarea mediante una llamada al
método notifyObservers( ) para cada uno en la lista. El método
notifyObservers( ) es parte de la clase base Observable.

115
En realidad, hay dos ”cosas que cambian” en el patrón obser-
vador: la cantidad de objetos de observación y la forma se produce
una actualización. Es decir, el patrón observador permite modificar
ambos sin afectar el código circundante.

Observer es una clase ”interfaz” que solo tiene una función


miembro, update( ). Esta función es llamada por el objeto que
está siendo observado, cuando ese objeto decide que es hora de actu-
alizar todos sus observadores. Los argumentos son opcionales; usted
podrı́a tener un update( ) sin argumentos y eso todavı́a encajarı́a
en el patrón observador; sin embargo, esto es más general — per-
mite al objeto observado pasar el objeto que causó la actualización
(ya que un Observer puede ser registrado con más de un objeto
observado) y cualquier información adicional si eso es útil, en lugar
de forzar el objeto Observer que busca alrededor para ver quién
está actualizando y para ir a buscar cualquier otra información que
necesita.

El ”objeto observado” que decide cuándo y cómo hacer la actu-


alización será llamado Observable.

Observable tiene una bandera para indicar si se ha cambiado.


En un diseño más simple, no habrı́a ninguna bandera; si algo pasó,
cad uno serı́a notificado. La bandera le permite esperar, y sólo
notificar al Observers cuando usted decide sea el momento ade-
cuado. Nótese, sin embargo, que el control del estado de la bandera
es protected, de modo que sólo un heredero puede decidir lo que
constituye un cambio, y no el usuario final de la clase Observer
derivada resultante.

La mayor parte del trabajo se hace en notifyObservers( ). Si


la bandera changed no se ha establecido, esto no hace nada. De
otra manera, ello primero despeja la bandera changed ası́ repitió las
llamadas a notifyObservers( ) para no perder el tiempo. Esto se
hace antes de notificar a los observadores en el caso de las llamadas
a update( ) hacer cualquier cosa que causa un cambio de nuevo
a este objeto Observable. Entonces se mueve a través del set y
vuelve a llamar a la función miembro update( ) de cada Observer.

116
Al principio puede parecer que usted puede utilizar un objeto
ordinario Observable para administrar las actualizaciones. Pero
esto no funciona; para obtener un efecto, usted debe heredar de
Observable y en algún lugar en el código de la clase derivada lla-
mar setChanged( ). Esta es la función miembro que establece la
bandera ”cambiado”, lo que significa que cuando se llama notify-
Observers( ) todos los observadores serán, de hecho, serán notifi-
cados. Cuando usted llama setChanged( ) depende de la lógica
de su programa.

117
14.1.1 Observando Flores

Dado que Python no tiene componentes de la librerı́a estándar para


apoyar el patrón observador (como hace Java), primero tenemos que
crear una. Lo más sencillo de hacer es traducir la librerı́a estándar
de Java Observer y Observable clases. Esto también proporciona
la traducción más fácil a partir de código Java que utiliza estas li-
brerı́as.

Al tratar de hacer esto, nos encontramos con un obstáculo menor,


que es el hecho de que Java tiene una palabra clave synchronized
que proporciona soporte integrado para la sincronización hilo. Cier-
tamente se podrı́a lograr lo mismo a mano utilizando código como
el siguiente:

import t h r e a d i n g
c l a s s ToSynch :
def init ( self ):
s e l f . mutex = t h r e a d i n g . RLock ( )
s e l f . val = 1
d e f aSynchronizedMethod ( s e l f ) :
s e l f . mutex . a c q u i r e ( )
try :
s e l f . v a l += 1
return s e l f . val
finally :
s e l f . mutex . r e l e a s e ( )
Pero esto se convierte rápidamente tedioso de escribir y de leer.
Peter Norvig me proporcionó una solución mucho más agradable:

#: u t i l : S y n c h r o n i z a t i o n . py
’ ’ ’ Simple e m u l a t i o n o f Java ’ s ’ s y n c h r o n i z e d ’
keyword , from P e t e r Norvig . ’ ’ ’
import t h r e a d i n g

d e f s y n c h r o n i z e d ( method ) :
d e f f (∗ a r g s ) :
s e l f = args [ 0 ]
s e l f . mutex . a c q u i r e ( ) ;

118
# p r i n t method . na me , ’ a c q u i r e d ’
try :
r e t u r n apply ( method , a r g s )
finally :
s e l f . mutex . r e l e a s e ( ) ;
# p r i n t method . na me , ’ r e l e a s e d ’
return f

d e f s y n c h r o n i z e ( k l a s s , names=None ) :
””” S y n c h r o n i z e methods i n th e g i v e n c l a s s .
Only s y n c h r o n i z e th e methods whose names a r e
given , o r a l l methods i f names=None . ” ” ”
i f type ( names)==type ( ’ ’ ) : names = names . s p l i t ( )
f o r ( name , v a l ) i n k l a s s . d i c t . i t e m s ( ) :
i f c a l l a b l e ( v a l ) and name != ’ i n i t ’ and \
( names == None o r name i n names ) :
# p r i n t ” s y n c h r o n i z i n g ” , name
k l a s s . d i c t [ name ] = s y n c h r o n i z e d ( v a l )

# You can c r e a t e your own s e l f . mutex , o r i n h e r i t


# from t h i s c l a s s :
c l a s s Synchronization :
def init ( self ):
s e l f . mutex = t h r e a d i n g . RLock ( )
#:˜
La función synchronized( ) toma un método y lo envuelve en
una función que añade la funcionalidad mutex. El método es lla-
mado dentro de esta función:

r e t u r n apply ( method , a r g s )
y como la sentencia return pasa a través de la cláusula finally,
el mutex es liberado.

Esto es de alguna manera el patrón de diseño decorador, pero


mucho más fácil de crear y utilizar. Todo lo que tienes que decir es:

myMethod = s y n c h r o n i z e d ( myMethod )

119
Para rodear su método con un mutex.

synchronized( ) es una función de conveniencia que aplica syn-


chronized( ) para una clase entera, o bien todos los métodos de la
clase (por defecto) o métodos seleccionados que son nombrados en
un string : cadena como segundo argumento.

Finalmente, para synchronized( ) funcione debe haber un self.mutex


creado en cada clase que utiliza synchronized( ). Este puede ser
creado a mano por el autor de clases, pero es más consistente para
utilizar la herencia, por tanto la clase base Synchronization es
proporcionada.

He aquı́ una prueba sencilla del módulo de Synchronization.

#: u t i l : T e s t S y n c h r o n i z a t i o n . py
from S y n c h r o n i z a t i o n import ∗

# To use f o r a method :
c l a s s C( S y n c h r o n i z a t i o n ) :
def init ( self ):
Synchronization . i n i t ( s e l f )
s e l f . data = 1
d e f m( s e l f ) :
s e l f . data += 1
r e t u r n s e l f . data
m = s y n c h r o n i z e d (m)
d e f f ( s e l f ) : r e t u r n 47
d e f g ( s e l f ) : r e t u r n ’ spam ’

# So m i s s y n c h r o n i z e d , f and g a r e not .
c = C( )

# On th e c l a s s l e v e l :
c l a s s D(C ) :
def init ( self ):
C. i n i t ( s e l f )
# You must o v e r r i d e an un−s y n c h r o n i z e d method
# i n o r d e r t o s y n c h r o n i z e i t ( j u s t l i k e Java ) :

120
d e f f ( s e l f ) : C. f ( s e l f )

# S y n c h r o n i z e e v e r y ( d e f i n e d ) method i n t h e c l a s s :
s y n c h r o n i z e (D)
d = D( )
d . f ( ) # Synchronized
d . g ( ) # Not s y n c h r o n i z e d
d .m( ) # S y n c h r o n i z e d ( i n t he base c l a s s )

c l a s s E(C ) :
def init ( self ):
C. i n i t ( s e l f )
d e f m( s e l f ) : C.m( s e l f )
d e f g ( s e l f ) : C. g ( s e l f )
d e f f ( s e l f ) : C. f ( s e l f )
# Only s y n c h r o n i z e s m and g . Note t h a t m ends up
# b e i n g doubly−wrapped i n s y n c h r o n i z a t i o n , which
# doesn ’ t h urt a n y t h i n g but i s i n e f f i c i e n t :
s y n c h r o n i z e (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 de
Synchronized() para m, dejando f y g solos. Clase D tiene todos
sus métodos sincronizados en masa, y la clase E utiliza la función
de conveniencia para sincronizar m y g. Tenga en cuenta que dado
que m termina siendo sincronizado en dos ocasiones, ello se entró y
salió dos veces para cada llamada, que no es muy deseable [puede
haber una corrección para este].

#: u t i l : Observer . py
# Class support f o r ” observer ” pattern .
from S y n c h r o n i z a t i o n import ∗

c l a s s Observer :
d e f update ( o b s e r v a b l e , ar g ) :

121
’ ’ ’ C a l l e d when t h e o b s e r v e d o b j e c t i s
m o d i f i e d . You c a l l an Ob s e r v a b le o b j e c t ’ s
n o t i f y O b s e r v e r s method t o n o t i f y a l l th e
o b j e c t ’ s o b s e r v e r s o f t he change . ’ ’ ’
pass

c l a s s O b s e r va b l e ( S y n c h r o n i z a t i o n ) :
def init ( self ):
s e l f . obs = [ ]
s e l f . changed = 0
Synchronization . i n i t ( s e l f )

d e f addObserver ( s e l f , o b s e r v e r ) :
i f o b s e r v e r not i n s e l f . obs :
s e l f . obs . append ( o b s e r v e r )

def deleteObserver ( s e l f , observer ) :


s e l f . obs . remove ( o b s e r v e r )

d e f n o t i f y O b s e r v e r s ( s e l f , ar g = None ) :
’ ’ ’ I f ’ changed ’ i n d i c a t e s t h a t t h i s o b j e c t
has changed , n o t i f y a l l i t s o b s e r v e r s , then
c a l l clearChanged ( ) . Each o b s e r v e r has i t s
update ( ) c a l l e d with two arguments : t h i s
o b s e r v a b l e o b j e c t and t he g e n e r i c ’ arg ’ . ’ ’ ’

s e l f . mutex . a c q u i r e ( )
try :
i f not s e l f . changed : r e t u r n
# Make a l o c a l copy i n c a s e o f s ynchr onous
# additions of observers :
l o c a l A r r a y = s e l f . obs [ : ]
s e l f . clearChanged ( )
finally :
s e l f . mutex . r e l e a s e ( )
# Updating i s not r e q u i r e d t o be s y n c h r o n i z e d :
for observer in localArray :
o b s e r v e r . update ( s e l f , a r g )

122
def d e l e t e O b s e r v e r s ( s e l f ) : s e l f . obs = [ ]
def setChanged ( s e l f ) : s e l f . changed = 1
def clearChanged ( s e l f ) : s e l f . changed = 0
def hasChanged ( s e l f ) : r e t u r n s e l f . changed
def c o u n t O b s e r v e r s ( s e l f ) : r e t u r n l e n ( s e l f . obs )

s y n c h r o n i z e ( Observable ,
” addObserver d e l e t e O b s e r v e r d e l e t e O b s e r v e r s ” +
” setChanged clearChanged hasChanged ” +
” countObservers ”)
#:˜
Usando esta librerı́a, aquı́ esta un ejemplo de el patrón obser-
vador:

#: c10 : ObservedFlower . py
# Demonstration o f ” o b s e r v e r ” p a t t e r n .
import s y s
s y s . path += [ ’ . . / u t i l ’ ]
from Observer import Observer , O b s e r v ab l e

c l a s s Flower :
def init ( self ):
s e l f . isOpen = 0
s e l f . o p e n N o t i f i e r = Flower . O p e n N o t 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 )
d e f open ( s e l f ) : # Opens i t s p e t a l s
s e l f . isOpen = 1
s e l f . openNotifier . notifyObservers ()
s e l f . c l o s e N o t i f i e r . open ( )
def close ( s e l f ) : # Closes i t s petals
s e l f . isOpen = 0
s e l f . clos eNo tifi er . notifyObservers ()
s e l f . openNotifier . close ()
def closing ( s e l f ) : return s e l f . c l o s e N o t i f i e r
c l a s s O p e n N o t i f i e r ( Ob s e r v a b le ) :
def i n i t ( s e l f , outer ) :
O b s e rv a b l e . i n i t ( s e l f )
s e l f . outer = outer
s e l f . alreadyOpen = 0

123
def notifyObservers ( s e l f ) :
i f s e l f . o u t e r . isOpen and \
not s e l f . alreadyOpen :
s e l f . setChanged ( )
O b s er v a b l e . n o t i f y O b s e r v e r s ( s e l f )
s e l f . alreadyOpen = 1
def close ( 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 ( O bs e r v a b le ) :
def i n i t ( s e l f , outer ) :
O b s e rv a b l e . i n i t ( s e l f )
s e l f . outer = outer
s e l f . alreadyClosed = 0
def notifyObservers ( s e l f ) :
i f not s e l f . o u t e r . isOpen and \
not s e l f . a l r e a d y C l o s e d :
s e l f . setChanged ( )
O b s er v a b l e . n o t i f y O b s e r v e r s ( s e l f )
s e l f . alreadyClosed = 1
d e f open ( s e l f ) :
alreadyClosed = 0

c l a s s Bee :
def i n i t ( s e l f , name ) :
s e l f . name = name
s e l f . openObserver = Bee . OpenObserver ( s e l f )
s e l f . c l o s e O b s e r v e r = Bee . C l o s e O b s e r v e r ( s e l f )

# An i n n e r c l a s s f o r o b s e r v i n g o p e n i n g s :
c l a s s OpenObserver ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Bee ” + s e l f . o u t e r . name + \
” ’ s b r e a k f a s t time ! ”
# Another i n n e r c l a s s f o r c l o s i n g s :
c l a s s C l o s e O b s e r v e r ( Observer ) :
def i n i t ( s e l f , outer ) :

124
s e l f . outer = outer

d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Bee ” + s e l f . o u t e r . name + \
” ’ s bed time ! ”

c l a s s Hummingbird :
def i n i t ( s e l f , name ) :
s e l f . name = name
s e l f . openObserver = \
Hummingbird . OpenObserver ( s e l f )
s e l f . closeObserver = \
Hummingbird . C l o s e O b s e r v e r ( s e l f )
c l a s s OpenObserver ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Hummingbird ” + s e l f . o u t e r . name + \
” ’ s b r e a k f a s t time ! ”
c l a s s C l o s e O b s e r v e r ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Hummingbird ” + s e l f . o u t e r . name + \
” ’ s bed time ! ”

f = Flower ( )
ba = Bee ( ” E r i c ” )
bb = Bee ( ” E r i c 0 . 5 ” )
ha = Hummingbird ( ”A” )
hb = Hummingbird ( ”B” )
f . o p e n N o t i f i e r . addObserver ( ha . openObserver )
f . o p e n N o t i f i e r . addObserver ( hb . openObserver )
f . o p e n N o t i f i e r . addObserver ( ba . openObserver )
f . o p e n N o 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 e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( hb . c l o s e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( ba . c l o s e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( bb . c l o s e O b s e r v e r )

125
# Hummingbird 2 d e c i d e s t o s l e e p i n :
f . o p e n N o t i f i e r . d e l e t e O b s e r v e r ( hb . openObserver )
# A change t h a t i n t e r e s t s o b s e r v e r s :
f . open ( )
f . open ( ) # I t ’ s a l r e a d y open , no change .
# Bee 1 doesn ’ t want t o go t o bed :
f . c l o s e N o t i f i e r . d e l e t e O b s e r v e r ( ba . c l o s e O b s e r v e r ) f . c l o s e ( )
f . c l o s e ( ) # I t ’ s a l r e a d y c l o s e d ; no change
f . openNotifier . deleteObservers ()
f . open ( )
f . close ()
#:˜
Los acontecimientos de interés incluyen que una Flower puede
abrir o cerrar. Debido al uso del idioma de la clase interna, estos
dos eventos pueden ser fenómenos observables por separado. Open-
Notifier y CloseNotifier ambos heredan de Observable, ası́ que
tienen acceso a setChanged( ) y pueden ser entregados a todo lo
que necesita un Observable.

El lenguaje de la clase interna también es muy útil para definir


más de un tipo de Observer, en Bee y Hummingbird, ya que
tanto las clases pueden querer observar independientemente aber-
turas Flower y cierres. Observe cómo el lenguaje de la clase interna
ofrece 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 del


patrón observador: la capacidad de cambiar el comportamiento en
tiempo de ejecución mediante el registro de forma dinámica y anular
el registro Observers con Observables.

Si usted estudia el código de arriba verás que OpenNotifier y


CloseNotifier utiliza la interfaz Observable básica. Esto significa
que usted podrı́a heredar otras clases Observable completamente
diferentes; la única conexión de Observable que tiene con Flower,
es la interfaz Observable.

126
14.2 Un ejemplo visual de Observadores
El siguiente ejemplo es similar al ejemplo ColorBoxes del Capı́tulo
14 en Thinking in Java, 2nd Edición. Las cajas se colocan en una
cuadrı́cula en la pantalla y cada uno se inicializa a un color aleatorio.
En adición, cada caja implements la interfaz Observer y es reg-
istrado con un objeto Observable.Al hacer clic en una caja, todas
las otras cajas son notificados de que un cambio se ha hecho porque
el objeto Observable llama automáticamente método update( )
de cada objeto Observer. Dentro de este método, las comproba-
ciones de caja para ver si es adyacente a la que se ha hecho clic, y
si de modo que cambia de color para que coincida con el cuadro se
hace clic.

[[NOTA: este ejemplo no se ha convertido. Véase más adelante


una versión que tiene la interfaz gráfica de usuario, pero no los ob-
servadores, en PythonCard. ]]

# c10 : BoxObserver . py
# Demonstration o f Observer p a t t e r n u s i n g
# Java ’ s b u i l t −i n o b s e r v e r c l a s s e s .

# You must i n h e r i t a type o f O b s er v a b l e :


c l a s s BoxObservable ( O b s e r va b l e ) :
d e f n o t i f y O b s e r v e r s ( s e l f , Object b ) :
# Otherwise i t won ’ t p r o p a g a t e changes :
setChanged ( )
super . notifyObservers (b)

c l a s s BoxObserver ( JFrame ) :
O b s er v a b l e n o t i f i e r = BoxObservable ( )
def i n i t ( self , int grid ) :
s e t T i t l e ( ” Demonstrates Observer p a t t e r n ” )
C o n t a i n e r cp = getContentPane ( )
cp . s e t L a y o u t ( GridLayout ( g r i d , g r i d ) )
f o r ( i n t x = 0 x < g r i d x++)
f o r ( i n t y = 0 y < g r i d y++)
cp . add (OCBox( x , y , n o t i f i e r ) )

127
d e f main ( s e l f , S t r i n g [ ] a r g s ) :
int grid = 8
i f ( args . length > 0)
grid = Integer . parseInt ( args [ 0 ] )
JFrame f = BoxObserver ( g r i d )
f . s e t S i z e ( 5 0 0 , 400)
f . setVisible (1)
# JDK 1 . 3 :
f . s e t D e f a u l t C l o s e O p e r a t i o n (EXIT ON CLOSE)
# Add a WindowAdapter i f you have JDK 1 . 2

c l a s s OCBox( JPanel ) implements Observer :


O b s er v a b l e n o t i f i e r
int x , y # Locations in grid
Color c C o l o r = newColor ( )
s t a t i c f i n a l Co lor [ ] c o l o r s =:
Color . black , Color . blue , Color . cyan ,
Color . darkGray , Color . gray , Color . green ,
Color . l i g h t G r a y , Color . magenta ,
Color . orange , Color . pink , Color . red ,
Color . white , Color . y e l l o w

s t a t i c f i n a l Color newColor ( ) :
return colors [
( i n t ) ( Math . random ( ) ∗ c o l o r s . l e n g t h )
]

def i n i t ( s e l f , i n t x , i n t y , O b s e r v ab l e
notifier ):
self .x = x
self .y = y
n o t i f i e r . addObserver ( s e l f )
self . notifier = notifier
addMouseListener (ML( ) )

d e f paintComponent ( s e l f , Graphics g ) :
s u p e r . paintComponent ( g )
g . setColor ( cColor )
Dimension s = g e t S i z e ( )

128
g . f i l l R e c t ( 0 , 0 , s . width , s . h e i g h t )

c l a s s ML( MouseAdapter ) :
d e f mousePressed ( s e l f , MouseEvent e ) :
n o t i f i e r . n o t i f y O b s e r v e r s (OCBox . s e l f )

d e f update ( s e l f , O b se r v a b l e o , Object a rg ) :
OCBox c l i c k e d = (OCBox) ar g
i f ( nextTo ( c l i c k e d ) ) :
cColor = c l i c k e d . cColor
repaint ()

p r i v a t e f i n a l b o o l e a n nextTo (OCBox b ) :
r e t u r n Math . abs ( x − b . x ) <= 1 &&
Math . abs ( y − b . y ) <= 1
# :˜
La primera vez que usted mira la documentación en lı́nea para
Observable que es un poco confuso, ya que parece que se puede
utilizar un objeto ordinario Observablepara manejar las actualiza-
ciones. Pero esto no funciona; inténtalo dentro de BoxObserver,
crea un objeto Observable en lugar de un objeto BoxObserver y
observe lo que ocurre: nada. Para conseguir un efecto, debe heredar
de Observable y en alguna parte de su código la clase derivada lla-
mada setChanged( ). Este es el método que establece la bandera
”changed : cambiado”, lo que significa que cuando se llama notify-
Observers( ) todos los observadores serán, de hecho, serán notifi-
cado. En el ejemplo anterior, setChanged( ) es simplemente lla-
mado dentro de notifyObservers( ), pero podrı́a utilizar cualquier
criterio que desee para decidir cuándo llamar setChanged( ).

BoxObserver contiene un solo objeto Observable llamado no-


tifier, y cada vez que se crea un objeto OCBox, está vinculada a
notifier. En OCBox al hacer clic con el mouse el método noti-
fyObservers( ) es llamado, pasando el objeto se hace clic en un
argumento para que todas las cajas que reciben el mensaje (en su
método update( ) ) de que se ha hecho clic saben y pueden decidir
si cambiar sı́ o no. Usando una combinación de código en noti-
fyObservers( ) y update( ) se puede trabajar algunos esquemas
bastante complejos.

129
Podrı́a parecer que la forma en que los observadores son notifi-
cados debe ser congelada en tiempo de compilación en el método
notifyObservers( ). Ahora bien, si se mira más de cerca el código
anterior usted verá que el único lugar en BoxObserver o OCBox
cuando es consciente de que usted está trabajando con una BoxOb-
servable está en el punto de la creación del objeto Observable —
de ahı́ en adelante todo lo que utiliza la interfaz básica Observable.
Esto significa que usted podrı́a heredar otras clases Observable y
intercambiarlos en tiempo de ejecución si desea cambiar el compor-
tamiento de notificación luego.

Aquı́ esta una versión de lo anterior que no utiliza el patrón Ob-


servador, escrito por Kevin Altis usando PythonCard, y colocado
aquı́ como un punto de partida para una traducción que sı́ incluye
Observador:

#: c10 : BoxObserver . py
””” Written by Kevin A l t i s as a f i r s t −c ut f o r
c o n v e r t i n g BoxObserver t o Python . The Observer
hasn ’ t been i n t e g r a t e d y e t .
To run t h i s program , you must :
I n s t a l l WxPython from
http : / /www. wxpython . o rg / download . php
I n s t a l l PythonCard . See :
http : / / pythoncard . s o u r c e f o r g e . n e t
”””

from PythonCardPrototype import l o g , model


import random

GRID = 8

c l a s s ColorBoxesTest ( model . Background ) :


d e f on openBackground ( s e l f , t a r g e t , e v e n t ) :
s e l f . document = [ ]
f o r row i n range (GRID ) :
line = []
f o r column i n range (GRID ) :

130
l i n e . append ( s e l f . c r e a t e B o x ( row , column ) )
s e l f . document . append ( l i n e [ : ] )
d e f c r e a t e B o x ( s e l f , row , column ) :
c o l o r s = [ ’ black ’ , ’ blue ’ , ’ cyan ’ ,
’ darkGray ’ , ’ gray ’ , ’ green ’ ,
’ l i g h t G r a y ’ , ’ magenta ’ ,
’ orange ’ , ’ pink ’ , ’ red ’ ,
’ white ’ , ’ y e l l o w ’ ]
width , h e i g h t = s e l f . p a n e l . GetSizeTuple ( )
boxWidth = width / GRID
boxHeight = h e i g h t / GRID
l o g . i n f o ( ” width : ” + s t r ( width ) +
” height :” + str ( height ))
l o g . i n f o ( ” boxWidth : ” + s t r ( boxWidth ) +
” boxHeight : ” + s t r ( boxHeight ) )
# use an empty image , though some o t h e r
# w i d g e t s would work j u s t as w e l l
boxDesc = { ’ type ’ : ’ Image ’ ,
’ s i z e ’ : ( boxWidth , boxHeight ) , ’ f i l e ’ : ’ ’ }
name = ’ box−\%d−%d ’ % ( row , column )
# There i s p r o b a b l y a 1 o f f e r r o r i n th e
# c a l c u l a t i o n below s i n c e t he boxes s h o u l d
# p r o b a b l y 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 o v e rl a p s
boxDesc [ ’ p o s i t i o n ’ ] =
( column ∗ boxWidth , row ∗ boxHeight )
boxDesc [ ’ name ’ ] = name
boxDesc [ ’ backgroundColor ’ ] =
random . c h o i c e ( c o l o r s )
s e l f . components [ name ] = boxDesc
r e t u r n s e l f . components [ name ]

d e f changeNeighbors ( s e l f , row , column , c o l o r ) :

# This a l g o r i t h m w i l l r e s u l t i n changing t h e
# c o l o r o f some boxes more than once , so an
# OOP s o l u t i o n where o n l y n e i g h b o r s a r e asked
# t o change o r boxes check t o s e e i f they a r e
# n e i g h b o r s b e f o r e changing would be b e t t e r

131
# p er t he o r i g i n a l example does t h e whole g r i d
# need t o change i t s s t a t e a t once l i k e i n a
# L i f e program ? s h o u l d th e c o l o r change
# in the propogation of another n o t i f i c a t i o n
# event ?

f o r r i n range (max ( 0 , row − 1 ) ,


min (GRID, row + 2 ) ) :
f o r c i n 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 han dler , so i t i s n ’ t


# s p e c i f i c t o a s i n g l e widget . Image w i d g e t s
# don ’ t have a mouseClick e v e n t ( wxCommandEvent
# i n wxPython )
d e f on mouseUp ( s e l f , t a r g e t , e v e n t ) :
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 )

if name == ’ m a i n ’ :
app = model . PythonCardApp ( ColorBoxesTest )
app . MainLoop ( )
#:˜
Este es el archivo de recursos para ejecutar el programa (ver
PythonCard para más detalles):

#: c10 : BoxObserver . r s r c . py
{ ’ s t a c k ’ : { ’ type ’ : ’ Stack ’ ,
’ name ’ : ’ BoxObserver ’ ,
’ backgrounds ’ : [
{ ’ type ’ : ’ Background ’ ,
’ name ’ : ’ bgBoxObserver ’ ,
’ t i t l e ’ : ’ Demonstrates Observer p a t t e r n ’ ,
’ position ’ : ( 5 , 5) ,
’ size ’ : ( 5 0 0 , 400) ,
’ components ’ : [

132
] # end components
} # end background
] # end backgrounds
} }
#:˜

14.3 Ejercicios
1. Utilizando el enfoque en Synchronization.py, crear una her-
ramienta que ajuste automáticamente todos los métodos en una
clase para proporcionar una rastreo de ejecución, de manera que
se puede ver el nombre del método y cuando entró y salió.

2. Crear un diseño minimalista Observador-observable en dos


clases. Basta con crear el mı́nimo en las dos clases, luego demostrar
su diseño mediante la creación 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 hizo clic
es parte de un parche contiguo del mismo color, entonces todas los
cuadros en ese parche se cambian al color que ha hecho clic. Puede
configurar el juego para la competencia entre los jugadores o para
no perder de vista el número de clics que un solo jugador utiliza
para convertir el campo en un solo color. También puede restringir
el color de un jugador a la primera que fue elegido.

15 11 : Despacho Múltiple
Cuando se trata de múltiples tipos que están interactuando, un pro-
grama puede un programa puede obtener todo desordenado. Por
ejemplo, considere un sistema que analiza y ejecuta expresiones
matemáticas. Usted desea ser capaz de decir Number + Number,
Number * Number, etc., donde Number es la clase base para
una familia de objetos numéricos. Pero cuando usted dice a + b,
y no conoce el tipo exacto de uno u otro a o b, ası́ que ¿cómo se
puede conseguir que interactúen correctamente?

133
La respuesta comienza con algo que probablemente no piensas:
Python sólo realiza despacho individual. Es decir, si está realizando
una operación en más de un objeto cuyo tipo es desconocido, Python
puede invocar el mecanismo de enlace dinámico a uno sólo de esos
tipos. Esto no resuelve el problema, por lo que termina detectando
algunos tipos manualmente y produciendo con eficacia su propio
comportamiento de enlace dinámico.

La solución es llamada multiple dispatching : despacho múltiple.


Recuerde que el polimorfismo puede ocurrir sólo a través de llamadas
a funciones miembro, ası́ que si quiere que el doble de despacho
ocurra, debe haber dos llamadas a la función miembro: el primero
para determinar el primero de tipo desconocido, y el segundo para
determinar el segundo de tipo desconocido. Con despacho múltiple,
usted debe tener una llamada a un método polimórfico para deter-
minar cada uno de los tipos. Generalmente, va a configurar una
configuración tal que una sola llamada de función miembro pro-
duce más de una dinámica llamada a la función miembro y por lo
tanto determina más de un tipo en el proceso. Para obtener este
efecto, usted necesita trabajar con más de una llamada a un método
polimórfico: usted necesitará una llamada para cada despacho. Los
métodos en el siguiente ejemplo se llaman compete( ) y eval( ), y
son ambos miembros del mismo tipo. (En este caso habrá sólo dos
despachos, que se conocen como doble despacho). Si está trabajando
con dos jerarquı́as de tipos diferentes que están interactuando, en-
tonces usted habrá de tener una llamada a un método polimórfico
en cada jerarquı́a.

Aquı́ está un ejemplo de despacho múltiple:

#: c11 : P a p e r S c i s s o r s R o c k . py
# Demonstration o f m u l t i p l e d i s p a t c h i n g .
from future import g e n e r a t o r s
import random

# An enumeration type :
c l a s s Outcome :
def i n i t ( s e l f , value , name ) :
s e l f . value = value

134
s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
def e q ( s e l f , other ) :
r e t u r n s e l f . v a l u e == o t h e r . v a l u e

Outcome .WIN = Outcome ( 0 , ” win ” )


Outcome . LOSE = Outcome ( 1 , ” l o s e ” )
Outcome .DRAW = Outcome ( 2 , ”draw ” )

c l a s s Item ( o b j e c t ) :
def str ( self ):
return s e l f . c l a s s . name

c l a s s Paper ( Item ) :
d e f compete ( s e l f , item ) :
# F i r s t d i s p a t c h : s e l f was Paper
r e t u r n item . e v a l P a p e r ( s e l f )
d e f e v a l P a p e r ( s e l f , item ) :
# Item was Paper , we ’ r e i n Paper
r e t u r n Outcome .DRAW
d e f 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 ’ r e i n Paper
r e t u r n Outcome .WIN
d e f evalRock ( s e l f , item ) :
# Item was Rock , we ’ r e i n Paper
r e t u r n Outcome . LOSE

c l a s s S c i s s o r s ( Item ) :
d e f compete ( s e l f , item ) :
# F i r s t d i s p a t c h : s e l f was S c i s s o r s
r e t u r n item . e v a l S c i s s o r s ( s e l f )
d e f e v a l P a p e r ( s e l f , item ) :
# Item was Paper , we ’ r e i n S c i s s o r s
r e t u r n Outcome . LOSE
d e f 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 ’ r e i n S c i s s o r s
r e t u r n Outcome .DRAW
d e f evalRock ( s e l f , item ) :
# Item was Rock , we ’ r e i n S c i s s o r s

135
r e t u r n Outcome .WIN

c l a s s Rock ( Item ) :
d e f compete ( s e l f , item ) :
# F i r s t d i s p a t c h : s e l f was Rock
r e t u r n item . evalRock ( s e l f )
d e f e v a l P a p e r ( s e l f , item ) :
# Item was Paper , we ’ r e i n Rock
r e t u r n Outcome .WIN
d e f 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 ’ r e i n Rock
r e t u r n Outcome . LOSE
d e f evalRock ( s e l f , item ) :
# Item was Rock , we ’ r e i n Rock
r e t u r n Outcome .DRAW

d e f match ( item1 , item2 ) :


p r i n t ”\%s <−−> \%s : \%s ” \% (
item1 , item2 , item1 . compete ( item2 ) )
# Generate t he i t e m s :
d e 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 i n range ( n ) :
y i e l d ( random . c h o i c e ( Items ) ( ) ,
random . c h o i c e ( Items ) ( ) )
f o r item1 , item2 i n itemPairGen ( 2 0 ) :
match ( item1 , item2 )
#:˜
Esta fue una traducción bastante literal de la versión de Java, y
una de las cosas que usted puede notar es que la información sobre
las distintas combinaciones se codifica en cada tipo de Item. En re-
alidad, termina siendo una especie de tabla excepto que se extiende
a través de todas las clases. Esto no es muy fácil de mantener si
alguna vez espera modificar el comportamiento o para añadir una
nueva clase Item. En su lugar, puede ser más sensible a hacer la
tabla explı́cita, ası́:

#: c11 : P a p e r S c i s s o r s R o c k 2 . py

136
# Multiple dispatching using a table
from future import g e n e r a t o r s
import random

c l a s s Outcome :
def i n i t ( s e l f , value , name ) :
s e l f . value = value
s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
def e q ( s e l f , other ) :
r e t u r n s e l f . v a l u e == o t h e r . v a l u e

Outcome .WIN = Outcome ( 0 , ” win ” )


Outcome . LOSE = Outcome ( 1 , ” l o s e ” )
Outcome .DRAW = Outcome ( 2 , ”draw ” )

c l a s s Item ( o b j e c t ) :
d e f compete ( s e l f , item ) :
# Use a t u p l e f o r t a b l e lookup :
r e t u r n outcome [ s e l f . c l a s s , item . class ]
def str ( self ):
return s e l f . c l a s s . name
c l a s s Paper ( Item ) : p a s s
c l a s s S c i s s o r s ( Item ) : p a s s
c l a s s Rock ( Item ) : p a s s
outcome = {
( Paper , Rock ) : Outcome .WIN,
( Paper , S c i s s o r s ) : Outcome . LOSE,
( 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,
}
d e f match ( item1 , item2 ) :
p r i n t ”\%s <−−> \%s : \%s ” % (
item1 , item2 , item1 . compete ( item2 ) )

137
# Generate t he i t e m s :
d e 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 i n range ( n ) :
y i e l d ( random . c h o i c e ( Items ) ( ) ,
random . c h o i c e ( Items ) ( ) )
f o r item1 , item2 i n itemPairGen ( 2 0 ) :
match ( item1 , item2 )
#:˜
Es un tributo a la flexibilidad de los diccionarios que una tupla se
puede utilizar como una clave tan fácilmente como un solo objeto.

138
15.1 Visitor, un tipo de despacho múltiple
La suposición es que usted tiene una jerarquı́a primaria de clases
que es fija; tal vez es de otro proveedor y no puede hacer cam-
bios en esa jerarquı́a. Sin embargo, usted tenı́a como añadir nuevos
métodos polimórficos a esa jerarquı́a, lo que significa que normal-
mente habrı́a que añadir algo a la interfaz de la clase base. Ası́ que
el dilema es que usted necesita agregar métodos a la clase base, pero
no se puede tocar la clase base. ¿Cómo se obtiene alrededor de esto?.

El patrón de diseño que resuelve este tipo de problemas se llama


un ”visitor : visitante” (el definitivo en el libro Design Patterns),
y se basa en el esquema de despacho doble mostrado en la última
sección.

El patrón visitante le permite extender la interfaz del tipo pri-


mario mediante la creación de una jerarquı́a de clases por separado
de tipo Visitor para virtualizar las operaciones realizadas en el tipo
primario. Los objetos del tipo primario simplemente ”aceptan” al
visitante, a continuación, llamar a la función miembro de visitor
dinámicamente enlazado.

#: c11 : F l o w e r V i s i t o r s . py
# Demonstration o f ” v i s i t o r ” p a t t e r n .
from future import g e n e r a t o r s
import random

# The Flower h i e r a r c h y cannot be changed :


c l a s s Flower ( o b j e c t ) :
def accept ( s e l f , v i s i t o r ) :
visitor . visit ( self )
def 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
def eat ( s e l f , eater ) :
p r i n t s e l f , ” e a t e n by ” , e a t e r
def str ( self ):
return s e l f . c l a s s . name

c l a s s G l a d i o l u s ( Flower ) : p a s s

139
c l a s s Runuculus ( Flower ) : p a s s
c l a s s Chrysanthemum ( Flower ) : p a s s
class Visitor :
def str ( self ):
return s e l f . c l a s s . name
c l a s s Bug ( V i s i t o r ) : p a s s
c l a s s P o l l i n a t o r ( Bug ) : p a s s
c l a s s P r e d a t o r ( Bug ) : p a s s

# Add th e a b i l i t y t o 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 ) :
def v i s i t ( s e l f , flower ) :
flower . pollinate ( s e l f )

# Add th e a b i l i t y t o 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 ) :
def v i s i t ( s e l f , flower ) :
flower . pollinate ( s e l f )

# Add th e a b i l i t y t o do ”Worm” a c t i v i t i e s :
c l a s s Worm( P r e d a t o r ) :
def v i s i t ( s e l f , flower ) :
flower . eat ( s e l f )

d e f flowerGen ( n ) :
f l w r s = Flower . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d random . c h o i c e ( f l w r s ) ( )

# I t ’ s almost as i f I had a method t o Perform


# v a r i o u s ”Bug” o p e r a t i o n s on a l l F lowe rs :
bee = Bee ( )
f l y = Fly ( )
worm = Worm( )
f o r f l o w e r i n flowerGen ( 1 0 ) :
f l o w e r . a c c e p t ( bee )
flower . accept ( f l y )
f l o w e r . a c c e p t (worm)
#:˜

140
15.2 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 llamada
Project que crea los diferentes habitantes y les lleva a interact( )
entre sı́ utilizando despacho múltiple.

2. Modificar el ejemplo de arriba para hacer las interacciones más


detalladas. Cada Inhabitant puede producir al azar un Weapon
usando getWeapon( ): un Dwarf usa Jargon o Play, un Elf usa
InventFeature o SellImaginaryProduct, y un Troll usa Edict
y Schedule. Usted debe decidir qué armas ”ganar” y ”perder” en
cada interacción (como en PaperScissorsRock.py). Agregar una
función miembro battle( ) a Project que lleva dos Inhabitants
y coinciden unos contra los otros. Ahora cree una función miem-
bro meeting( ) para Project que crea grupos de Dwarf, Elf y
Manager y batallas contra los grupos entre sı́ hasta que sólo los
miembros de un grupo se quedan de pie. Estos son los ”ganadores”.

3. Modificar PaperScissorsRock.py para reemplazar el doble


despacho con una búsqueda en la tabla. La forma más sencilla de
hacerlo es crear un Map de Maps, con la clave de cada Map la clase
de cada objeto. Entonces usted puede hacer la búsqueda diciendo:
((Map)map.get(o1.getClass())).get(o2.getClass()).
Observe lo fácil que es volver a configurar el sistema. ¿Cuándo
es más apropiado utilizar este enfoque vs. difı́ciles de codificación
los despachos dinámicos? ¿Se puede crear un sistema que tiene la
sencillez sintáctica de uso del despacho dinámico, pero utiliza una
búsqueda en la tabla?

4. Modificar Ejercicio 2 para utilizar la técnica de tabla de con-


sulta descrito en el Ejercicio 3.

141
16 12 : Patrón Refactorización
Este capı́tulo no ha tenido traducción significativa todavı́a.

En este capı́tulo se analizará el proceso de resolución de un prob-


lema mediante la aplicación de patrones de diseño de una forma
evolutiva. Es decir, un primer diseño de corte será utilizado para
la solución inicial, y luego esta solución será examinada y diversos
patrones de diseño se aplicarán al problema (algunos de los cuales
trabajarán, y algunos de los cuales no). La pregunta clave que siem-
pre se preguntó en la búsqueda de soluciones mejoradas es ”¿qué va
a cambiar?”

Este proceso es similar a lo que Martin Fowler habla en su libro


Refactoring:Improving the Design of Existing Code19 (a pesar de que
tiende a hablar de piezas de código más de diseños a nivel de patrón).
Se comienza con una solución, y luego cuando se descubre que no
continúa para satisfacer sus necesidades, lo arregla. Por supuesto,
esta es una tendencia natural, pero en la programación informática
que ha sido muy difı́cil de lograr con programas de procedimiento, y
la aceptación de la idea de que podemos refactorizar código y diseños
se añade al cuerpo de prueba de que la programación orientada a
objetos es ”una cosa buena.”

16.1 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 información de tipo
especı́fico se pierde. Pero más tarde, la información de tipo especı́fico
debe ser recuperada para ordenar adecuadamente la basura. En la
solución inicial, RTTI(descrito en el capı́tulo 12 de Thinking in Java,
Segunda edición) es utilizado.

Esto no es un diseño trivial, ya que tiene una restricción añadida.


Eso es lo que hace que sea interesante —se parece más a los proble-
mas desordenados que es probable que encuentre en su trabajo. La
restricción adicional es que la basura llega a la planta de reciclaje de
19 Addison-Wesley, 1999.

142
la basura todo mezclado. El programa debe modelar la clasificación
de esa basura. Aquı́ es donde entra en juego RTTI : usted tiene un
montón de piezas anónimas de basura, y el programa se da cuenta
exactamente de qué tipo son.

# c12 : r e c y c l e a : RecycleA . py
# R e c y c l i n g with RTTI .

c l a s s Trash :
p r i v a t e double weight
def i n i t ( s e l f , double wt ) : weight = wt
a b s t r a c t double g e t V a l u e ( )
double getWeight ( ) : r e t u r n weight
# Sums t he v a l u e o f Trash i n a b i n :
s t a t i c v o i d sumValue ( I t e r a t o r i t ) :
double v a l = 0 . 0 f
w h i l e ( i t . hasNext ( ) ) :
# One kind o f RTTI :
# A dynamically −checked c a s t
Trash t = ( Trash ) i t . next ( )
# Polymorphism i n a c t i o n :
v a l += t . getWeight ( ) ∗ t . g e t V a l u e ( )
print (
” weight o f ” +
# Using RTTI t o g e t type
# i n f o r m a t i o n about t he c l a s s :
t . g e t C l a s s ( ) . getName ( ) +
” = ” + t . getWeight ( ) )

p r i n t ” Total v a l u e = ” + v a l

c l a s s Aluminum ( Trash ) :
s t a t i c double v a l = 1 . 6 7 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval

c l a s s Paper ( Trash ) :

143
s t a t i c double v a l = 0 . 1 0 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval

c l a s s G l a s s ( Trash ) :
s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval

c l a s s RecycleA ( UnitTest ) :
Collection
b in = A r r a y L i s t ( ) ,
glassBin = ArrayList () ,
paperBin = A r r a y L i s t ( ) ,
alBin = ArrayList ()
def init ( self ):
# F i l l up th e Trash b in :
f o r ( i n t i = 0 i < 30 i ++)
s w i t c h ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :
case 0 :
b in . add ( new
Aluminum ( Math . random ( ) ∗ 1 0 0 ) )
break
case 1 :
b in . add ( new
Paper ( Math . random ( ) ∗ 1 0 0 ) )
break
case 2 :
b in . add ( new
G l a s s ( Math . random ( ) ∗ 1 0 0 ) )

def test ( s e l f ) :
I t e r a t o r s o r t e r = b in . i t e r a t o r ( )
# S o r t t he Trash :
w h i l e ( s o r t e r . hasNext ( ) ) :

144
Object t = s o r t e r . next ( )
# RTTI t o show c l a s s membership :
i f ( t i n s t a n c e o f Aluminum )
a l B i n . 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 s B i n . add ( t )

Trash . sumValue ( a l B i n . 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 s B i n . i t e r a t o r ( ) )
Trash . sumValue ( b in . i t e r a t o r ( ) )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleA ( ) . t e s t ( )

# :˜
En los listados de código fuente disponibles para este libro, este
archivo se colocará en el subdirectorio recyclea que se ramifica
desde el subdirectorio c12 (para el Capı́tulo 12). La herramienta
de desembalaje se encarga de colocarlo en el subdirectorio correcto.
La razón para hacer esto es que este capı́tulo reescribe este ejemplo
particular, un número de veces y poniendo cada versión en su pro-
pio directorio (utilizando el paquete por defecto en cada directorio
para que invocando el programa es fácil), los nombres de clase no se
enfrentarán.

Varios objetos ArrayList se crean para mantener referencias


Trash. Claro, ArrayLists en realidad mantenga Objects por lo
que sostendrán nada en absoluto. La razón por la que tienen Trash
(o algo derivado de Trash) es sólo porque usted ha sido cuidadoso
para no poner en nada excepto Trash. Si usted hace poner algo
”equivocado” en el ArrayList, usted no conseguirá ninguna compi-
lación — advertencias de tiempo o errores — usted descubrirá sólo
a través de una excepción en tiempo de ejecución.

Cuando las referencias Trash son añadidas, pierden sus iden-


tidades especı́ficas y se vuelven simplemente Object references

145
(son upcast). Sin embargo, debido polimorfismo del comportamiento
apropiado se sigue produciendo cuando los métodos dinámicamente
enlazados son llamados a través de la Iterator sorter, una vez que
el Object resultante ha sido lanzado de nuevo a Trash. sum-
Value( ) también toma un Iterator para realizar operaciones en
cada objeto en el ArrayList.

Parece una tonterı́a upcast los tipos de Trash en una retención


de contenedores de referencias de tipo base, y luego dar la vuelta y
abatido. ¿Por qué no poner la basura en el recipiente adecuado en el
primer lugar? (De hecho, se trata de todo el enigma del reciclaje).
En este programa serı́a fácil reparar, pero a veces la estructura y
la flexibilidad de un sistema pueden beneficiarse enormemente de
downcasting.

El programa cumple con los requisitos de diseño: funciona. Esto


podrı́a estar bien, siempre y cuando se trata de una solución de un
solo tiro Ahora bien, un programa útil tiende a evolucionar con el
tiempo, por lo que debe preguntar: ”¿Qué pasa si la situación cam-
bia?” Por ejemplo, el cartón es ahora un valioso producto reciclable,
ası́ que cómo eso será integrado en el sistema (especialmente si el
programa es grande y complicado).Desde lo anterior, la codificación
tipo de verificación en la declaración switch podrı́a ser esparcido en
todo el programa, usted debe ir a buscar todo ese código cada vez
que se agrega un nuevo tipo, y si se le pasa una el compilador no le
dará ninguna ayuda señalando un error.

La clave para el mal uso de RTTI aquı́ es que cada tipo se pone a
prueba. Si usted está buscando sólo un subconjunto de tipos porque
ese subconjunto necesita un tratamiento especial, eso probablemente
está muy bien. Pero si usted está buscando para cada tipo dentro
de una sentencia switch, entonces usted está probablemente perdi-
endo un punto importante, y definitivamente hacer su código menos
mantenible. En la siguiente sección vamos a ver cómo este programa
ha evolucionado a lo largo de varias etapas para llegar a ser mucho
más flexible. Esto debe resultar un ejemplo valioso en el diseño del
programa.

146
16.2 Mejorando el diseño
Las soluciones en Design Patterns se organizan en torno a la pre-
gunta ”¿Qué va a cambiar a medida que evoluciona este programa?”
Esta suele ser la pregunta más importante que usted puede pregun-
tar acerca de cualquier diseño. Si usted puede construir su sistema
en torno a la respuesta, los resultados serán de dos vertientes: no sólo
su sistema permite un fácil (y barato) mantenimiento, pero también
se pueden producir componentes que son reutilizables, de modo que
los otros sistemas se pueden construir de forma más económica. Esta
es la promesa de la programación orientada a objetos, pero esto no
sucede automáticamente; se requiere el pensamiento y la visión de
su parte. En esta sección veremos cómo este proceso puede suceder
durante el refinamiento de un sistema.

A la pregunta ”¿Qué va a cambiar? para el sistema de reciclaje


es una respuesta común: se añadirán más tipos al sistema. El obje-
tivo del diseño, entonces, es hacer de esta adición de tipos lo menos
doloroso posible. En el programa de reciclaje, nos gustarı́a encap-
sular todos los lugares donde se menciona la información de tipo
especı́fico, ası́ (si no por otra razón) los cambios se pueden localizar
a esas encapsulaciones. Resulta que este proceso también limpia el
resto del código considerablemente.

16.2.1 “Hacer más objetos”

Esto nos lleva a un principio de diseño orientado a objetos en gen-


eral que escuché por primera vez hablado por Grady Booch: ”Si
el diseño es demasiado complicado, hacer más objetos.” Esto es si-
multáneamente contrario a la intuición y ridı́culamente simple, y sin
embargo es la guı́a más útil que he encontrado. (Es posible observar
que ”hacer más objetos” a menudo es equivalente a ”agregar otro
nivel de indirección.”) En general, si usted encuentra un lugar con
código desordenado, tenga en cuenta qué tipo de clase limpiarı́a eso.
A menudo, el efecto secundario de la limpieza del código será un
sistema que tiene mejor estructura y es más flexible.

Considere primero el lugar donde se crean los objetos Trash, que


es una sentencia switch dentro de main():

147
f o r ( i n t i = 0 i < 30 i ++)
s w i t c h ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :
case 0 :
b in . add ( new
Aluminum ( Math . random ( ) ∗ 1 0 0 ) )
break
case 1 :
b in . add ( new
Paper ( Math . random ( ) ∗ 1 0 0 ) )
break
case 2 :
b in . add ( new
G l a s s ( Math . random ( ) ∗ 1 0 0 ) )
Esto es definitivamente desordenado, y también un lugar donde
usted debe cambiar el código cada vez que se agrega un nuevo tipo.
Si comúnmente se añaden nuevos tipos, una mejor solución es un
sólo método que toma toda la información necesaria y produce una
referencia a un objeto del tipo correcto, ya upcast a un objeto de
basura. En Design Patterns esto se conoce en general como un
patrón creacional (de los cuales hay varios). El patrón especı́fico
que se aplicará aquı́ es una variante del Factory Method : Método
de fábrica. Aquı́, el método de fábrica es un miembro static de
Trash, pero más comúnmente es un método que se anula en la
clase derivada.

La idea del método de fábrica es que se le pasa la información


esencial que necesita saber para crear su objeto, a continuación,
retroceder y esperar por la referencia (ya upcast al tipo base) para
que salga como el valor de retorno. A partir de entonces, usted trata
al objeto polimórficamente. Ası́, usted ni siquiera necesita saber el
tipo exacto de objeto que se crea. De hecho, el método de fábrica
lo esconde de usted para evitar el mal uso accidental. Si desea uti-
lizar el objeto sin polimorfismo, debe utilizar explı́citamente RTTI
y fundición.

Pero hay un pequeño problema, especialmente cuando se utiliza el


enfoque más complicado (no se muestra aquı́) de hacer que el método
de fábrica en la clase base y anulando en las clases derivadas. ¿Qué

148
pasa si la información requerida en la clase derivada requiere argu-
mentos más o diferentes? ”La creación de más objetos” resuelve este
problema. Para implementar el método de fábrica, la clase Trash
consigue un nuevo método llamado factory. Para ocultar los datos
creacionales, hay una nueva clase llamada Messenger que lleva
toda la información necesaria para el método factory para crear
el objeto Trash apropiado (hemos empezado haciendo referencia a
Messenger como un patrón de diseño, pero es bastante simple que
no puede elegir elevarlo a ese estado). Aquı́ esta una simple imple-
mentación de Messenger:

c l a s s Messenger :
i n t type
# Must change t h i s t o add a n o t h e r type :
s t a t i c f i n a l i n t MAX NUM = 4
double data
def i n i t ( s e l f , i n t typeNum , double v a l ) :
type = typeNum % MAX NUM
data = v a l
El único trabajo de un objeto Messenger es mantener la infor-
mación para el método factory( ). Ahora, si hay una situación en
la que factory( ) necesita información más o diferente para crear
un nuevo tipo de objeto Trash, la interfaz factory( ) no necesita
ser cambiada. La clase Messenger puede ser cambiada mediante
la adición de nuevos datos y nuevos constructores, o en el la manera
orientada a objetos más tı́pica de las subclases.

El método factory( ) para este sencillo ejemplo se ve ası́:

s t a t i c Trash f a c t o r y ( Messenger i ) :
s w i t c h ( i . type ) :
d e f a u l t : # To q u i e t th e c o m p i l e r
case 0:
r e t u r n Aluminum ( i . data )
case 1:
r e t u r n Paper ( i . data )
case 2:
r e t u r n G l a s s ( i . data )

149
# Two l i n e s h e r e :
case 3:
r e t u r n Cardboard ( i . data )
Aquı́, la determinación del tipo exacto de objeto es simple, pero
se puede imaginar un sistema más complicado en el que factory( )
utiliza un algoritmo elaborado. El punto es que está ahora escon-
dido en un lugar, y usted sabe llegar a este lugar cuando se agregan
nuevos tipos.

La creación de nuevos objetos es ahora mucho más simple en


main( ):

f o r ( i n t i = 0 i < 30 i ++)
b in . add (
Trash . f a c t o r y (
Messenger (
( i n t ) ( Math . random ( ) ∗ Messenger .MAX NUM) ,
Math . random ( ) ∗ 1 0 0 ) ) )
Se crea un objeto Messenger para pasar los datos en factory(
), que a su vez produce una especie de objeto Trash en la pila
y devuelve la referencia que se agrega al ArrayList bin. Claro,
si cambia la cantidad y tipo de argumento, esta declaración to-
davı́a necesitará ser modificada, pero que puede ser eliminada si
la creación del objeto Messenger está automatizada. Por ejemplo,
un ArrayList de argumentos puede ser pasado en el constructor de
un objeto Messenger (o directamente en una llamada factory( ),
para el caso). Esto requiere que los argumentos sean analizados y
verificados en tiempo de ejecución, pero proporciona la mayor flex-
ibilidad.

Se puede ver en el código que el problema ”vector de cambio”


de la fábrica es responsable de resolver: si agrega nuevos tipos al
sistema (el cambio), el único código que debe ser modificado está
dentro de la fábrica, por lo que la fábrica aı́sla el efecto de ese cambio.

150
16.3 Un patrón para la creación de prototipos
Un problema con el diseño anterior es que todavı́a requiere una
ubicación central donde deben ser conocidos todos los tipos de los
objetos: dentro del método factory(). Si regularmente se agregan
nuevos tipos al sistema, el método factory( ) debe cambiarse para
cada nuevo tipo. Cuando usted descubre algo como esto, es útil para
tratar de avanzar un paso más y mover toda la información sobre
el tipo — incluyendo su creación — en la clase que representa ese
tipo. De esta manera, la única cosa que necesita hacer para agregar
un nuevo tipo al sistema es heredar una sola clase.

Para mover la información relativa a la creación de tipo en cada


tipo especı́fico de Trash, se utilizará el patrón ”prototipo” (del libro
Design Patterns). La idea general es que usted tiene una secuencia
principal de los objetos, uno de cada tipo que usted está intere-
sado en hacer. Los objetos en esta secuencia sólo se utilizan para
la fabricación de nuevos objetos, utilizando una operación que no
es diferente del esquema clone( ) incorporado en clase raı́z Object
de Java. En este caso, vamos a nombrar el método de clonación
tClone( ). Cuando esté listo para hacer un nuevo objeto, presumi-
blemente usted tiene algún tipo de información que establece el tipo
de objeto que desea crear, a continuación, se mueve a través de la
secuencia maestra comparando su información con cualquier infor-
mación apropiada que se encuentra en los objetos de prototipo en
la secuencia principal. Cuando usted encuentra uno que se ajuste a
sus necesidades, clonarlo.

En este esquema no hay información modificable para la creación.


Cada objeto sabe cómo exponer la información adecuada y la forma
de clonarse a sı́ mismo. Ası́, el método factory( ) no necesita ser
cambiado cuando se añade un nuevo tipo al sistema.

Un enfoque al problema del prototipado es agregar un número


de métodos para apoyar la creación de nuevos objetos. Ahora bien,
en Java 1.1 ya hay apoyo para la creación de nuevos objetos si tiene
una referencia al objeto Class. Con Java 1.1 reflection : reflexión
(introducido en el capı́tulo 12 de Thinking in Java, segunda edición)
puede llamar a un constructor, incluso si tiene sólo una referencia
al objeto Class. Esta es la solución perfecta para el problema del

151
prototipado.

La lista de los prototipos será representada indirectamente por


una lista de referencias a todos los objetos de Class que desea crear.
En adición, si el prototipado falla, el método factory( ) asumirá
que es porque un objeto particular Class no estaba en la lista, y
se tratará de cargarlo. Al cargar los prototipos de forma dinámica
como este, la clase Trash no necesita saber con qué tipos está traba-
jando, por lo que no necesita ninguna modificación al agregar nuevos
tipos. Esto permite que sea fácilmente reutilizable durante todo el
resto del capı́tulo.

# c12 : t r a s h : 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 weight
def i n i t ( s e l f , double wt ) : weight = wt
def init ( self ):
def getValue ( s e l f )
d e f getWeight ( s e l f ) : r e t u r n weight
# Sums t he v a l u e o f Trash g i v e n an
# I t e r a t o r t o any c o n t a i n e r o f Trash :
d e f sumValue ( s e l f , I t e r a t o r i t ) :
double v a l = 0 . 0 f
w h i l e ( i t . hasNext ( ) ) :
# One kind o f RTTI :
# A dynamically −checked c a s t
Trash t = ( Trash ) i t . next ( )
v a l += t . getWeight ( ) ∗ t . g e t V a l u e ( )
print (
” weight o f ” +
# Using RTTI t o g e t type
# i n f o r m a t i o n about t he c l a s s :
t . g e t C l a s s ( ) . getName ( ) +
” = ” + t . getWeight ( ) )

p r i n t ” Total v a l u e = ” + v a l

152
# Remainder o f c l a s s p r o v i d e s
# support f o r prototyping :
p r i v a t e s t a t i c L i s t tr as hTy pes =
ArrayList ()
d e 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 e n ( tra shT yp es ) i ++):
# Somehow d e t e r m i n e t he type
# t o c r e a t e , and c r e a t e one :
C l a s s t c = ( C l a s s ) tra sh Typ es . g e t ( i )
i f ( t c . getName ( ) . i n d e x ( i n f o . i d ) != −1):
try :
# Get th e dynamic c o n s t r u c t o r method
# t h a t t a k e s a double argument :
Constructor ctor = tc . getConstructor (
C l a s s [ ] { double . c l a s s )
# C a l l th e c o n s t r u c t o r
# to c r e a t e a o b j e c t :
r e t u r n ( Trash ) c t o r . n ew I ns t an ce (
Object [ ] { Double ( i n f o . data ) )
c a t c h ( Ex cep ti on ex ) :
ex . p r i n t S t a c k T r a c e ( System . e r r )
throw RuntimeException (
” Cannot Create Trash ” )

# C l a s s was not i n th e l i s t . Try t o l o a d i t ,


# but i t must be i n your c l a s s path !
try :
p r i n t ” Loading ” + i n f o . i d
tr ash Ty pes . add ( C l a s s . forName ( i n f o . i d ) )
c a t c h ( Ex cep ti on e ) :
e . p r i n t S t a c k T r a c e ( System . e r r )
throw RuntimeException (
” P rot ot ype not found ” )

# Loaded s u c c e s s f u l l y .
# R e c u r s i v e c a l l s h o u l d work :
return factory ( info )

p u b l i c s t a t i c c l a s s Messenger :

153
public String id
public double data
public Messenger ( S t r i n g name , double v a l ) :
id = name
data = val

# :˜
La clase básica Trash y sumValue( ) permanecen como antes.
El resto de la clase soporta el patrón de prototipado. Primero ve
dos clases internas (que se hacen static, ası́ que son las clases in-
ternas solamente para los propósitos de la organización de código)
describiendo excepciones que pueden ocurrir. Esto es seguido por
un ArrayList llamado trashTypes, que se utiliza para mantener
las referencias Class.

En Trash.factory( ), el String dentro del objeto Messenger


id (una versión diferente de la clase Messenger que el de la dis-
cusión previa) contiene el nombre del tipo de la Trash a crearse;
este String es comparado con los nombres Class en la lista. Si hay
una coincidencia, entonces ese es el objeto a crear. Por supuesto,
hay muchas formas para determinar qué objeto desea hacer. Éste
se utiliza para que la información leı́da desde un archivo se pueda
convertir en objetos.

Una vez que haya descubierto qué tipo de Trash crear, a con-
tinuación, los métodos de reflexión entran en juego. El método
getConstructor( ) toma un argumento que es un array de refer-
encias Class. Este array representa los argumentos, en su debido
orden, para el constructor que usted está buscando. Aquı́, el array
es creado de forma dinámica usando Jvaa 1.1 la sintaxis de creación
de array:

C l a s s [ ] : double . c l a s s
Este código asume que cada tipo Trash tiene un constructor
que toma un double (y observe que double.class es distinto de
Double.class). También es posible, por una solución más flexible,
llamar getConstructors( ), que devuelve un array con los posibles
constructores.

154
Lo que viene de vuelta de getConstructor( ) es una referencia a
un objeto Constructor (parte de java.lang.reflect). Usted llama
al constructor de forma dinámica con el método 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 dentro
de una clase de contenedor de modo que pueda ser parte de este
array de objetos. El proceso de llamar newInstance( ) extrae el
double, pero se puede ver que es un poco confuso — un argumento
puede ser un double o un Double, pero cuando se hace la llamada
siempre se debe pasar en un Double. Afortunadamente, existe este
problema sólo para los tipos primitivos.

Una vez que entienda cómo hacerlo, el proceso de crear un nuevo


objeto dado sólo una referencia Class es muy simple. Reflexión
también le permite llamar métodos en esta misma manera dinámica.

Por supuesto, la referencia apropiada Class podrı́a no estar en la


lista trashTypes. En este caso, el return en el bucle interno no se
ejecuta y que se retirará al final. Aquı́, el programa trata de recti-
ficar la situación mediante la carga del objeto Class dinámicamente
y agregarlo a la lista trashTypes. Si aún ası́ no se puede encon-
trar algo anda realmente mal, pero si la carga tiene éxito, entonces el
método factory se llama de forma recursiva para volver a intentarlo.

Como verá, la belleza de este diseño es que el código no necesita


ser cambiado, independientemente de las diferentes situaciones en
que se utilizará (asumiendo que todas las subclases Trash contiene
un constructor que toma un solo argumento double).

16.4 Subclases Trash


Para encajar en el esquema de prototipado, lo único que se requiere
de cada nueva subclase de Trash es que contiene un constructor que
toma un argumento double. Java reflexión se encarga de todo lo

155
demás.

Éstos son los diferentes tipos de Trash, cada uno en su propio


archivo, pero parte del paquete Trash (de nuevo, para facilitar la
reutilización dentro del capı́tulo):

# c12 : t r a s h : Aluminum . py
# The Aluminum c l a s s with p r o t o t y p i n g .

c l a s s Aluminum ( Trash ) :
p r i v a t e s t a t i c double v a l = 1 . 6 7 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal

# :˜
# c12 : t r a s h : Paper . py
# The Paper c l a s s with p r o t o t y p i n g .

c l a s s Paper ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 1 0 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal

# :˜
# c12 : t r a s h : G l a s s . py
# The G l a s s c l a s s with p r o t o t y p i n g .

c l a s s G l a s s ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal

# :˜

156
Y aquı́ hay un nuevo tipo de Trash(basura):
# c12 : t r a s h : Cardboard . py
# The Cardboard c l a s s with p r o t o t y p i n g .

c l a s s Cardboard ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal

# :˜
Se puede ver que, aparte del constructor, no hay nada de especial
en cualquiera de estas clases.

16.5 Analizar Trash desde un archivo externo


La información sobre los objetos Trash será leı́do desde un archivo
exterior. El archivo cuenta con toda la información necesaria sobre
cada pieza de basura en una sola lı́nea en la forma de Trash:weight,
como:

# c12 : t r a s h : Trash . dat


c12 . t r a s h . G l a s s : 5 4
c12 . t r a s h . Paper : 2 2
c12 . t r a s h . Paper : 1 1
c12 . t r a s h . G l a s s : 1 7
c12 . t r a s h . Aluminum : 8 9
c12 . t r a s h . Paper : 8 8
c12 . t r a s h . Aluminum : 7 6
c12 . t r a s h . Cardboard : 9 6
c12 . t r a s h . Aluminum : 2 5
c12 . t r a s h . Aluminum : 3 4
c12 . t r a s h . G l a s s : 1 1
c12 . t r a s h . G l a s s : 6 8
c12 . t r a s h . G l a s s : 4 3
c12 . t r a s h . Aluminum : 2 7
c12 . t r a s h . Cardboard : 4 4

157
c12 . t r a s h . Aluminum : 1 8
c12 . t r a s h . Paper : 9 1
c12 . t r a s h . G l a s s : 6 3
c12 . t r a s h . G l a s s : 5 0
c12 . t r a s h . G l a s s : 8 0
c12 . t r a s h . Aluminum : 8 1
c12 . t r a s h . Cardboard : 1 2
c12 . t r a s h . G l a s s : 1 2
c12 . t r a s h . G l a s s : 5 4
c12 . t r a s h . Aluminum : 3 6
c12 . t r a s h . Aluminum : 9 3
c12 . t r a s h . G l a s s : 9 3
c12 . t r a s h . Paper : 8 0
c12 . t r a s h . G l a s s : 3 6
c12 . t r a s h . G l a s s : 1 2
c12 . t r a s h . G l a s s : 6 0
c12 . t r a s h . Paper : 6 6
c12 . t r a s h . Aluminum : 3 6
c12 . t r a s h . Cardboard : 2 2
# :˜
Tenga en cuenta que la ruta de clase debe ser incluido al dar los
nombres de las clases, de lo contrario la clase no será encontrada.

Este archivo se lee utilizando la herramienta StringList definida


previamente, y cada lı́nea es recogido aparte usando el método
String indexOf( ) para producir el ı́ndice del ‘:’. Esto se utiliza
primero con el método String substring( ) para extraer el nombre
del tipo de basura, y al lado para obtener el valor que se convirtió en
un double con el método static Double.valueOf( ). El método
trim( ) elimina los espacios en blanco en ambos extremos de un
string : cadena.

El analizador Trash es colocado en un archivo separado, ya que


se reutilizará en todo este capı́tulo:

# c12 : t r a s h : ParseTrash . py
# Parse f i l e c o n t e n t s i n t o Trash o b j e c t s ,
# p l a c i n g each i n t o a F i l l a b l e h o l d e r .

158
c l a s s ParseTrash :
def f i l l B i n ( String filename , F i l l a b l e bin ) :
f o r l i n e i n open ( f i l e n a m e ) . r e a d l i n e s ( ) :
S t r i n g type = l i n e . s u b s t r i n g ( 0 ,
l i n e . index ( ’ : ’ ) ) . s t r i p ( )
double weight = Double . valueOf (
l i n e . s u b s t r i n g ( l i n e . index ( ’ : ’ ) + 1)
. s t r i p ( ) ) . doubleValue ( )
b in . addTrash (
Trash . f a c t o r y (
Trash . Messenger ( type , weight ) ) )

# S p e c i a l c a s e t o handle C o l l e c t i o n :
d e f f i l l B i n ( S t r i n g f i l e n a m e , C o l l e c t i o n bi n ) :
f i l l B i n ( f i l e n a m e , F i l l a b l e C o l l e c t i o n ( b in ) )

# :˜
En RecycleA.py, un ArrayList se utiliza para contener los ob-
jetos Trash. Sin embargo, otros tipos de contenedores pueden ser
utilizados también. Para permitir esto, la primera versión de fill-
Bin( ) hace una referencia a un Fillable, lo cual es simplemente
una interface que soporta un método llamado addTrash( ):

# c12 : t r a s h : F i l l a b l e . py
# Any o b j e c t t h a t can be f i l l e d with Trash .

class Fillable :
d e f addTrash ( s e l f , Trash t )
# :˜
Cualquier cosa que soporta esta interfaz se puede utilizar con
fillBin. Claro, Collection no implementa Fillable, por lo que
no va a funcionar. Dado que Collection se utiliza en la mayorı́a
de los ejemplos, tiene sentido añadir un segundo método fillBin(
) sobrecargado que toma un Collection. Cualquier Collection a
continuación, se puede utilizar como un objeto Fillable usando una
clase adaptador:

# c12 : t r a s h : F i l l a b l e C o l l e c t i o n . py

159
# Adapter t h a t makes a C o l l e c t i o n F i l l a b l e .

class FillableCollection ( Fillable ):


private Collection c
def i n i t ( s e l f , C o l l e c t i o n cc ) :
c = cc

d e f addTrash ( s e l f , Trash t ) :
c . add ( t )

# :˜
Se puede ver que el único trabajo de esta clase es conectar el
método addTrash( ) de Fillable a Collection’s add( ). Con
esta clase en la mano, el método sobrecargado fillBin( ) se puede
utilizar con un Collection en ParseTrash.py.

public s t a t i c void
f i l l B i n ( S t r i n g f i l e n a m e , C o l l e c t i o n b in ) :
f i l l B i n ( f i l e n a m e , F i l l a b l e C o l l e c t i o n ( b in ) )
Este enfoque funciona para cualquier clase de contenedor que
se utiliza con frecuencia. Alternativamente, la clase de contenedor
puede proporcionar su propio adaptador que implementa Fillable.
(Usted verá esto después, en DynaTrash.py.)

16.6 Reciclaje con prototipos


Ahora se puede ver la versión revisada de RecycleA.py utilizando
la técnica de prototipos:

# c12 : r e c y c l e a p : RecycleAP . py
# R e c y c l i n g with RTTI and P r o t o t y p e s .

c l a s s RecycleAP ( UnitTest ) :
Collection
b in = A r r a y L i s t ( ) ,
glassBin = ArrayList () ,
paperBin = A r r a y L i s t ( ) ,

160
alBin = ArrayList ()
def init ( self ):
# F i l l up th e Trash b in :
ParseTrash . f i l l B i n (
” . . / t r a s h / Trash . dat ” , b in )

def test ( s e l f ) :
I t e r a t o r s o r t e r = b in . i t e r a t o r ( )
# S o r t t he Trash :
w h i l e ( s o r t e r . hasNext ( ) ) :
Object t = s o r t e r . next ( )
# RTTI t o show c l a s s membership :
i f ( t i n s t a n c e o f Aluminum )
a l B i n . 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 s B i n . add ( t )

Trash . sumValue ( a l B i n . 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 s B i n . i t e r a t o r ( ) )
Trash . sumValue ( b in . i t e r a t o r ( ) )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleAP ( ) . t e s t ( )

# :˜
Todos los objetos Trash, ası́ como las clases ParseTrash y de
apoyo, 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 descripciones


Trash y el análisis de ese archivo han sido envuelto en el método
static ParseTrash.fillBin( ), por lo que ahora ya no es parte de
nuestro enfoque de diseño. Verá que en el resto del capı́tulo, no
importa que se agregan nuevas clases, ParseTrash.fillBin( ) con-
tinuará funcionando sin cambios, lo que indica un buen diseño.

161
En términos de creación de objetos, este diseño en efecto, localiza
severamente los cambios que necesita hacer para agregar un nuevo
tipo al sistema. Ahora bien, hay un problema significativo en el
uso de RTTI que se muestra claramente aquı́. El programa parece
funcionar bien, y sin embargo, nunca se detecta algún cardboard :
cartón, a pesar de que cardboard está en la lista! Esto sucede de-
bido a el uso de RTTI, que busca sólo los tipos que le indican que
debe buscar. La pista que RTTI está siendo mal utilizada es que
cada tipo en el sistema se está probando, en lugar de un solo tipo o
subconjunto de tipos. Como se verá más adelante, hay maneras de
utilizar polimorfismo en lugar de cuando se está probando para cada
tipo. Pero si usa RTTI mucho de esta manera, y añade un nuevo
tipo a su sistema, usted puede olvidar fácilmente hacer los cambios
necesarios en su programa y producir un error difı́cil de encontrar.
Ası́ que vale la pena tratar de eliminar RTTI en este caso, no sólo
por razones estéticas — produce código más mantenible.

16.7 Haciendo abstracción de uso


Con la creación fuera del camino, es el momento de abordar el resto
del diseño: donde se utilizan las clases. Dado que es el acto de la
clasificación en los contenedores que es particularmente feo y ex-
puesto, por qué no tomar ese proceso y ocultarlo dentro de una
clase? 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 inicialización de objetos TrashSorter ahora debe ser cambi-


ado cada vez que un nuevo tipo de Trash se añade al modelo. Usted
podrı́a imaginar que la clase TrashSorter podrı́a ser algo como esto:

c l a s s TrashSorter ( ArrayList ) :

162
d e 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 ts = TrashSorter ()
t s . add ( A r r a y L i s t ( ) )
Ahora, sin embargo, sort( ) se convierte en un problema. ¿Cómo
el método estáticamentecodificado trata con el hecho de que un
nuevo tipo ha sido añadido? Para solucionar esto, la información de
tipo debe ser removido de sort( ) de manera que todo lo que que
necesita hacer es llamar a un método genérico que se ocupa de los
detalles del tipo. Esto, por supuesto, es otra manera de describir
un método dinámicamente enlazado. Ası́ sort( ) simplemente se
moverá a través de la secuencia y llamar a un método dinámicamente
enlazado para cada ArrayList. Dado que el trabajo de este método
es tomar las piezas de basura en que está interesado, este es llamado
grab(Trash). La estructura ahora queda como:

TrashSorter necesita llamar cada método grab() y obtener un


resultado diferente dependiendo de qué tipo de Trash ArrayList
actual está sosteniendo. Es decir, cada ArrayList debe ser con-
sciente del tipo que contiene. El enfoque clásico a este problema
es crear una clase ”Trash bin : Contenedor de basura” base y
heredar una nueva clase derivada para cada tipo diferente que quiera
mantener. Si Java tenı́a un mecanismo de tipo parametrizado ese

163
probablemente serı́a el enfoque más sencillo. Pero en lugar de la
codificación manual de todas las clases que tal mecanismo debe es-
tar construyendo para nosotros, mayor observación puede producir
un mejor enfoque.

Un principio básico de diseño en programación orientada a obje-


tos es: ”Usar los miembros de datos para la variación en el estado,
utilice el polimorfismo para la variación en el comportamiento.” Su
primer pensamiento podrı́a ser que el método grab( ) ciertamente
se comporta de manera diferente para un ArrayList que contiene
Paper que para uno que sostiene Glass. Pero lo que hace es estric-
tamente dependiente del tipo, y nada más. Esto podrı́a interpretarse
como un estado diferente, y dado que Java tiene una clase para rep-
resentar el tipo (Class) Esto se puede utilizar para determinar el
tipo de Trash que sostendrá a Tbin particular .

El constructor para este Tbin requiere que le entregue la Class


de su elección. Esto le dice al ArrayList qué tipo se supone que
debe mantener. Entonces el método grab( ) usa Class BinType
y RTTI para ver si el objeto Trash que ha entregado coincide con
el tipo que se supone que agarra.

Aquı́ esta una nueva versión del programa:

# c12 : r e c y c l e b : RecycleB . py
# C o n t a i n e r s t h a t grab o b j e c t s o f i n t e r e s t .

# A c o n t a i n e r t h a t admits o n l y th e r i g h t type
# o f Trash ( e s t a b l i s h e d i n th e c o n s t r u c t o r ) :
c l a s s Tbin :
private Collection l i s t = ArrayList ()
p r i v a t e C l a s s type
def i n i t ( s e l f , C l a s s binType ) : type = binType
d e f grab ( s e l f , Trash t ) :
# Comparing c l a s s t y p e s :
i f ( t . g e t C l a s s ( ) . e q u a l s ( type ) ) :
l i s t . add ( t )
r e t u r n 1 # Object grabbed

164
r e t u r n 0 # Object not grabbed

def i t e r a t o r ( s e l f ) :
return l i s t . i t e r a t o r ()

c l a s s TbinList ( ArrayList ) :
d e 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 f
w h i l e ( e . hasNext ( ) )
i f ( ( ( Tbin ) e . next ( ) ) . grab ( t ) ) r e t u r n
# Need a Tbin f o r t h i s type :
add ( Tbin ( t . g e t C l a s s ( ) ) )
sort ( t ) # Recursive c a l l

c l a s s RecycleB ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
TbinList trashBins = TbinList ()
def init ( self ):
ParseTrash . f i l l B i n ( ” . . / t r a s h / Trash . dat ” , bi n )

def test ( s e l f ) :
I t e r a t o r i t = bi n . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) )
t r a s h B i n s . s o r t ( ( Trash ) i t . next ( ) )
Iterator e = trashBins . i t e r a t o r ()
w h i l e ( e . hasNext ( ) ) :
Tbin b = ( Tbin ) e . next ( )
Trash . sumValue ( b . i t e r a t o r ( ) )

Trash . sumValue ( b in . i t e r a t o r ( ) )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleB ( ) . t e s t ( )

# :˜
Tbin contiene una referencia Class type que establece en el con-
structor qué tipo debe agarrar. El método grab() revisa este tipo
contra el objeto que se pasa. Tenga en cuenta que en este diseño,
grab() solo acepta objetos Trash de este modo usted consigue la

165
comprobación de tipos en tiempo de compilación del tipo base, pero
usted podrı́a también apenas aceptar Object y todavı́a funcionarı́a.

TbinList sostiene un conjunto de referencias Tbin, ası́ que sort(


) puede iterar a través de los Tbins cuando está buscando una
pareja para el objeto Trash lo habéis transmitido. Si este no en-
cuentra una pareja, crea un nuevo Tbin para el tipo que no ha sido
enconstrado, y hace una llamada recursiva a sı́ mismo – la próxima
vez, se encontrará el nuevo bin.
Note la generalidad de este código: no cambia en absoluto si
se añaden nuevos tipos. Si la mayor parte de su código no necesita
cambiar cuando se añade un nuevo tipo (o algún otro cambio ocurre)
entonces usted tiene un sistema fácilmente extensible.

166
16.8 Despacho múltiple
El diseño anterior es ciertamente satisfactorio. La adición de nuevos
tipos al sistema consiste en añadir o modificar clases distintas sin
causar cambios en el código que se propagan por todo el sistema.
En adición, RTTI no está ”mal utilizada” como lo estaba en Recy-
cleA.py. Sin embargo, es posible ir un paso más allá y tomar un
punto de vista purista sobre RTTI y decir que debe ser eliminada
por completo de la operación 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 detección del
tipo de un pedazo de basura y ponerla en el recipiente apropiado —
deben ser controladas a través del polimorfismo y enlace dinámico.

Los ejemplos anteriores primero ordenados por tipo, entonces ac-


tuaron en las secuencias de elementos que eran todos de un tipo
particular. Pero cada vez que usted se encuentra eligiendo tipos
particulares, deténgase y piense. Toda la idea de polimorfismo
(dinámicamente enlazado con llamadas a métodos) es encargarse
de la información de tipo especı́fico para usted. Ası́ que ¿por qué la
búsqueda de tipos?

La respuesta es algo que probablemente no piensa: Python sólo


realiza despacho individual. Es decir, si está realizando una op-
eración en más de un objeto cuyo tipo es desconocido, Python in-
vocará el mecanismo de enlace dinámico en sólo uno de esos tipos.
Esto no resuelve el problema, ası́ que usted termina la detección
de algunos tipos manualmente y produciendo eficazmente su propio
comportamiento de enlace dinámico.

La solución es llamada multiple dispatching : Despacho múltiple


lo cual significa la creación de una configuración tal que una única
llamada al método produce más de una llamada a un método dinámico
y por lo tanto determina más de un tipo en el proceso. Para con-
seguir este efecto, usted necesita trabajar con más de una jerarquı́a
de tipos: usted necesitará una jerarquı́a de tipos para cada envı́o.
El siguiente ejemplo trabaja con dos jerarquı́as: la familia Trash
existente y una jerarquı́a de los tipos de contenedores de basura en

167
que la basura será colocada. Esta segunda jerarquı́a no siempre es
evidente y en este caso esto necesitaba ser creado con el fin de pro-
ducir despacho múltiple (en este caso habrá sólo dos despachos, lo
cual hace referencia como double dispatching : despacho doble).

168
16.8.1 La implementación del doble despacho

Recuerde que el polimorfismo puede ocurrir sólo a través de lla-


madas a métodos, ası́ que si quiere que se produzca el despacho
doble, deben existir dos llamadas a métodos: uno utilizado para
determinar el tipo dentro de cada jerarquı́a. En la jerarquı́a Trash
habrá un nuevo método llamado addToBin(), que toma un argu-
mento de un array de TypedBin. Utiliza este array para recorrer
y tratar de agregarse a sı́ misma a a la papelera apropiada, y aquı́
es donde usted verá el doble despacho.

La nueva jerarquı́a es TypedBin, y que contiene su propio método


llamado add() que también es utilizado polimórficamente. Pero aquı́
está un giro adicional: add() está sobrecargado para tomar argu-
mentos de los diferentes tipos de trash : basura. Ası́ que una parte
esencial del esquema de doble despacho también implica una so-
brecarga. El rediseño del programa produce un dilema: ahora es
necesario para la clase base Trash contener un método addToBin(
). Un enfoque consiste en copiar todo el código y cambiar la clase
base. Otro enfoque, que puede tomar cuando usted no tiene el con-
trol del código fuente, es poner el método addToBin( ) dentro de
un interface, dejar Trash solo, y heredar nuevos tipos especı́ficos
de Aluminum, Paper, Glass, y Cardboard. Este es el enfoque

169
que se tomará aquı́.

La mayorı́a de las clases en este diseño debe ser public, por lo


que se colocan en sus propios archivos. Aquı́ esta la interfaz:

# c12 : d o u b l e d i s p a t c h : TypedBinMember . py
# An c l a s s f o r adding th e double
# d i s p a t c h i n g method t o t h e t r a s h h i e r a r c h y
# without m o d i f y i n g t he o r i g i n a l h i e r a r c h y .

c l a s s TypedBinMember :
# The method :
b o o l e a n addToBin ( TypedBin [ ] tb )
# :˜
En cada subtipo particular de Aluminum, Paper, Glass ,
and Cardboard, el método addToBin( ) en la interfaz interface
TypedBinMember es implementado, pero parece que el código es
exactamente el mismo en cada caso:

# c12 : d o u b l e d i s p a t c h : DDAluminum . py
# Aluminum f o r double d i s p a t c h i n g .

c l a s s DDAluminum( Aluminum )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0

# :˜
# c12 : d o u b l e d i s p a t c h : DDPaper . py
# Paper f o r double d i s p a t c h i n g .

c l a s s DDPaper ( Paper )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . init ( wt )

170
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0

# :˜
# c12 : d o u b l e d i s p a t c h : DDGlass . py
# G l a s s f o r double d i s p a t c h i n g .

c l a s s DDGlass ( G l a s s )
implements TypedBinMember :

def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0

# :˜
# c12 : d o u b l e d i s p a t c h : DDCardboard . py
# Cardboard f o r double d i s p a t c h i n g .

c l a s s DDCardboard ( Cardboard )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0

# :˜
El código en cada addToBin( ) llama add( ) para cada objeto
TypedBin en el array. Pero note el argumento: this. El tipo de
this es diferente para cada subclase de Trash, por lo que el código
es diferente. (Aunque este código se beneficiará si un mecanismo de
tipo parametrizado es alguna vez agregado a Java.) Ası́ que esta

171
es la primera parte del doble despacho, porque una vez que está
dentro de este método usted sabe que es Aluminum, o Paper,
etc. Durante la llamada a add( ), esta información se pasa a través
del tipo de this. El compilador resuelve la llamada a la versión
correcta sobrecargada de add( ). Pero puesto que tb[i] produce
una referencia al tipo base TypedBin, esta llamada va a terminar
llamando a un método diferente dependiendo del tipo de Typed-
Bin que está actualmente seleccionado. Ese es el segundo despacho.

Aquı́ esta la clase base para TypedBin:

# c12 : d o u b l e d i s p a t c h : TypedBin . py
# A c o n t a i n e r f o r t he second d i s p a t c h .

c l a s s TypedBin :
Collection c = ArrayList ()
d e f a d d I t ( s e l f , Trash t ) :
c . add ( t )
return 1

def i t e r a t o r ( s e l f ) :
return c . i t e r a t o r ()

d e f add ( s e l f , DDAluminum a ) :
return 0

d e f add ( s e l f , DDPaper a ) :
return 0

d e f add ( s e l f , DDGlass a ) :
return 0

d e f add ( s e l f , DDCardboard a ) :
return 0

# :˜
Puede ver que todos los métodos sobrecargados add( ) retornan
false. Si el método no está sobrecargado en una clase derivada,
continuará retornando false, y el llamador (addToBin( ), en este

172
caso) asumirá que el objeto actual Trash no se ha añadido con éxito
a un contenedor, y continuar buscando el contenedor correcto.

En cada una de las subclases de TypedBin, sólo un método so-


brecargado es anulado, de acuerdo con el tipo de bin que está siendo
creado. Por ejemplo, CardboardBin anula add(DDCardboard).
El método anulado agrega el objeto trash : basura a su contenedor y
retorna true, mientras todo el resto de los métodos 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 parametrizado
en Java permitirı́a la generación automática de código. (Con C++
templates, usted no tendrı́a que escribir explı́citamente las sub-
clases o colocar el método addToBin( ) en Trash.)

Puesto que para este ejemplo los tipos de basura se han person-
alizado y colocado en un directorio diferente, usted necesitará un
archivo de datos de basura diferente para hacer que funcione. Aquı́
está un posible DDTrash.dat:

# c12 : d o u b l e d i s p a t c h : DDTrash . dat


DDGlass : 5 4
DDPaper : 2 2
DDPaper : 1 1
DDGlass : 1 7
DDAluminum : 8 9
DDPaper : 8 8
DDAluminum : 7 6
DDCardboard : 9 6
DDAluminum : 2 5
DDAluminum : 3 4
DDGlass : 1 1
DDGlass : 6 8
DDGlass : 4 3
DDAluminum : 2 7
DDCardboard : 4 4
DDAluminum : 1 8
DDPaper : 9 1
DDGlass : 6 3
DDGlass : 5 0

173
DDGlass : 8 0
DDAluminum : 8 1
DDCardboard : 1 2
DDGlass : 1 2
DDGlass : 5 4
DDAluminum : 3 6
DDAluminum : 9 3
DDGlass : 9 3
DDPaper : 8 0
DDGlass : 3 6
DDGlass : 1 2
DDGlass : 6 0
DDPaper : 6 6
DDAluminum : 3 6
DDCardboard : 2 2
# :˜
Aquı́ esta el resto del programa:

# c12 : d o u b l e d i s p a t c h : DoubleDispatch . py
# Using m u l t i p l e d i s p a t c h i n g t o handle more
# than one unknown type d u r i n g a method c a l l .

c l a s s AluminumBin ( TypedBin ) :
d e f add ( s e l f , DDAluminum a ) :
return addIt ( a )

c l a s s PaperBin ( TypedBin ) :
d e f add ( s e l f , DDPaper a ) :
return addIt ( a )

c l a s s GlassBin ( TypedBin ) :
d e f add ( s e l f , DDGlass a ) :
return addIt ( a )

c l a s s CardboardBin ( TypedBin ) :
d e f add ( s e l f , DDCardboard a ) :
return addIt ( a )

c l a s s TrashBinSet :

174
p r i v a t e TypedBin [ ] b i n S e t =:
AluminumBin ( ) ,
PaperBin ( ) ,
GlassBin ( ) ,
CardboardBin ( )

def s o r t I n t o B i n 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 = b in . i t e r a t o r ( )
w h i l e ( e . hasNext ( ) ) :
TypedBinMember t =
( TypedBinMember ) e . next ( )
i f ( ! t . addToBin ( b i n S e t ) )
System . e r r . p r i n t l n ( ” Couldn ’ t add ” + t )

p u b l i c TypedBin [ ] b i n S e t ( ) : r e t u r n b i n S e t

c l a s s DoubleDispatch ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
TrashBinSet b i n s = TrashBinSet ( )
def init ( self ):
# ParseTrash s t i l l works , without changes :
ParseTrash . f i l l B i n ( ” DDTrash . dat ” , bi n )

def test ( s e l f ) :
# S o r t from t he master b in i n t o
# t he i n d i v i d u a l l y −typed b i n s :
b i n s . s o r t I n t o B i n s ( b in )
TypedBin [ ] tb = b i n s . b i n S e t ( )
# Perform sumValue f o r each b i n . . .
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
Trash . sumValue ( tb [ i ] . c . i t e r a t o r ( ) )
# . . . and f o r th e master bi n
Trash . sumValue ( b in . i t e r a t o r ( ) )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
DoubleDispatch ( ) . t e s t ( )

# :˜
TrashBinSet encapsula todos los diferentes tipos de Typed-

175
Bins, junto con el método sortIntoBins( ), que es donde todo el
doble despacho toma lugar. Usted puede ver que una vez que la
estructura está configurada, la clasificación en los distintos Type-
dBins es muy fácil. En adición, la eficiencia de dos llamadas al
método dinámico es probablemente mejor que cualquier otra forma
usted podrı́a ordenar.

Note la facilidad de uso de este sistema en main( ), ası́ como la


completa independencia de cualquier información de tipo especı́fico
dentro de main( ). Todos los otros métodos que hablan sólo a la
interfaz de la clase base Trash serán igualmente invulnerable a los
cambios en los tipos Trash.

Los cambios necesarios para agregar un nuevo tipo son relati-


vamente aislados: modifica TypedBin, heredar el nuevo tipo de
Trash con su método addToBin( ), luego heredar un nuevo Type-
dBin (esto es realmente sólo una copia y sencilla edición), y por
último añadir un nuevo tipo en la inicialización agregada de Trash-
BinSet.

16.9 El patrón Visitor : visitante


Ahora considerar la aplicación de un patrón de diseño que tiene un
objetivo completamente diferente al problema de clasificación de ba-
sura.

Para este patrón, ya no estamos preocupados con la optimización


de la adición de nuevos tipos de Trash para el sistema. Cierta-
mente, este patrón hace que la adición de un nuevo tipo de Trash
más complicado. El supuesto es que usted tiene una jerarquı́a de
clases primaria que es fija; quizás es de otro proveedor y no puedes
realizar cambios en esa jerarquı́a. Sin embargo, usted tenı́a como
añadir nuevos métodos polimórficos a esa jerarquı́a, lo cual significa
que normalmente tenı́a que añadir algo a la interfaz de la clase base.
Ası́ el dilema es que usted necesita añadir métodos a la clase base,
pero no puede tocar la clase base. ¿Cómo se obtiene alrededor de
esto?

176
El patrón de diseño que resuelve este tipo de problema es lla-
mado un “visitor : visitante” (la final en el libro Design Patterns
: Patrones de Diseño), y se basa en el esquema de despacho doble
mostrado en la última sección.

El patrón visitor : visitante le permite extender la interfaz del


tipo primario mediante la creación de una jerarquı́a de clases por
separado de tipo Visitor para virtualizar las operaciones realizadas
en el tipo primario. Los objetos del tipo primario simplemente
”aceptan” el visitante, a continuación, llaman el visitante del método
dinámicamente enlazado. Se ve ası́:

177
Ahora, si v es una referencia Visitable para un objeto Alu-
minum, el código:

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 . a c c e p t ( pv )
utiliza despacho doble para causar dos llamadas a métodos polimórficos:
el primero para seleccionar la versión de Aluminum de accept( ),
y el segundo dentro de accept( ) cuando la versión especifica de

178
visit( ) es llamada de forma dinamica usando la clase base Visitor
referencia v.

Esta configuración significa que la nueva funcionalidad puede ser


añadido al sistema en forma de nuevas subclases de Visitor. La
jerarquı́a Trash no necesita ser tocada. Este es el principal benefi-
cio del patrón visitante: usted puede agregar nueva funcionalidad
polimórfica a una jerarquı́a de clases sin tocar esa jerarquı́a (una vez
que los métodos accept( ) se han instalado). Tenga en cuenta que
el beneficio es útil aquı́, pero no es exactamente lo que empezamos
a lograr, ası́ que a primera vista podrı́a decidir que esta no es la
solución deseada.

Pero mire una cosa que se ha logrado: la solución visitante Evita


la clasificación de la secuencia Trash maestro en secuencias escritas
individuales. Ası́, usted puede dejar todo en la única secuencia
maestra y simplemente pasar a través de esa secuencia utilizando el
visitante apropiado para lograr el objetivo. Aunque este compor-
tamiento parece ser un efecto secundario del visitante, Esto nos da
lo que queremos (evitando RTTI).

El despacho doble en el patrón visitante se ocupa de determinar


tanto el tipo de Trash y el tipo de Visitor. En el siguiente ejem-
plo, hay dos implementaciones de Visitor: PriceVisitor tanto
para determinar y resumir el precio, y WeightVisitor hacer un
seguimiento de los pesos.

Usted puede ver todo esto implementado en la nueva y mejorada


versión del programa de reciclaje.

Al igual que con DoubleDispatch.py, la clase Trash se deja


solo y una nueva interfaz es creada para agregar el método 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 t o add v i s i t o r f u n c t i o n a l i t y
# t o t he Trash h i e r a r c h y without
# m o d i f y i n g th e base c l a s s .

179
class Visitable :
# The method :
def accept ( s e l f , V i si to r v)
# :˜
Dado que no hay nada concreto en la clase base Visitor, se puede
crear 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 .

class Visitor :
def visit ( self , Aluminum a )
def visit ( self , Paper p )
def visit ( self , Glass g )
def visit ( self , Cardboard c )
# :˜

16.10 Un decorador reflexivo


En este punto, usted podrı́a seguir el mismo criterio que se utilizó
para el despacho doble y crear nuevos subtipos de Aluminum, Pa-
per, Glass, y Cardboard que implementan el método 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 t he p r e v i o u s 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 t he v i s i t o r p a t t e r n .

c l a s s VAluminum ( Aluminum )
implements V i s i t a b l e :
def i n i t ( s e l f , double wt ) : . init ( wt )
def accept ( s e l f , V i si to r v ) :
v. visit ( self )

# :˜
Sin embargo, Parece que estamos encontrando una ”explosión
de interfaces:” Trash básico, versiones especiales para el despa-
cho doble, y ahora las versiones más especiales para los visitantes.

180
Claro, esta ”explosión de interfaces” es arbitraria — uno podrı́a
simplemente poner los métodos adicionales de la clase Trash. Si
ignoramos que en lugar podemos ver la oportunidad de utilizar el
patrón Decorador : Parece como que deberı́a ser posible crear un
Decorador que puede ser envuelto alrededor de un objeto ordinario
Trash y producirá la misma interfaz que Trash y agrega el método
extra accept( ). De hecho, es un ejemplo perfecto del valor de Dec-
orador.

El doble despacho crea un problema, no obstante. Como se basa


en la sobrecarga de ambos accept( ) y visit( ), esto parecerı́a re-
querir código especializado para cada versión diferente del método
accept( ). Con las plantillas de C ++, esto serı́a bastante fácil de
lograr (ya que las plantillas generan automáticamente código de tipo
especializado) pero Python no tiene tal mecanismo — al menos no
parece. Sin embargo, reflexión le permite determinar la información
de tipo en tiempo de ejecución, y llegar a resolver muchos proble-
mas que parecerı́an requerir plantillas (aunque no tan simplemente).
Aquı́ está el decorador que hace el truco20 :
newpage
# c12 : t r a s h v i s i t o r : V i s i t a b l e D e c o r a t o r . py
# A d e c o r a t o r t h a t adapts th e 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 .
class VisitableDecorator
e x t e n d s Trash implements V i s i t a b l e :
p r i v a t e Trash d e l e g a t e
p r i v a t e Method d i s p a t c h
def i n i t ( s e l f , Trash t ) :
delegate = t
try :
d i s p a t c h = V i s i t o r . c l a s s . getMethod (
” v i s i t ” , Class [ ] : t . getClass ()
)
c a t c h ( E xc ept ion ex ) :
ex . p r i n t S t a c k T r a c e ( )
def getValue ( s e l f ) :
return d e l e g a t e . getValue ( )
20 Estafue una solución creada por Jaroslav Tulach en una clase de diseño de patrones que
di en Praga

181
d e f getWeight ( s e l f ) :
r e t u r n d e l e g a t e . getWeight ( )
def accept ( s e l f , V i si to r v ) :
try :
d i s p a t c h . i n v o k e ( v , Object [ ] { d e l e g a t e )
c a t c h ( E xc ept ion ex ) :
ex . p r i n t S t a c k T r a c e ( )
# :˜
[[Descripción del uso de Reflexión]]

La única otra herramienta que necesitamos es un nuevo tipo de


adaptador Fillable que automáticamente decora los objetos a me-
dida que se crean a partir del archivo original Trash.dat. Pero esto
bien podrı́a ser un decorador de sı́ mismo, la decoración de cualquier
tipo 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 D e c o r a t o r t h a t adds t he v i s i t a b l e
# d e c o r a t o r as t he Trash o b j e c t s a r e
# being created .

class FillableVisitor
implements F i l l a b l e :
private Fillable f
def init ( self , Fillable ff ): f = ff
d e f addTrash ( s e l f , Trash t ) :
f . addTrash ( V i s i t a b l e D e c o r a t o r ( t ) )
# :˜
Ahora usted puede envolver alrededor de cualquier tipo de Fillable
existente, o cualquier otros nuevos que aún no se han creado.

El resto del programa crea tipos Visitor especı́ficos y los envı́a


a través de una lista única de objetos Trash:

# c12 : t r a s h v i s i t o r : T r a s h V i s i t o r . py
# The ” v i s i t o r ” p a t t e r n 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 l g o r i t h m s packaged

182
# i n each i m p l e m e n t a t i o n o f V i s i t o r :
class PriceVisitor ( Visitor ):
p r i v a t e double alSum # Aluminum
p r i v a t e double pSum # Paper
p r i v a t e double gSum # G l a s s
p r i v a t e double cSum # Cardboard
d e f v i s i t ( s e l f , Aluminum a l ) :
double v = a l . getWeight ( ) ∗ a l . g e t V a l u e ( )
p r i n t ” v a l u e o f Aluminum= ” + v
alSum += v

d e f v i s i t ( s e l f , Paper p ) :
double v = p . getWeight ( ) ∗ p . g e t V a l u e ( )
p r i n t ” v a l u e o f Paper= ” + v
pSum += v

def v i s i t ( s e l f , Glass g ) :
double v = g . getWeight ( ) ∗ g . g e t V a l u e ( )
p r i n t ” v a l u e o f G l a s s= ” + v
gSum += v

d e f v i s i t ( s e l f , Cardboard c ) :
double v = c . getWeight ( ) ∗ c . g e t V a l u e ( )
p r i n t ” v a l u e o f Cardboard = ” + v
cSum += v

def total ( s e l f ) :
print (
” Total Aluminum : $” + alSum +
”\n Total Paper : $” + pSum +
”\ nTotal G l a s s : $” + gSum +
”\ nTotal Cardboard : $” + cSum +
”\ nTotal : $” +
( alSum + pSum + gSum + cSum ) )

c l a s s WeightVisitor ( V i s i t o r ) :
p r i v a t e double alSum # Aluminum
p r i v a t e double pSum # Paper
p r i v a t e double gSum # G l a s s

183
p r i v a t e double cSum # Cardboard

d e 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 ( ) )

d e 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 ( ) )

def v i s i t ( s e l f , Glass g ) :
gSum += g . getWeight ( )
p r i n t ( ” weight o f G l a s s = ”
+ g . getWeight ( ) )

d e 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 ( ) )

def total ( s e l f ) :
print (
” Total weight Aluminum : ” + alSum +
”\ nTotal weight Paper : ” + pSum +
”\ nTotal weight G l a s s : ” + gSum +
”\ nTotal weight Cardboard : ” + cSum +
”\ nTotal weight : ” +
( alSum + pSum + gSum + cSum ) )

c l a s s T r a s h V i s i t o r ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
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 ( )
W e i g h t V i s i t o r wv = W e i g h t V i s i t o r ( )
def init ( self ):
ParseTrash . f i l l B i n ( ” . . / t r a s h / Trash . dat ” ,
FillableVisitor (
F i l l a b l e C o l l e c t i o n ( bi n ) ) )

184
def test ( s e l f ) :
I t e r a t o r i t = bi n . i t e r a t o r ( )
w h i 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 . a c c e p t ( pv )
v . a c c e p t (wv)

pv . t o t a l ( )
wv . t o t a l ( )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
TrashVisitor ( ) . test ()

# :˜
En Test( ), observe cómo se añade la visitabilidad simplemente
creando un tipo diferente de bin usando el decorador. Observe
también que el adaptador FillableCollection tiene la apariencia
de ser utilizado como decorador (para ArrayList) en esta situación.
Ahora bien, cambia completamente la interfaz del ArrayList, visto
que la definición de Decorador es que la interfaz de la clase decorada
aún debe estar allı́ después de la decoración.

Tenga en cuenta que la forma del código del cliente (que se mues-
tra en la clase Test) ha cambiado de nuevo, a partir de los enfoques
originales al problema. Ahora sólo hay un solo bin Trash. Los dos
objetos Visitor son aceptados en cada elemento de la secuencia, y
realizan sus operaciones. Los visitantes mantienen sus propios datos
internos para concordar los pesos y precios totales.

Finalmente, no hay identificación de tipo en tiempo de ejecución


que no sea el molde inevitable a Trash al tirar cosas fuera de la se-
cuencia. Esto, también, podrı́a ser eliminado con la implementación
de tipos parametrizados en Java.

Una manera en que usted puede distinguir esta solución de la


solución de despacho doble descrita anteriormente es tener en cuenta
que, en la solución del doble despacho, solamente uno de los métodos
sobrecargados, add( ), fue anulado cuando se creó cada subclase,

185
mientras que aquı́ cada uno de los métodos visit( ) sobrecargados
es anulado en cada subclase de Visitor.

16.10.1 Más acoplamiento?

Hay mucho más código aquı́, y hay acoplamiento definitivo entre la


jerarquı́a Trash y la jerarquı́a Visitor. Ahora bien, también hay
alta cohesión dentro de los respectivos conjuntos de clases: cada
uno de ellos hacen una sola cosa (Trash describe Basura, mientras
que Visitor describe las acciones realizadas en Trash), que es un
indicador de un buen diseño. Claro, en este caso funciona bien sólo
si está agregando nuevos Visitors, pero se pone en el camino al
agregar nuevos tipos de Trash.

Bajo acoplamiento entre clases y alta cohesión dentro de una


clase es sin duda un objetivo de diseño importante. Aplicado sin
pensar, sin embargo, puede impedirle el logro de un diseño más ele-
gante.Parece que algunas clases, inevitablemente, tienen una cierta
intimidad con cada uno. Estos a menudo ocurren en parejas que
quizás podrı́an ser llamados couplets : coplas; por ejemplo, los con-
tenedores y los iteradores. El par anterior Trash-Visitor parece
ser otro como couplet.

16.11 RTTI considerado dañino?


Varios diseños en este capı́tulo intentan eliminar RTTI, lo cual
podrı́a darle la impresión de que se ”considera perjudicial” (la con-
denación utilizado para pobres, malogrado goto, que por lo tanto
nunca fue puesto en Java). Esto no es verdad; es el mal uso de
RTTI, ese es el problema. La razón por la que nuestros diseños
eliminan RTTI se debe a la mala aplicación de esa caracterı́stica que
impide extensibilidad, mientras que el objetivo declarado era capaz
de añadir un nuevo tipo al sistema con el menor impacto en circun-
dante código como sea posible. Dado que RTTI es a menudo mal
usado por tener que buscar todo tipo único en su sistema, provoca
código que no sea extensible: cuando se agrega un nuevo tipo, usted
tiene que ir a buscar por todo el código en el que se usa RTTI, y si

186
se olvida de alguno usted no conseguirá la ayuda del compilador.

Sin embargo, RTTI no crea automáticamente el código no exten-


sible. Vamos a revisar el reciclador de basura una vez más. Esta
vez, una nueva herramienta será introducida, la cual yo llamo un
TypeMap. 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 de
un tipo particular. Las claves para el contenido HashMap son los
tipos en el ArrayList asociado. La belleza de este diseño (sugerido
por Larry O’Brien) es que el TypeMap agrega dinámicamente un
nuevo par cada vez que encuentra un nuevo tipo, por lo que cada
vez que añade un nuevo tipo al sistema (incluso si se agrega el nuevo
tipo en tiempo de ejecución), se adapta.

Nuestro ejemplo nuevamente se basará en la estructura de los


tipos Trash en package c12.Trash (y el archivo Trash.dat uti-
lizado se pueden utilizar aquı́ sin modificar):

# c12 : d y n a t r a s h : DynaTrash . py
# Using a Map o f L i s t s and RTTI
# to automatically s o r t trash i n t o
# A r r a y L i s t s . This s o l u t i o n , d e s p i t e t he
# use o f RTTI , i s e x t e n s i b l e .

# G e n e r i c TypeMap works i n 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 ( )
d e f add ( s e l f , Object o ) :
C l a s s type = o . g e t C l a s s ( )
i f ( t . h a s k e y ( type ) )
( ( L i s t ) t . g e t ( type ) ) . add ( o )
else :
List v = ArrayList ()
v . add ( o )
t . put ( type , v )

d e f g e t ( s e l f , C l a s s type ) :
r e t u r n ( L i s t ) t . g e t ( type )

187
d e f keys ( s e l f ) :
r e t u r n t . keySet ( ) . i t e r a t o r ( )

# Adapter c l a s s t o a l l o w 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 map
def i n i t ( s e l f , TypeMap tm ) : map = tm
d e f addTrash ( s e l f , Trash t ) : map . add ( t )

c l a s s DynaTrash ( UnitTest ) :
TypeMap b i n = TypeMap ( )

def init ( self ):


ParseTrash . f i l l B i n ( ” . . / t r a s h / Trash . dat ” ,
TypeMapAdapter ( b in ) )

def test ( s e l f ) :
I t e r a t o r keys = bi n . keys ( )
w h i l e ( keys . hasNext ( ) )
Trash . sumValue (
b in . g e t ( ( C l a s s ) keys . next ( ) ) . i t e r a t o r ( ) )

d e f main ( s e l f , S t r i n g a r g s [ ] ) :
DynaTrash ( ) . t e s t ( )
# :˜
Aunque potente, la definición para TypeMap es simple. Con-
tiene un HashMap, y el método add( ) hace la mayorı́a del trabajo.
Cuando usted add( ) un nuevo objeto, se extrae la referencia para
el objeto Class para ese tipo. Esto se utiliza como una clave para
determinar si un ArrayList que sostiene objetos de ese tipo ya está
presente en el HashMap. Si es ası́, ese ArrayList se extrae y el
objeto se añade al ArrayList. Si no, el objeto Class y un nuevo
ArrayList se añaden como un par clave-valor.

Usted puede obtener un Iterator de todos los objetos Class de


keys( ), y usar cada objeto Class para buscar el correspondiente
ArrayList con get( ). Y eso es todo lo que hay que hacer.

188
El método filler( ) es interesante porque Se aprovecha del diseño
de ParseTrash.fillBin( ), que no sólo tratar de llenar un Ar-
rayList sino cualquier cosa que implementa la interfaz Fillable con
su método addTrash( ). Todo filler( ) necesita hacer es devolver
una referencia a una interface que implementa Fillable, y luego
esta referencia puede ser utilizado como un argumento a fillBin( )
como esto:

ParseTrash . f i l l B i n ( ” Trash . dat ” , b in . f i l l e r ( ) )


Para producir esta referencia, una clase interna anónima (de-
scrito en el capı́tulo 8 de Thinking in Java, segunda edición) es
utilizada. Usted nunca necesita una clase llamada para implemen-
tar Fillable, sólo necesita una referencia a un objeto de esa clase,
por lo que este es un uso apropiado de las clases internas anónimas.

Una cosa interesante sobre este diseño es que a pesar de que no


fue creado para manejar la clasificación, fillBin( ) está realizando
tipo 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 los
nuevos objetos Trash en un bin de tipo ArrayList, bin es de tipo
TypeMap, ası́ que cuando la basura es arrojada en bin se ordena
de inmediato por el TypeMap del mecanismo de clasificación in-
terna. Dando un paso a través de TypeMap y operando en cada
ArrayList individual se convierte en un asunto sencillo.

Como puede ver, la adición de un nuevo tipo al sistema no afec-


tará este código en absoluto, y el código en TypeMap es completa-
mente independiente. Esta es ciertamente la solución más pequeña
del problema, y podrı́a decirse que el más elegante también. No
depende mucho de de RTTI, pero observe que cada par clave-valor
en el HashMap está en busca de un solo tipo. En adición, no
hay manera que usted puede ”olvidar” añadir el código adecuado a
este sistema cuando se agrega un nuevo tipo, ya que no hay ningún
código que necesite agregar.

189
16.12 Resumen
Surgir con un diseño como TrashVisitor.py que contiene una mayor
cantidad de código que los diseños anteriores puede parecer en un
principio ser contraproducente. Vale la pena notar lo que estás
tratando de lograr con varios diseños. Los patrones de diseño en
general se esfuerzan por separar las cosas que cambian de las cosas
que permanecen igual. Las ”cosas que cambian” puede referirse a
muchos tipos diferentes de cambios. Quizás el cambio ocurre porque
el programa se coloca en un nuevo entorno o porque algo en el en-
torno actual cambia: (esto podrı́a ser: ”El usuario quiere añadir
una nueva forma para el diagrama actualmente en la pantalla”). O,
como en este caso, el cambio podrı́a ser la evolución del cuerpo del
código. Mientras que las versiones anteriores del ejemplo de clasi-
ficación de basura enfatizaron la adición de nuevos tipos de Trash
al sistema, TrashVisitor.py le permite añadir fácilmente nuevas
funcionalidades sin molestar a la jerarquı́a Trash. Hay más código
en TrashVisitor.py, pero la adición de nueva funcionalidad para
Visitor es de mal gusto. Si esto es algo que sucede mucho, entonces
vale la pena el esfuerzo extra y el código para hacer que suceda con
más facilidad.

El descubrimiento del vector de cambio no es un asunto trivial;


esto no es algo que un analista usualmente puede detectar antes de
que el programa considera este su diseño inicial. La información
necesaria probablemente no aparecerá hasta las últimas fases del
proyecto: a veces sólo en las fases de diseño o de implementación
se descubre una necesidad más profunda o más sutil en su sistema.
En el caso de la adición de nuevos tipos (el cual fue el foco de la
mayorı́a de los ejemplos ”reciclar”) usted puede darse cuenta de que
necesita una jerarquı́a de herencia particular sólo cuando se está en
la fase de mantenimiento y de comenzar la ampliación del sistema!

Una de las cosas más importantes que aprenderá mediante el es-


tudio de los patrones de diseño parece ser un cambio de actitud de
lo que se ha promovido hasta ahora en este libro. Es decir: ”Pro-
gramación Orientada a Objetos es todo acerca de polimorfismo.”
Esta declaración puede producir el sindrome ”dos años de edad, con
un martillo” (todo se ve como un clavo). Dicho de otra manera, es
bastante dı́ficil ”obtener” polimorfismo, y una vez que lo hace, trate

190
de emitir todos sus diseños en un molde particular.

¿Qué patrones de diseño dicen que la Programación Orientada a


Objetos no se trata sólo de polimorfismo?. Esto se trata de ”la sep-
aración de cosas que cambian de los objetos que permanecen igual.”
Polimorfismo es una manera especialmente importante para hacer
esto, y resulta ser útil si el lenguaje de programación apoya direc-
tamente el polimorfismo (por lo que no tiene que conectarlo usted
mismo, lo que tenderı́a a hacer que sea prohibitivamente caro). Pero
los patrones de diseño en general muestran otras maneras de lograr
el objetivo básico, y una vez que sus ojos se han abierto a esto usted
comenzará a buscar más diseños creativos.

Desde que el libro Design Patterns salió e hizó tal impacto, la


gente ha estado buscando otros patrones. Usted puede esperar ver
más 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 movimiento
de los patrones:

http://st-www.cs.uiuc.edu/users/patterns
http://c2.com/cgi/wiki
http://c2.com/ppr
http://www.bell-labs.com/people/co
pe/Patterns/Process/index.html
http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns
http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic
http://www.cs.wustl.edu/ schmidt/patterns.html
http://www.espinc.com/patterns/overview.html

También tenga en cuenta que ha habido una conferencia anual


sobre los patrones de diseño, llamada PLOP, que produce unas actas
publicadas, la tercera de las cuales salieron a finales de 1997 (todas
publicadas por Addison-Wesley).

191
16.13 Ejercicios
1. Añade la clase Plastic a TrashVisitor.py

2. Añade la clase Plastic a DynaTrash.py

3. Crear un decorador como VisitableDecorator, pero para el


ejemplo de despacho múltiple, junto con una clase ”decorador adap-
tador ” como la creada para VisitableDecorator. Construir el
resto del ejemplo y demostrar que funciona.

17 Proyectos
Este capı́tulo no ha tenido traducción significativa todavı́a.

Una serie de proyectos más desafiantes para que usted pueda


resolver.
[[Algunos de estos pueden convertirse en ejemplos en el libro, por lo
que en algún momento podrı́a desaparecer de aquı́]]

17.1 Ratas y Laberintos


Primero, crea un Blackboard
(citar esta referencia) que es un objeto sobre el que cualquier persona
puede registrar la información. Este Blackboard particular dibuja
un laberinto, y es usado como información que vuelve sobre la es-
tructura de un laberinto desde las ratas que lo están buscando.

Ahora cree el propio laberinto. Como un laberinto real, este ob-


jeto revela muy poca información sobre si mismo dada una coorde-
nada, que le dirá si hay paredes o espacios en las cuatro direcciones
inmediatamente que coordinan, pero no más. Para empezar, lea el
laberinto desde un archivo de texto pero considere la busqueda en
internet para un algoritmo que genere un laberinto. En cualquier
caso, el resultado debe ser un objeto que, dado una coordenada del
laberinto, informará paredes y espacios alrededor de esa coordenada.
Además, debe ser capaz de preguntar por un punto de entrada al

192
laberinto.

Finalmente, crear la clase Rat laberinto-buscar. Cada rata puede


comunicarse tanto con el Blackboard para dar la información actual
y el laberinto para solicitar neva información sobre la base de la
posición actual de la rata. Sin embargo, cada vez que una rata llega
a un punto de decisión donde se ramifica el laberinto, crea una nueva
rata que baja por cada una de las ramas. Cada rata es conducida
por su propio hilo. Cuando una rata llega a un callejón sin salida,
termina en sı́ después de informar los resultados de su búsqueda fi-
nal al Blackboard.

El objetivo es trazar un mapa completo del laberinto, pero también


usted debe determinar si la condición final será encontrada natural-
mente o si el blackboard debe ser responsable de la decisión.

Un ejemplo de implementación 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 a r r a y
p r i v a t e i n t width = −1
p r i v a t e i n t h e i g h t = −1
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s )
throws IOException :
i f ( args . length < 1):
p r i n t ‘ ‘ Enter f i l e n a m e ”
System . e x i t ( 0 )

Maze m = Maze ( )
m. l o a d ( a r g s [ 0 ] )
Frame f = Frame ( )
f . s e t S i z e (m. width ∗20 , m. h e i g h t ∗20)
f . add (m)
Rat r = Rat (m, 0 , 0 )
f . setVisible (1)

def init ( self ):

193
l i n e s = Vector ( )
setBackground ( Color . l i g h t G r a y )

synchronized public boolean


isEmptyXY ( i n t x , i n t y ) :
i f ( x < 0 ) x += width
i f ( y < 0 ) y += h e i g h t
# Use mod a r i t h m e t i c t o b r i n g r a t i n l i n e :
byte [ ] by =
( byte [ ] ) ( l i n e s . elementAt ( y%h e i g h t ) )
r e t u r n by [ x%width ]==’ ’

synchronized public void


setXY ( i n t x , i n t y , byte newByte ) :
i f ( x < 0 ) x += width
i f ( y < 0 ) y += h e i g h t
byte [ ] by =
( byte [ ] ) ( l i n e s . elementAt ( y%h e i g h t ) )
by [ x%width ] = newByte
repaint ()

public void
l o a d ( S t r i n g f i l e n a m e ) throws IOException :
String currentLine = null
B u f f e r e d R e a d e r br = B u f f e r e d R e a d e r (
FileReader ( filename ))
f o r ( c u r r e n t L i n e = br . r e a d L i n e ( )
c u r r e n t L i n e != n u l l
c u r r e n t L i n e = br . r e a d L i n e ( ) ) :
l i n e s . addElement ( c u r r e n t L i n e . g e t B y t e s ( ) )
i f ( width < 0 | |
c u r r e n t L i n e . g e t B y t e s ( ) . l e n g t h > width )
width = c u r r e n t L i n e . g e t B y t e s ( ) . l e n g t h

height = len ( l i n e s )
br . c l o s e ( )

d e f update ( s e l f , Graphics g ) : p a i n t ( g )
p u b l i c v o i d p a i n t ( Graphics g ) :

194
i n t c a n v a s H e i g h t = s e l f . getBounds ( ) . h e i g h t
i n t canvasWidth = s e l f . getBounds ( ) . width
i f ( h e i g h t < 1 | | width < 1 )
r e t u r n # n o t h i n g t o do
i n t width =
( ( byte [ ] ) ( l i n e s . elementAt ( 0 ) ) ) . l e n g t h
f o r ( i n t y = 0 y < l e n ( l i n e s ) y++):
byte [ ] b
b = ( byte [ ] ) ( l i n e s . elementAt ( y ) )
f o r ( i n t x = 0 x < width x++):
switch (b [ x ] ) :
c a s e ’ ’ : # empty p a r t o f maze
g . s e t C o l o r ( Color . l i g h t G r a y )
g. fillRect (
x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
canvasWidth / width ,
canvasHeight / height )
break
case ’∗ ’: # a wall
g . s e t C o l o r ( Color . darkGray )
g. fillRect (
x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
( canvasWidth / width ) −1 ,
( c a n v a s H e i g h t / h e i g h t ) −1)
break
default : # must be r a t
g . s e t C o l o r ( Color . r ed )
g . f i l l O v a l ( x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
canvasWidth / width ,
canvasHeight / height )
break

# :˜
# c13 : Rat . py

c l a s s Rat :

195
s t a t i c i n t ratCount = 0
p r i v a t e Maze p r i s o n
private int vertDir = 0
private int horizDir = 0
private int x , y
p r i v a t e i n t myRatNo = 0
def i n i t ( s e l f , Maze maze , i n t x S t a r t , i n t
yStart ) :
myRatNo = ratCount++
p r i n t ( ” Rat no . ” + myRatNo +
” ready t o s c u r r y . ” )
p r i s o n = maze
x = xStart
y = yStart
p r i s o n . setXY ( x , y , ( byte ) ’R’ )
Thread ( ) :
d e f run ( s e l f ){ s c u r r y ( )
. start ()

def scurry ( 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 .
# H o r i z o n t a l backward
b o o l e a n ratCanMove = 1
w h i l e ( ratCanMove ) :
ratCanMove = 0

# South
i f ( p r i s o n . isEmptyXY ( x , y + 1 ) ) :
vertDir = 1 horizDir = 0
ratCanMove = 1

# North
i f ( p r i s o n . isEmptyXY ( x , y − 1 ) )
i f ( ratCanMove )
Rat ( p r i s o n , x , y−1)
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
v e r t D i r = −1 h o r i z D i r = 0

196
ratCanMove = 1

# West
i f ( p r i s o n . isEmptyXY ( x−1, y ) )
i f ( ratCanMove )
Rat ( p r i s o n , x−1, y )
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
v e r t D i r = 0 h o r i z D i r = −1
ratCanMove = 1

# East
i f ( p r i s o n . isEmptyXY ( x+1, y ) )
i f ( ratCanMove )
Rat ( p r i s o n , x+1, y )
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
vertDir = 0 horizDir = 1
ratCanMove = 1

i f ( ratCanMove ) : # Move o r i g i n a l r a t .
x += h o r i z D i r
y += v e r t D i r
p r i s o n . setXY ( x , y , ( byte ) ’R’ )
# I f not then t he r a t w i l l d i e .
try :
Thread . s l e e p ( 2 0 0 0 )
catch ( InterruptedException i e ) :

p r i n t ( ” Rat no . ” + myRatNo +
” can ’ t move . . dying . . a a r r g g g h . ” )

# :˜
El archivo de inicialización de laberinto:

197
17.1.1 Otros Recursos para Laberinto

Una discusión de algoritmos para crear laberintos ası́ como el código


fuente de Java para implementarlas :

http://www.mazeworks.com/mazegen/mazegen.htm

Una discusión de algoritmos para la detección de colisiones y


otros comportamientos de movimiento individual/grupal de los ob-
jetos fı́sicos autónomos:

http://www.red3d.com/cwr/steer/

17.2 Decorador XML


Crear un par de decoradores para I/O Los lectores y escritores que
codifican (para el decorador Escritor) y decodificación XML.

198

También podría gustarte