Anda di halaman 1dari 198

Pensando

en
Python
Bruce Eckel
Presidente MindView,Inc.
September 23, 2016
Patrones de Dise
no y resoluci
on de problemas t
ecnicos
Por favor observe que este documento esta en su forma inicial, y
a
un queda mucho por hacer.

Contents
1 Pr
ologo

2 Introducci
on
2.1 El sndrome Y2K . . . . . . . . . . . . . . . . . . . . . . . . . . .
2.2 Contexto y composicion . . . . . . . . . . . . . . . . . . . . . . .

8
9
10

3 Un r
apido curso para programadores
3.1 Visi
on General de Python . . . . . .
3.1.1 Construido en contenedores .
3.1.2 Funciones . . . . . . . . . . .
3.1.3 Cadenas . . . . . . . . . . . .
3.1.4 Clases . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

11
11
12
13
14
16

4 El concepto Patr
on
4.1 Que es un Patr
on? . . . .
4.2 Taxonoma Patr
on . . . . .
4.3 Estructuras Dise
no . . . . .
4.4 Criterios de Dise
no . . . . .
4.5 Singleton . . . . . . . . . .
4.6 Clasificaci
on de Patrones . .
4.7 El desafo para el desarrollo
4.8 Ejercicios . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

20
20
21
23
24
25
30
31
32

5 2: Pruebas Unitarias
5.1 Escribir pruebas primero . . . . . . .
5.2 Simples pruebas de Python . . . . .
5.3 Un framework muy simple . . . . . .
5.4 Escribir pruebas . . . . . . . . . . .
5.5 Pruebas de caja blanca y caja negra
5.6 Ejecuci
on de Pruebas . . . . . . . . .
5.7 Ejecutar Pruebas Automaticamente
5.8 Ejercicios . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

32
34
35
36
38
41
44
48
48

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

6 3: Entornos de aplicaciones de construcci


on
48
6.1 Metodo Plantilla . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7 4: Al frente de una implementaci
on
7.1 Proxy . . . . . . . . . . . . . . . . . .
7.2 State : Estado . . . . . . . . . . . . .
7.3 StateMachine . . . . . . . . . . . . . .
7.4 Table-Driven State Machine . . . . . .
7.4.1 La clase State . . . . . . . . . .
7.4.2 Condiciones para la transicion
7.4.3 Acciones de transicion . . . . .
2

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

50
51
53
56
64
66
66
67

7.5

La tabla . . . . . . . . . . . .
7.5.1 La m
aquina basica . .
Simple m
aquina expendedora
Prueba de la m
aquina . . . .
Herramientas . . . . . . . . .
Ejercicios . . . . . . . . . . .

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

.
.
.
.
.
.

67
68
69
73
74
74

8 X: Decoradores:
Selecci
on Tipo din
amico
8.1 Estructura Decorador basico .
8.2 Un ejemplo cafe . . . . . . . .
8.3 Clase para cada combinacion
8.4 El enfoque decorador . . . . .
8.5 Compromiso . . . . . . . . . .
8.6 Otras consideraciones . . . .
8.7 Ejercicios . . . . . . . . . . .

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

.
.
.
.
.
.
.

76
77
77
77
80
83
86
87

7.6
7.7
7.8
7.9

9 Y: Iteradores:
Algoritmos de desacoplamiento de contenedores
88
9.1 Iteradores con seguridad de tipos . . . . . . . . . . . . . . . . . . 89
10 5: F
abricas:
encapsular
la creaci
on de objetos
10.1 Simple metodo de fabrica
10.2 F
abricas polim
orficas . . .
10.3 F
abricas abstractas . . . .
10.4 Ejercicios . . . . . . . . .

.
.
.
.

.
.
.
.

90
. 91
. 94
. 96
. 100

11 6 : Objetos de funci
on
11.1 Comando: la elecci
on de la operacion en tiempo de ejecucion
11.2 Estrategia: elegir el algoritmo en tiempo de ejecucion . . . . .
11.3 Cadena de responsabilidad . . . . . . . . . . . . . . . . . . . .
11.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

101
101
103
106
110

12 7: Cambiando la interfaz.
110
12.1 Adapter : Adaptador . . . . . . . . . . . . . . . . . . . . . . . . . 110
12.2 Facade : Fachada . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
12.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
13 C
odigo Tabla impulsada:
flexibilidad de configuraci
on

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
ultiple
133
15.1 Visitor, un tipo de despacho m
ultiple . . . . . . . . . . . . . . . . 139
15.2 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
16 12 : Patr
on Refactorizaci
on
16.1 Simulando el reciclador de basura . . . . . . .
16.2 Mejorando el dise
no . . . . . . . . . . . . . .
16.2.1 Hacer m
as objetos . . . . . . . . . .
16.3 Un patr
on para la creacion de prototipos . . .
16.4 Subclases Trash . . . . . . . . . . . . . . . .
16.5 Analizar Trash desde un archivo externo . .
16.6 Reciclaje con prototipos . . . . . . . . . . . .
16.7 Haciendo abstracci
on de uso . . . . . . . . . .
16.8 Despacho m
ultiple . . . . . . . . . . . . . . .
16.8.1 La implementacion del doble despacho
16.9 El patr
on Visitor : visitante . . . . . . . . . .
16.10Un decorador reflexivo . . . . . . . . . . . . .
16.10.1 M
as acoplamiento? . . . . . . . . . . .
16.11RTTI considerado da
nino? . . . . . . . . . . .
16.12Resumen . . . . . . . . . . . . . . . . . . . . .
16.13Ejercicios . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

142
142
147
147
151
155
157
160
162
167
169
176
180
186
186
190
192

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

192
192
198
198

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

La composici
on de esta traduccion se realizo utilizando LATEX, (gracias al
editor online ShareLatex)1 . El lector es totalmente libre de hacer correcciones
(en caso de alg
un error de sintaxis en la traslacion de ingles a espa
nol) dentro
de la traducci
on en beneficio de la comunidad.
Adicionalmente, el autor del libro tiene la siguiente opinion frente a la traducci
on:

Supongo que si eso es u


til para usted. Ese libro nunca fue terminado y lo
que yo escribira ahora es muy diferente. Pero si funciona para usted, eso est
a
bien.
09 de Enero de 2016. Enviado por Bruce Eckel, va Gmail.

1 www.sharelatex.com

Grupo de trabajo acad


emico GNU/Linux Universidad Distrital.
Semillero de Investigaci
on en Tecnologa Libre.
Traducido por :
Leidy Marcela Aldana Burgos. (Estudiante de Ingeniera de
Sistemas)
C
odigo Acad
emico : 20151020019
Bogot
a, Colombia.

Agradecimientos
Se reconoce la colaboraci
on para las respectivas correcciones de esta traduccion
hechas por:
Omar Leonardo Zambrano (Estudiante de Ingeniera Electronica)
Diego Andres Osorio Gutierrez (Estudiante de Ingeniera de Sistemas)

Pr
ologo

El material en este libro inici


o en conjunci
on con el seminario de Java
que yo he dado por varios a
nos, un par de veces con Larry OBrien,
luego con Bill Venners. Bill y yo hemos dado muchas repeticiones de
este seminario y lo hemos cambiado a trav
es de los a
nos ya que ambos hemos aprendido m
as acerca de patrones y sobre dar el seminario.
En el proceso, ambos hemos producido informacion mas 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 Estados Unidos, as como en Praga (donde nosotros intentamos tener una miniconferencia cada primavera a la vez con un n
umero de otros seminarios). Hemos
dado ocasionalmente un seminario en el lugar, pero esto es caro y difcil de programar porque nosotros somos dos.
Muchos agradecimientos a las personas que han participado en estos seminarios en los u
ltimos a
nos, y a Larry y a Bill, ya que me han ayudado a trabajar
mediante estas ideas y perfeccionarlas. Espero ser capaz de continuar para formar y desarrollar este tipo de ideas a traves de este libro y el seminario durante
muchos a
nos por venir.
Este libro no parar
a 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 despues de mucho examinar, decid
que la mejor manera para crear inicialmente mi escrito de patrones de dise
no 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
on de Java. He tenido la experiencia antes de emitir una idea en
un lenguaje m
as potente, luego traducir de nuevo en otro lenguaje, y he encontrado que esto es mucho m
as facil para obtener informacion y tener la idea clara.
As que Pensando en Python es, inicialmente, una traduccion de Thinking in
Patterns with Java, en lugar de una introduccion a Python (ya hay un monton
de introducciones finas para este esplendido lenguaje). Me parece que este
prospecto es mucho m
as emocionante que la idea de esforzarse a traves de otro
tutorial del lenguaje (mis disculpas a aquellos que estaban esperando para esto).

Introducci
on

Este es un libro sobre el proyecto en el que he estado trabajando durante a


nos, ya que b
asicamente nunca empec
e a tratar de leer Design
Patterns (Patrones de Dise
no) (Gamma,Helm,Johnson y Vlissides,
Addison-Wesley, 1995), com
unmente conocida como Gang of Four 2
o solo GoF).
Hay un captulo sobre los patrones de dise
no en la primera edicion de Thinking in C++, que ha evolucionado en el Volumen 2 de la segunda edicion de
Thinking in C++ y usted tambien encontrara un captulo sobre los patrones en
la primera edici
on de Thinking in Java. Tome ese captulo de la segunda edicion
de Thinking in Java porque ese libro fue creciendo demasiado, y tambien porque
yo haba decidido escribir Thinking in Patterns. Ese libro, a
un no se ha terminado, se ha convertido en este. La facilidad de expresar estas ideas mas
complejas en Python, creo que, finalmente, me permitira decirlo todo.
Este no es un libro introductorio. Estoy asumiendo que usted ha trabajado su camino a traves de por lo menos Learning Python (por Mark Lutz y
David Ascher; OReilly, 1999) o un texto equivalente antes de empezar este libro.
Adem
as, Supongo que tiene algo mas que una comprension de la sintaxis de
Python. Usted debe tener una buena comprension 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
on orientada a objetos al ver objetos utilizados en muchas situaciones
diferentes. Si su conocimiento de objetos es elemental, este se consiguira mucho
m
as fuerte en el proceso de comprension de los dise
nos en este libro.

2 Esta es una referencia ir


onica para un evento en China despu
es 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.

2.1

El sndrome Y2K

En un libro que tiene tecnicas de resolucion de problemas en su subtitulo,


vale la pena mencionar una de las mayores dificultades encontradas en la programaci
on: la optimizaci
on prematura. Cada vez traigo este concepto hacia
adelante, casi todo el mundo esta de acuerdo en ello. Ademas, todo el mundo
parece reservar en su propia mente un caso especial a excepcion de esto que
me he enterado es un problema particular.
La raz
on por la que llamo a esto el sndrome Y2K debo hacerlo con ese
especial conocimiento. Los computadores son un misterio para la mayora de
la gente, as que cuando alguien anuncio que los tontos programadores de computadoras haban olvidado poner suficientes dgitos para mantener las fechas
m
as all
a del a
no 1999, de repente todo el mundo se convirtio en un experto
en inform
atica estas cosas no son tan difcil despues de todo, si puedo ver
un problema tan obvio. Por ejemplo, mi experiencia fue originalmente en ingeniera inform
atica, y empece a cabo mediante la programacion de sistemas
embebidos. Como resultado, se 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
alculos importantes. Y sin embargo, me dijeron en terminos muy claros
que todos los sistemas embebidos iban a bloquearse en 01 de enero 2000 3 .En
lo que puedo decir el u
nico recuerdo que se perdio en esa fecha en particular
fue el de las personas que estaban prediciendo perdida que es como si nunca
hubieran dicho nada de eso.
El punto es que es muy facil caer en el habito de pensar que el algoritmo
particular o el trozo de c
odigo que usted por casualidad entiende en parte o
totalmente, es naturalmente, sera el estancamiento en su sistema, simplemente
porque puede imaginar lo que esta pasando en ese trozo de codigo y as, que
usted piensa que debe ser de alguna manera mucho menos eficiente que el resto
de piezas de c
odigo que usted no conoce. Pero a menos que haya ejecutado las
pruebas reales, tpicamente con un perfilador, realmente no se puede saber lo
que est
a pasando. E incluso si usted tiene razon, que una pieza de codigo es
muy ineficiente, recuerde que la mayora de los programas gastan algo as como
90% de su tiempo en menos de 10% del codigo en el programa, as que a menos
que el trozo de c
odigo que usted esta pensando sobre lo que sucede al caer en
ese 10% no va a ser importante.
Optimizaci
on prematura es la raz de todo mal. se refiere a veces como
La ley de Knuth (de Donald E. Knuth).
3 Estas mismas personas tambi
en estaban convencidos de que todos los ordenadores iban
a bloquearse a continuaci
on, tambi
en. Pero como casi todo el mundo tena la experiencia de
su m
aquina Windows estrell
andose todo el tiempo sin resultados particularmente graves, esto
no parece llevar el mismo drama de la cat
astrofe inminente.

2.2

Contexto y composici
on

Uno de los terminos que vera utilizar una y otra vez en literatura de patrones
de dise
no es context. De hecho, una definicion com
un de un patron de dise
no
es: Una soluci
on a un problema en un contexto. Los patrones GoF a menudo
tienen un objeto de contexto que el programador interact
ua con el cliente. En
cierto momento se me ocurrio que dichos objetos parecan dominar el paisaje
de muchos patrones, y as comence preguntando de que trataban.
El objeto de contexto a menudo act
ua como una peque
na fachada para ocultar la complejidad del resto del patron, y, ademas, a menudo sera el controlador
que gestiona el funcionamiento del patron. Inicialmente, me pareca que no era
realmente esencial para la implementacion, uso y comprension del patron. Sin
embargo, Recorde una de las declaraciones mas dramaticas realizadas en el GoF:
Preferira composici
on a la herencia. El objeto de contexto le permite utilizar
el patr
on en una composici
on, y eso puede ser su valor principal.

10

Un r
apido curso para programadores

Este libro asume que usted es un programador experimentado, y es


mejor si usted ha aprendido Python a trav
es de otro libro. Para todos
los dem
as, este capitulo da una r
apida introducci
on al lenguaje.

3.1

Visi
on General de Python

Esta breve introducci


on es para el programador experimentado (que es lo que
usted debera ser si esta leyendo este libro). Usted puede atribuir a la documentaci
on completa de www.Python.org (especialmente a la pagina HTML increblemente u
til A Python Quick Reference), y tambien numerosos libros como
Learning Python por Mark Lutz y David Ascher(OReilly, 1999).
Python se conoce a menudo como un lenguaje de script, pero los lenguajes
de script tienden a estar limitando, especialmente en el ambito de los problemas
que ellos resuelven. Python, por otro lado, es un lenguaje de programacion que
tambien soporta Scripting. Es maravilloso para scripting, y puede encontrar
usted mismo la sustituci
on de todos sus archivos por lotes, scripts de shell, y
programas sencillos con scripts de Python. Pero es mucho mas que un lenguaje
de script.
Python est
a dise
nado para ser muy limpio para escribir y especialmente para
leer. Usted encontrar
a que es muy facil leer su propio codigo mucho despues de
que lo ha escrito, y tambien para leer el codigo de otras personas. Esto se logra
parcialmente a traves de la sintaxis limpia, al punto, pero un factor mayor en la
legibilidad del c
odigo es la identacion la determinacion del alcance en Python
viene determinada por la identacion. 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 basica de Python es C-ish como se
puede ver en la declaraci
on if. Pero en C un if, se vera obligado a utilizar
parentesis alrededor del condicional, mientras que no son necesarios en Python
(no reclamar
a si los usa de todas formas).

11

La cl
ausula condicional termina con dos puntos, y esto indica que lo que
sigue ser
a un grupo de sentencias identadas, que son la parte entonces de la
sentencia if. En este caso hay una declaracion de imprimir el cual enva el
resultado a la salida est
andar, seguido de una asignacion a una variable llamada
val. La declaraci
on posterior no esta identada as ya no es parte del if. Identando puede anidar a cualquier nivel, al igual que los corchetes en C ++ o Java,
pero a diferencia de esos lenguajes no hay ninguna opcion (y ning
un argumento)
acerca de d
onde se colocan los corchetes el compilador obliga el codigo 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 solo una declaracion por lnea (se puede poner
m
as separ
andolos con punto y coma), por lo que el punto y coma de terminacion
no es necesario. Incluso desde el breve ejemplo anterior se puede ver que el
lenguaje est
a dise
nado 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


nadidos en las librerias
y no integros al lenguaje. En Python, la naturaleza esencial de los contenedores
para la programaci
on es reconocido por su construccion en el n
ucleo del lenguaje:
ambas listas y matrices (: arrays) asociativas (mapas alias, diccionarios, tablas
hash) son tipos de datos fundamentales. Esto a
nade mucho a la elegancia del
lenguaje.
Adem
as, la declaraci
on for itera automaticamente a traves de las listas y
no s
olo contando a traves de una secuencia de n
umeros. Tiene mucho sentido
cuando se piensa en esto, ya que casi siempre esta utilizando un bucle for para
recorrer una matriz o un contenedor. Python formaliza esto automaticamente
haciendo uso de for un iterador que funciona a traves 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 mostrara exactamente como usted pone esto (en contraste, recuerde que yo tuve que crear
una clase especial Arrays2 en Thinking in Java, 2da Edici
on en orden para imprimir arrays en Java). Las listas son como contenedores de Java usted puede
12

a
nadir elementos nuevos a ellos (aqu, es usado append()) y van a cambiar
autom
aticamente el tama
no de s mismos. La sentencia for crea un iterador x
que toma cada valor de la lista.
Usted puede crear una lista de n
umeros con la funcion 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 objetos aparecen simplemente, y Python infiere su tipo por la forma en que se usan.
Es como si Python est
a dise
nado para que usted solo necesite pulsar las teclas
que sean absolutamente necesarias. Usted encontrara despues de haber trabajado 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 palabrera adicional que fue exigido por su lenguaje de programacion
diferente de Python pero no describe en realidad lo que se supona que hiciera
su programa.
3.1.2

Funciones

Para crear una funci


on en Python, use la palabra clave def, seguido por el nombre de la funci
on y la lista de argumentos, y dos puntos para empezar el cuerpo
de la funci
on. Aqu esta el primer ejemplo convertido en una funcion:
#: 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 informacion de tipo en la firma de la funcion todo lo
que se especifica es el nombre de la funcion 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 mnimos posibles en
la introducci
on de caracteres. Por ejemplo, usted podra pasar y devolver diferentes tipos desde la misma funcion:

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 u
nicas limitaciones sobre un objeto que se pasa a la funcion, son que
la funci
on puede aplicar sus operaciones a ese objeto, pero aparte de eso, no
importa. Aqu, la misma funcion 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 concatenacion,
(si, Python soporta la sobrecarga de operadores, y esto hace un buen trabajo
del mismo).

3.1.3

Cadenas

El ejemplo anterior tambien muestra un poco sobre manejo de cadenas de


Python, que es el mejor lenguaje que he visto. Usted puede usar comillas simples 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 despues de la serpiente, sino
m
as bien la compa
na de comedia Monty Python, y as los ejemplos estan
pr
acticamente obligados a incluir referencias Python-esque.

14

La triple cita de sintaxis cita todo, incluyendo saltos de lnea. Esto hace
que sea especialmente u
til para hacer las cosas como la generacion de paginas
web (Python es un lenguaje CGI especialmente bueno), ya que usted puede solo
triple citar la p
agina completa que desee sin ninguna otra edicion.
La r justo antes significa una cadena raw : en bruto, que toma las barras invertidas : \\, literalmente as que usted no tiene que poner en una barra
inversa extra a fin de insertar una barra invertida literal.
La sustituci
on en cadenas es excepcionalmente facil, ya que Python usa de
C la sintaxis de sustituci
on 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 mas de un argumento
entre parentesis (esto forma una tupla, que es una lista que no puede ser modificado tambien puede utilizar las listas regulares para m
ultiples argumentos,
pero tuplas son tpicas).
Todo el formato de printf() es valido, incluyendo el control sobre el lugar
y alineaci
on de n
umeros decimales. Python tambien tiene expresiones regulares
muy sofisticadas.

15

3.1.4

Clases

Como todo lo dem


as en Python, la definicion de una clase usa una mnima sintaxis adicional. Usted usa la palabra clave class, y en el interior del cuerpo se
utiliza def para crear metodos. Aqu esta una clase simple:
#: c01 : S i m p l e C l a s s . py
c l a s s Simple :
i n i t ( self , str ):
def
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
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 )
#:
if

Ambos metodos tienen self como su primer argumento. C++ y Java ambos tienen un primer argumento oculto en sus metodos de clase, el cual apunta
al objeto para el metodo que fue llamado y se puede acceder usando la palabra clave this. Los metodos de Python tambien utilizan una referencia al
objeto actual, pero cuando usted esta definiendo un metodo debe especificar
explcitamente la referencia como el primer argumento. Tradicionalmente, la
referencia se llama self pero usted podra utilizar cualquier identificador que
desee (si usted no utiliza self probablemente confundira a mucha gente, sin embargo). Si necesita hacer referencia a campos en el objeto u otros metodos en el
objeto, debe utilizar self en la expresion. Sin embargo, cuando usted llama un
metodo para un objeto como en x.show(), no entrega la referencia al objeto
que esta hecha para usted.
Aqu, el primer metodo es especial, como lo es cualquier identificador que
comienza y termina con doble guion bajo. En este caso, define el constructor,
el cual es llamado autom
aticamente 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
on de un objeto se parece a una llamada a la funcion 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
odigo al fondo se pone en marcha por una clausula if, la cual
comprueba para ver si algo llamado name es equivalente a main . De
nuevo, los dobles guiones bajos indican nombres especiales. La razon de if es
que cualquier archivo tambien puede ser utilizado como un modulo de librera
dentro de otro programa (modulos se describen en breve). En ese caso, usted
s
olo quiere las clases definidas, pero usted no quiere el codigo en la parte inferior del archivo a ejecutar. Esta declaracion en particular if solo es verdadera
cuando est
a ejecutando este archivo directamente; eso es, si usted dice en la
lnea 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 modulo en otro programa,
no se ejecuta el c
odigo main .
Algo que es un poco sorprendente al principio es que se define campos dentro
de los metodos, y no fuera de los metodos como C ++ o Java (si crea campos
utilizando el estilo de C ++ / Java, implcitamente se convierten en campos
est
aticos). Para crear un campo de objeto, solo lo nombra usando self
dentro de uno de los metodos (usualmente en el constructor, pero no siempre),
y se crea el espacio cuando se ejecuta ese metodo. Esto parece un poco extra
no viniendo de C++ o Java donde debe decidir de antemano cuanto espacio
su objeto va a ocupar, pero resulta ser una manera muy flexible para programar.
Herencia
Porque Python es debilmente tipado, esto realmente no importa sobre interfaces lo u
nico que le importa es la aplicacion de las operaciones a los objetos
(de hecho, la palabra clave interface de Java sera 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
un. En Python, la u
nica razon por la que hereda es para heredar una implementaci
on reutilizar el codigo 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 inclinaci
on de Python para la sencillez). Cada vez que se crea un archivo, se crea
implcitamente un m
odulo (que es como un paquete en Java) con el mismo nombre que el archivo. Por lo tanto, no se necesito la palabra clave package en
Python. Cuando se desea utilizar un modulo, solo dice import y da el nombre
del m
odulo. Python busca el PYTHONPATH del mismo modo que Java busca
el CLASSPATH (pero por alguna razon, Python no tiene el mismo tipo de dificultades al igual que Java) y lee en el archivo. Para referirse a cualquiera de
las funciones o clases dentro de un modulo, usted le da el nombre del modulo,
un perodo, y el nombre de la funcion 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) enumerando el nombre(s) : name(s) de la clase dentro de parentesis despues del
nombre de la clase heredera. Tenga en cuenta que la clase Simple, la cual
reside en el archivo (y por lo tanto, el modulo) 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 basec 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 basec 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 basec 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
name
== m a i n :
if
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 ( ) # Onel 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
metodo 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 tambien puede verse en la version anulada de show( ).
18

En main , usted puede ver (cuando corre el programa) que el constructor de la clase base es llamado. Tambien puede ver que el metodo showMsg()
es v
alido en las clases derivadas, del mismo modo que se puede esperar con la
herencia.
La clase Different tambien tiene un metodo llamado show(), pero esta
clase no es derivada de Simple. El metodo f() definido en main demuestra
tipificaci
on debil: lo u
nico que importa es que show() se puede aplicar a obj, y
no tiene ning
un 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
on. Si usted es un programador de C++, debera ver que el
objetivo de la funci
on de C ++ template es exactamente esto: proporcionar
tipificaci
on debil en un lenguaje fuertemente tipado. Por lo tanto, en Python
autom
aticamente obtendr
a el equivalente de plantillas sin tener que aprender
esa sintaxis y sem
antica particularmente difcil.
[[Sugerir Otros Temas para su inclusion en el captulo introductorio]]

19

El concepto Patr
on

Los patrones de dise


no ayudan a aprender de los
exitos de los dem
as
en lugar de sus propios fracasos
4

Probablemente el avance mas importante en el dise


no orientado a objetos es
el movimiento patrones de dise
no, cronica en Design Patterns : Dise
no de patrones (ibid) 5 Ese libro muestra 23 soluciones diferentes a las clases particulares
de problemas. En este libro, los conceptos basicos de los patrones de dise
no se
introducir
an junto con ejemplos. Esto debera abrir su apetito para leer Design
Patterns : Dise
no 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 programacion orientada a objetos.
La u
ltima parte de este libro contiene un ejemplo del proceso de evolucion del
dise
no, comenzando con una solucion inicial y moviendose a traves de la logica
y el proceso de la evoluci
on de la solucion a los dise
nos mas apropiados. El
programa mostrado (una simulacion de clasificacion de basura) ha evolucionado
con el tiempo, puede mirar en dicha evolucion como un prototipo de la forma
en que su propio dise
no puede comenzar como una solucion adecuada a un
problema particular y evolucionar hacia un enfoque flexible para una clase de
problemas.

4.1

Qu
e es un Patr
on?

Inicialmente, usted puede pensar en un patron como una forma especialmente


inteligente y perspicaz de la solucion de una determinada clase de problemas. Es
decir, parece que muchas personas han trabajado todos los angulos de un problema y han llegado con la solucion mas general y flexible para ello. El problema
podra ser uno que ha visto y resuelto antes, pero su solucion probablemente no
tena el tipo de integridad usted vera incorporada en un patron.
Aunque se les llama patrones de dise
no, ellos realmente no estan atados
al
ambito del dise
no. Un patron parece estar al margen de la forma tradicional
de pensar en el an
alisis, dise
no, e implementacion. En lugar, un patron encarna
una idea completa dentro de un programa, y por lo tanto a veces puede aparecer
en la fase de an
alisis o de la fase de dise
no de alto nivel. Esto es interesante
porque un patr
on tiene una aplicacion directa en el codigo y por lo que podra
no esperar que aparezca antes del dise
no o implementacion de bajo nivel (de
hecho, es posible que no se de cuenta de que se necesita un patron particular
hasta llegar a esas fases).
El concepto b
asico de un patron tambien puede ser visto como el concepto
b
asico de dise
no del programa: la adicion de una capa de abstraccion. Cuando
4 De

Mark Johnson
cuidado: los ejemplos est
an en C ++.

5 Pero

20

usted abstrae algo usted esta aislando detalles particulares, y una de las motivaciones m
as convincentes detras 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
on u otra, usted querr
a mantener esos cambios con respecto a la propagacion
de otros cambios a traves de su codigo. Esto no solo hace el codigo mucho mas
econ
omico de mantener, pero tambien resulta que por lo general es mas simple
de entender (lo cual resulta en menores costes).
A menudo, la parte m
as difcil de desarrollar un dise
no elegante y economico
de mantener, es en el descubrimiento de lo que yo llamo el vector del cambio.
(Aqu, vector se refiere a la gradiente maxima y no una clase contenedora.)
Esto significa encontrar la cosa mas 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 estructurar su dise
no.
As que el objetivo de los patrones de dise
no es aislar los cambios en su
c
odigo. Si se mira de esta manera, usted ha estado viendo algunos patrones de
dise
no que ya est
an en este libro. Por ejemplo, la herencia puede ser pensado
como un patr
on de dise
no (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
on tambien puede ser considerada un patron, ya que le permite
cambiar din
amica o est
aticamente los objetos que implementan la clase,
y por lo tanto la forma en que funciona la clase.
Otro patr
on que aparece en Design Patterns es el iterador, el cual ha sido
implcitamente v
alido en bucles for desde el comienzo del lenguaje, y fue introducido como una caracterstica explcita en Python 2.2. Un iterador le permite
ocultar la implementaci
on particular de del contenedor como usted esta pasando
a traves de los elementos y seleccionando uno por uno. As, puede escribir codigo
generico que realiza una operacion en todos los elementos en una secuencia sin
tener en cuenta la forma en que se construye la secuencia. As, su codigo generico
se puede utilizar con cualquier objeto que pueda producir un iterador.

4.2

Taxonoma Patr
on

Uno de los acontecimientos que se produjeron con el aumento de patrones de


dise
no es lo que podra ser considerado como la contaminacion del termino
la gente ha empezado a utilizar el termino para definir casi cualquier cosa en
sin
onimo de bueno. Despues de alguna ponderacion, yo he llegado con una
especie de jerarqua que describe una sucesion de diferentes tipos de categoras:

21

1.Idioma:
C
omo escribimos c
odigo en un lenguaje particular, para hacer este tipo particular de cosas. Esto podra ser algo tan com
un como la forma en que codifica el
proceso de paso a paso a traves de una matriz en C (y no se salga del final).
2.Dise
no Especifico:
la soluci
on que se nos ocurrio para resolver este problema en particular. Esto
podra ser un dise
no inteligente, pero no intenta ser general.
3.Dise
no Est
andar:
una manera de resolver este tipo de problema. Un dise
no que se ha vuelto mas
general, tpicamente a traves de la reutilizacion.
4. Patr
on de Dise
no:
c
omo resolver toda una clase de problema similar. Esto normalmente solo
aparece despues de la aplicacion de un dise
no estandar un n
umero de veces,
y despues de ver un patr
on com
un a traves de estas aplicaciones.
Siento que esto ayuda a poner las cosas en perspectiva, y para mostrar donde
algo podra 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
on de dise
no 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.
Tambien se podra argumentar a favor de la inclusion del Analysis Pattern
: Patr
on An
alisis y Architectural Pattern : Patr
on arquitect
onico en esta taxonoma.

22

4.3

Estructuras Dise
no

Una de las luchas que he tenido con los patrones de dise


no es su clasificacion A menudo he encontrado el enfoque GoF a ser demasiado oscuro, y no siempre
muy servicial. Ciertamente, los patrones creacionales son bastante sencillos:
c
omo se va a crear sus objetos? Esta es una pregunta que normalmente necesita 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 u
tiles. No he sido capaz de mirar un problema y
decir Claramente, se necesita un patron estructural aqu, por lo que la clasificaci
on no me lleva a una solucion (Voy a admitir facilmente que yo pueda estar
perdiendo algo aqu).
He trabajado por un tiempo con este problema, primero se
nalando 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 u
til se vera 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 basicas de dise
no,
y tratar de ver si hay una manera de relacionar aquellas estructuras a los diversos patrones de dise
no que aparecen en sistemas bien pensados. Corrientemente,
s
olo estoy tratando de hacer una lista, pero eventualmente espero hacer pasos
hacia la conexi
on de estas estructuras con los patrones (o Puedo llegar con un enfoque totalmente diferente esta se encuentra todava en su etapa de formacion)
Aqu 6 est
a la lista actual de candidatos, solo algunos de los cuales llegaran
al final de la lista. Sientase libre de sugerir otros, o posiblemente, las relaciones
con los patrones.
Encapsulaci
on: auto contencion e incorporando un modelo de uso.
Concurrencia
Localizaci
on
Separaci
on
Ocultaci
on
Custodiando
Conector
Obst
aculo/valla
6 Esta

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

23

Variaci
on en el Comportamiento
Notificaci
on
Transacci
on
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 diferente (Puede ser una variacion de Proxy).

4.4

Criterios de Dise
no

Cuando puse un concurso de ideas en mi boletn de noticias7 , una serie de sugerencias regresaron, lo cual resulto ser muy u
til, pero diferente a la clasificacion
anterior, y me di cuenta de que una lista de principios de dise
no es al menos
tan importante como estructuras de dise
no, pero por una razon diferente: estos
permiten hacer preguntas sobre su dise
no propuesto, para aplicar las pruebas
de calidad.
Principio de menor asombro: (no se sorprenda).
Hacer f
aciles las cosas comunes, y posibles las cosas raras
Consistencia: Una cosa ha llegado a ser muy claro para m, especialmente debido a Python: las normas mas al azar que acumula sobre el
programador, reglas que no tienen nada que ver con la solucion del problema en cuesti
on, m
as lento que el programador puede producir. Y esto
no parece ser un factor lineal, sino una exponencial.
Ley de Demeter: tambien denominado No hables con extra
nos. Un
objeto s
olo debe hacer referencia a s mismo, sus atributos, y los argumentos de sus metodos.
Sustracci
on: un dise
no se termina cuando no puede llevar nada mas
lejos8 .
Simplicidad antes de generalidad:9 (Una variacion de Occams Razor, que dice que la solucion mas simple es el mejor). Un problema
com
un que encontramos en marcos es que estan dise
nados para ser de uso
general sin hacer referencia a los sistemas reales. Esto lleva a una increble
variedad de opciones que estan a menudo sin uso, mal uso o simplemente
7 Una

publicaci
on de correo electr
onico gratuito. Ver www.BruceEckel.com para suscribirse.
idea se atribuye generalmente a Antoine de St. Exupery de The Little Prince : El
principito La perfection est atteinte non quand il ne reste rien `
a ajouter, mais quand il ne
reste rien a
` enlever, o La perfecci
on se alcanza no cuando no hay nada m
as que a
nadir, sino
cuando no hay nada m
as que eliminar.
9 A partir de un correo electr
onico de Kevlin Henney.
8 Esta

24

no es u
til. Sin embargo, la mayora de los desarrolladores trabajan en
sistemas especficos, y la b
usqueda de la generalidad no siempre sirven
bien. La mejor ruta para la generalidad es a traves de la comprension
de ejemplos especficos bien definidos. Por lo tanto, este principio act
ua
como el desempate entre alternativas de dise
no de otro modo igualmente
viables. Por supuesto, es totalmente posible que la solucion mas simple es
la m
as general.
La reflexividad: (mi termino sugerido). Una abstraccion por clase, una
clase por la abstracci
on. Tambien podra ser llamado Isomorfismo.
Independencia o Ortogonalidad. Expresar ideas independientes de
forma independiente. Esto complementa Separacion, Encapsulacion y
Variaci
on, y es parte del mensaje de bajo Acoplamiento-alta de Cohesion.
Una vez y s
olo una vez: Evitar la duplicacion de la logica y la estructura
donde la duplicaci
on no es accidental, es decir, donde ambas piezas de
c
odigo expresan la misma intencion por la misma razon.
En el proceso de intercambio de ideas de esta idea, Espero llegar a un
peque
no pu
nado 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 u
tiles como una lista de verificacion mientras
camina a traves y analizando su dise
no.

4.5

Singleton

Posiblemente el patr
on de dise
no mas simple es el Singleton, el cual es una manera de proporcionar un y s
olo un objeto de un tipo particular. Para lograr esto,
usted debe tomar el control de la creacion de objetos fuera de las manos del
programador. Una forma c
omoda 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 :
OnlyOne :
class
def
i n i t ( s e l f , arg ) :
s e l f . val = arg
str ( self ):
def
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 .
else :
25

OnlyOne ( a r g )

OnlyOne . i n s t a n c e . v a l = a r g
g e t a t t r ( s e l f , name ) :
def
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 privado por lo que el usuario no puede acceder directamente a ella. La clase interna
contiene todos los metodos 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
on mediante el uso de su constructor. La primera vez que usted crea un
OnlyOne, inicializa instance, pero despues de eso solo le ignora.
El acceso viene a traves de la delegacion, usando el metodo getattr ( )
para redireccionar las llamadas a la instancia u
nica. Se puede ver en la salida
que a pesar de que parece que se han creado m
ultiples 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 creacion de
un solo objeto. Esta es tambien una tecnica para crear un grupo limitado de
objetos. En esa situaci
on, sin embargo, usted puede ser confrontado con el
problema de compartir objetos en el grupo. Si esto es un problema, puede crear
una soluci
on involucrando una salida y el registro de los objetos compartidos.

26

Una variaci
on en esta tecnica utiliza el metodo de la clase
en Python 2.2:

new

a
nadido

#: c01 : NewSingleton . py
c l a s s OnlyOne ( o b j e c t ) :
class
OnlyOne :
init ( self ):
def
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
new ( c l s ) : #
new
always a c l a s s m e t h o d
def
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
< m a i n . OnlyOne
< m a i n . OnlyOne
< m a i n . OnlyOne
< m a i n . OnlyOne

#:

instance
instance
instance
instance
instance

at
at
at
at
at

0 x00798900>s a u s a g e
0 x00798900>e g g s
0 x00798900>spam
0 x00798900>spam
0 x00798900>spam

Alex Martelli hace la observacion de que lo que realmente queremos con un


Singleton es tener un u
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 informacion de estado y luego lograr el efecto de Singleton.
27

logra esto con lo que el llama Borg 10 , lo cual se logra configurando todas las
El
dict s a la misma pieza estatica 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 ) :
init ( self )
Borg .
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 mas
elegante. En el primer caso, deben conectarse en el comportamiento Singleton a
cada una de sus clases, pero Borg esta dise
nado para ser reutilizado facilmente
10 Del programa de televisi
on Star Trek: The Next Generation. Los Borg son un colectivo
colmena-mente: todos somos uno.

28

a traves de la herencia.
Otras dos formas interesantes para definir singleton11 incluyen envolviendo
una clase y utilizando metaclases. El primer enfoque podra ser pensado como
un decorador de clase (decoradores se definiran mas adelante en el libro), porque
lleva la clase de interes y a
nade funcionalidad a ella envolviendola 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
c a l l ( s e l f , a r g s , kwds ) :
def
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
on ]]
El segundo enfoque utiliza metaclases, un tema que a
un 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
= s t a t i c m e t h o d ( my new )
c l s . new
c l a s s bar ( o b j e c t ) :
metaclass
= SingletonMetaClass
i n i t ( s e l f , val ) :
def
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
on prolongada, detallada, informativa de lo que son metaclases y
c
omo funcionan, por arte de magia insertado aqu]]
Ejercicio
Modificar BorgSingleton.py para que utilice un metodo

4.6

new () de clase.

Clasificaci
on de Patrones

El libro Design Patterns discute 23 patrones diferentes, clasificados en tres


prop
ositos (los cuales giran en torno al aspecto particular que puede variar).
Los tres prop
ositos son:
1. Creacional: c
omo se puede crear un objeto. Esto a menudo involucra
el aislamiento de los detalles de la creacion de objetos por lo que su codigo no
depende de que 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 clasificado como un patr
on creacional, y mas tarde en este libro usted vera ejemplos
de Factory Method y Prototype.

30

2. Estructural: dise
nando objetos para satisfacer determinadas restricciones del proyecto. Estos funcionan con la forma en que los objetos estan
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
on de un lenguaje, el cumplimiento de una solicitud,
movimiento a traves de una secuencia (como en un iterador), o implementando
un algoritmo. Este libro contiene ejemplos de los patrones Observer : Observador y Visitor : visitante.
El libro Design Patterns tiene una seccion por cada uno de sus 23 patrones
junto con uno o m
as ejemplos para cada uno, normalmente en C ++, pero a
veces en Smalltalk. (Usted encontrara que esto no importa demasiado puesto
que puedes traducir f
acilmente los conceptos de cualquier lenguaje en Python.)
Este libro no repetir
a todos los patrones mostrados en Design Patterns ya que el
libro se destaca por su cuenta y debera ser estudiado por separado. En lugar de
ello, este libro dar
a algunos ejemplos que debera proporcionarle una sensacion
decente para lo que son los patrones y por que son tan importantes.
Despues de a
nos de mirar estas cosas, ello comenzo a ocurrir para mi que
los patrones utilizan para s mismos principios basicos de organizacion, distintos
de (y m
as fundamental que) los descritos en Design Patterns. Estos principios se basan en la estructura de las implementaciones, que es donde he visto
grandes similitudes entre los patrones (mas que aquellos expresados en Design
Patterns). Aunque nosotros generalmente tratamos de evitar la implementacion
en favor de la interfaz, he encontrado que a menudo es mas facil que pensar, y
especialmente para aprender acerca de los patrones en terminos de estos principios estructurales. Este libro tratara de presentar los patrones basados en su
estructura en lugar de las categoras presentadas en Design Patterns.

4.7

El desafo para el desarrollo

Problemas del desarrollo, el proceso UML, Programacion extrema.


Es la evaluaci
on valiosa? La Capacidad de la inmadurez del modelo:
Wiki P
agina: http://c2.com/cgi-bin/wiki?CapabilityImMaturityModel
Articulo: http://www.embedded.com/98/9807br.htm
Investigaci
on programaci
on en parejas:

31

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

4.8

Ejercicios

1. SingletonPattern.py siempre crea un objeto, incluso si nunca se ha utilizado. Modifique este programa para usar lazy initialization, por lo que el
objeto singleton s
olo 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 terminos en cualquier momento.

2: Pruebas Unitarias

Este captulo no ha tenido traduccion significativa todava.

Una de las realizaciones recientes importantes es el valor dramatico


de las pruebas unitarias.
Este es el proceso de construccion de pruebas integradas en todo
el codigo que usted crea, y ejecutar esas pruebas cada vez que hace
una construccion. Es como si usted esta ampliando el compilador,
diciendole mas acerca de lo que se supone que su programa hace. De
esa manera, el proceso de construccion puede comprobar para algo
mas que errores de sintaxis, ya que usted le ense
na como comprobar
si hay errores semanticos tambien.
Los Lenguajes de programacion de estilo C, y C++ en particular, han valorado tpicamente rendimiento sobre la seguridad de
programacion. La razon de que el desarrollo de programas en Java
es mucho mas rapido que en C ++ es debido a la red de seguridad
de Java: caractersticas como la mejor comprobacion de tipos, excepciones forzadas y recoleccion de basura. Mediante la integracion
de la unidad de pruebas en su proceso de construccion, usted esta
ampliando esta red de seguridad, y el resultado es que usted puede
desarrollar mas rapidamente. Tambien puede ser mas audaz en los
cambios que realice, y refactorizar mas facilmente su codigo cuando
se descubre el desperfector en el dise
no o la implementacion, y en

32

general, producir un producto mejor, mas rapido.


Las pruebas unitarias no se consideran generalmente un patron
de dise
no; de hecho, podran ser consideradas un patron de desarrollo, pero tal vez hay suficientes frases patron 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 sera introducido aqu.
My propia experiencia con las pruebas unitarias empezo cuando
Me di cuenta que todos los programas en un libro debe ser extrado
de forma automatica y organizado en un arbol de codigo fuente,
junto con makefiles apropiados (o alguna tecnologa equivalente) asi
que usted solo podra escribir make para construir todo el arbol.
El efecto de este proceso sobre la calidad del codigo del libro era
tan inmediato y dramatico que pronto se convirtio (en mi mente) en
un requisito para cualquier libro de programacion como puede
confiar en el codigo que usted no compila? Tambien descubr que si
quera hacer cambios radicales, Yo podra hacerlo as usando buscary-reemplazar en todo el libro, y tambien atacar el codigo a voluntad.
Yo saba que si yo introduje una falla, el extractor de codigo y los
makefiles podran eliminarla.
Como los programas llegaron a ser mas complejos, sin embargo,
Tambien me di cuenta de que haba un agujero en serio en mi sistema. Siendo capaz de compilar con exito programas es claramente
un primer paso importante, y por un libro publicado pareca uno
bastante revolucionario por lo general debido a las presiones de
la publicacion, es bastante tpico abrir al azar un libro de programacion y descubrir un defecto de codificacion. Ahora bien, Segu
recibiendo mensajes de los lectores reportando problemas semanticos
en mi codigo (en Thinking in Java). Estos problemas solo podan
ser descubiertos mediante la ejecucion del codigo. Naturalmente,
Entend esto y haba tomado algunos pasos vacilantes tempranos
hacia la implementacion de un sistema que realizara pruebas de
ejecucion automatica, pero yo haba sucumbido a las presiones de la
publicacion, todo el tiempo sabiendo que definitivamente haba algo
equivocado con mi proceso y que esto volvera a atacarme en forma
de informes de errores embarazosos (en el mundo del codigo abierto,
la verg
uenza es uno de los principales factores de motivacion hacia
33

el aumento de la calidad de su codigo!).


El otro problema fue que me faltaba una estructura para el sistema de pruebas. Eventualmente, empece escuchando acerca de las
pruebas unitarias y JUnit12 , lo cual proporciono una base para una
estructura de prueba. No obstante, aunque JUnit esta destinado a
hacer facil la creacion de codigo de prueba, quera ver si poda hacerlo a
un mas facil, aplicando el principio de Programacion Extrema
de Hacer la cosa mas simple que podra posiblemente funcionar
como punto de partida, y luego la evolucion del sistema como demandas de uso (Ademas, quera tratar de reducir la cantidad de
codigo de prueba, en un intento de ajustarse a una mayor funcionalidad en menos codigo para presentaciones en pantalla). Este captulo
es el resultado.
5.1

Escribir pruebas primero

Como yo mencione, uno de los problemas que encontre que la


mayora de la gente encuentra, a resolver fue someterse a las presiones de la editorial y como resultado dejando caer pruebas por el
borde del camino. Esto es facil de hacer si usted sigue adelante y
escribe el codigo de su programa porque hay una peque
na voz que
te dice que, despues de todo, lo has conseguido funcionando ahora,
y no sera mas interesante / u
til / conveniente simplemente seguir
adelante y escribir esa otra parte (siempre podemos volver atras y
escribir las pruebas mas adelante). Como resultado, las pruebas
asumen menos importancia, como hacen a menudo en un proyecto
de desarrollo.
La respuesta a este problema, el cual encontre la primera vez
descrito en Extreme Programming Explained, es escribir las pruebas
antes de escribir el codigo. Esto puede parecer para forzar artificialmente 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 codigo hace, no con alguna herramienta grafica externa pero con el codigo que realmente coloca
12 ttp://www.junit.org

34

la especificacion en terminos concretos y verificables.


2. Proporcionar un ejemplo de como se debe utilizar el codigo; de
nuevo, esto es un funcionamiento, ejemplo probado, mostrando
normalmente todas las llamadas a metodos importantes, en lugar
de solo una descripcion academica de una librera.
3. Proporcionar una forma de verificar cuando se termina el codigo
(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 solo un paso de verificacion que se puede omitir si sucede que se siente comodo sobre
el codigo que acabas de escribir (un consuelo, he encontrado, que es
usualmente equivocado).
Usted puede encontrar argumentos convincentes en Extreme Programming Explained, como Escribir pruebas primero es un principio fundamental de XP. Si usted no esta convencido que necesita
adoptar cualquiera de los cambios sugeridos por XP, tenga en cuenta
que conforme a los estudios del Instituto de Ingeniera de Software
(SEI), casi el 70% de las organizaciones de software se ha quedado
atascado en los dos primeros niveles de escala de sofisticacion del
SEI: caos, y un poco mejor que el caos. Si usted cambia nada mas,
a
nadir pruebas automatizadas.
5.2

Simples pruebas de Python

Comprobacion de validez de una prueba rapida 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 )
#:
Solo tiene que ejecutar esto desde el directorio raz de los listados de codigo para el libro; ello descendera en cada subdirectorio
y ejecutar el programa all. Una forma sencilla de comprobar las
cosas es redirigir la salida estandar a un archivo, entonces, si hay
cualquier error seran la u
nica cosa que aparece en la consola durante
la ejecucion del programa.
5.3

Un framework muy simple

Como mencione, un objetivo primario de este codigo es hacer la


escritura de codigo de pruebas unitarias muy simple, incluso mas
simple que con JUnit. Como otras necesidades se descubren durante el uso de este sistema, a continuacion, que la funcionalidad se
puede a
nadir, pero para empezar con el marco solo va a proporcionar
36

una manera de crear y ejecutar facilmente y pruebas, e informar fracaso si algo se rompe (el exito no producira resultados distintos de
salida normal que puede ocurrir durante la ejecucion 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 ejecucion de un comando. El proceso de construccion consistira en la compilacion de
los programas y la ejecucion de pruebas unitarias, y si make recibe
a traves de todo el camino exitosamente, entonces el sistema sera
validado, de lo contrario, se anulara en el lugar de la falta, para que
pueda proporcionar cualquier granularidad que necesita escribiendo
el mayor n
umero de pruebas como quiera, cada una cubriendo tanto
o tan poco como usted encuentra necesario.
En alg
un sentido, este marco proporciona un lugar alternativo
para todas aquellas declaraciones de imprimir que he escrito y
posteriormente borrados a traves de los a
nos.
Para crear un conjunto de pruebas, usted comienza haciendo una
clase interna static dentro de la clase que desea probar (su codigo
de prueba tambien puede probar otras clases; usted decide). Este
codigo 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 nonmemory
# 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 u
nico metodo 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 metodo hace es verificar que algo es true.
Si no, a
nade 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 debera ver dentro de poco) ha fracasado. Aunque esto no es una gran cantidad de informacion es
posible que tambien desee tener el n
umero de lnea, lo que podra ser
extrado de una excepcion puede ser suficiente para la mayora
de las situaciones.
A diferencia de JUnit, (que usa los metodos setUp( ) y tearDown()), los objetos de prueba se construiran usando la construccion
ordinaria Python. Usted define los objetos de prueba mediante la
creacion de ellos como miembros de la clase ordinaria de la clase de
prueba, y un nuevo objeto de clase de prueba se creara para cada
metodo de ensayo (evitando as cualquier problema que pueda ocurrir debido a efectos secundarios entre las pruebas). Ocasionalmente,
la creacion de un objeto de prueba asignara recursos sin memoria,
en cuyo caso debe anular cleanup( ) para liberar esos recursos.

5.4

Escribir pruebas

Escribir pruebas llega a ser muy simple. Aqu esta 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
o a ser reservada en
el JDK 1.4 cuando se a
nadieron 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 metodo test3() esta comentado, porque, como vera, hace que
la acumulacion automatica de codigo fuente de arboles de este libro
se detuviera.
Usted puede nombrar su a su clase interna como quiera; el u
nico
factor importante es extends UnitTest. Tambien puede incluir
cualquier codigo de apoyo necesario en otros metodos. Solo los
metodos public que toman ning
un argumento y retorno void seran
tratados como pruebas (Los nombres de estos metodos no son tambien
limitados).
La clase de prueba anterior crea dos instancias de TestDemo.
El constructor TestDemo imprime algo, para que podamos ver que
esta siendo llamado. Usted podra tambien definir un constructor
por defecto (el u
nico tipo que es utilizado por el marco de prueba),
aunque ninguno es necesario aqu. La clase TestDemo tiene un
39

metodo close() que sugiere que se utiliza como parte de la limpieza


del objeto, as este es llamado en el metodo reemplazado cleanup()
en Test.
Los metodos de prueba utilizan el metodo affirm( ) para validar
expresiones, y si hay un fallo de la informacion se almacena y se imprime despues se ejecutan todas las pruebas. Claro, los argumentos
affirm() son usualmente mas complicados que este; usted vera mas
ejemplos a lo largo del resto de este libro.
Observe que en testB(), el campo private objCounter es accesible para el codigo de prueba esto es proque Test tiene los
permisos de una clase interna.
Se puede ver que escribir codigo de prueba requiere muy poco esfuerzo adicional, y ning
un conocimiento distinto del utilizado para
escribir las clases ordinarias.
Para ejecutar las pruebas, utilice RunUnitTests.py (que sera
presentado dentro de poco). El comando para el codigo anterior se
ve as:
java com.bruceeckel.test.RunUnitTests TestDemo
Esto produce el siguiente resultado:
t e s t 1 : count =
t e s t 2 : count =
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 =
t e s t 2 : count =
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

1
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

3
4

Todo el ruido de salida es tan lejos como el exito o el fracaso de


la unidad de pruebas se refiere. Solo si una o mas de la unidad de

40

pruebas fallan el programa devuelve un valor distinto de cero para


terminar el proceso de make despues 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 codigo de impresion que pueda
necesitar si usted hace esto, se tiende a mantener dicho codigo
alrededor en lugar de ponerlo dentro y despojarlo afuera como se
hace normalmente con codigo 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
codigo anterior, todas las afirmaciones son siempre verdaderas por
lo que las pruebas nunca fallaran.
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 codigo de prueba tiene un acceso completo a la


parte interna de la clase que esta siendo probado (por lo que podra
ser llamado mas apropiadamente las pruebas caja transparente).
Pruebas de caja blanca sucede automaticamente cuando usted hace
la clase de prueba de unidad como una clase interna de la clase que
esta probando, ya que las clases internas tienen automaticamente
acceso a todos sus elementos de clase exteriores, incluso los que son
private.
Una forma posiblemente mas com
un de la prueba es la blackbox testing : prueba de caja negra, que se refiere al tratamiento
de la clase que se esta probando como una caja impenetrable. No
se puede ver el funcionamiento interno; solo se puede acceder a las
partes public de la clase. As, las pruebas de caja negra corresponden mas estrechamente a las pruebas funcionales, para verificar
los metodos que el programador-cliente va a utilizar. En adicion,
las pruebas de caja negra proporcionan una hoja de instrucciones
mnimas para el programador-cliente en ausencia de toda otra documentacion, las pruebas de caja negra al menos demuestran como
hacer llamadas basicas a los metodos 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 demas reglas son las mismas (por
ejemplo, la clase de prueba de unidad debe ser public, y derivado
de UnitTest).
Hay otra advertencia, que tambien proporcionara un peque
no
repaso de los paquetes Java. Si usted quiere ser completamente riguroso, debe poner su clase de prueba de caja negra en un directorio
independiente de la clase puesta a prueba, de lo contrario, tendra
acceso paquete a los elementos de la clase siendo probada. Es decir,
usted sera capaz de acceder a los elementos protected y friendly
de la clase siendo probada. Aqui esta 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 u
nico metodo que podra ser accesible directamente para el programador-cliente es f4(). Sin embargo, si usted
pone su prueba de caja negra en el mismo directorio, automaticamente
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 paquete 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 tambien es posible importar una clase paquete-menos,
dando el nombre de clase en la declaracion import y asegurando
que la clase esta 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 esta la prueba de la caja-negra en su propio paquete, mostrando
como solamente los metodos p
ublicos 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 similar al que el programador-cliente escribira para utilizar su clase,
incluyendo las importaciones y metodos disponibles. De modo que
hace que un buen ejemplo de programacion. Claro, es mas facil
desde el punto de vista de codificacion para simplemente hacer una
clase interna, y a menos que sea apasionado sobre la necesidad especfica de pruebas de caja negra es posible que solo quiera seguir
adelante y utilizar las clases internas (con el conocimiento que si
usted necesita que mas tarde puede extraer las clases internas en
clases de prueba de caja negra separadas, sin demasiado esfuerzo).
5.6

Ejecuci
on de Pruebas

El programa que ejecuta las pruebas hace un uso significativo de reflexion 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 )
#
#
#
if

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 .
( 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


aticamente

5.8

Ejercicios

1. Instalar el codigo fuente arbol de este libro y asegurar que usted


tenga una utilidad make instalada en su sistema.
2. Modificar TestDemo.java mediante la adicion de una nueva
prueba que produzca una excepcion. Escriba make y observar los
resultados.
3. Modifique sus soluciones a los ejercicios en el captulo 1,
a
nadiendo las pruebas unitarias. Escribe makefiles que incorporen
las pruebas unitarias.

3: Entornos de aplicaciones de construcci


on

Un entorno de aplicacion le permite heredar de una clase o conjunto


de clases y crear una nueva aplicacion, reutilizando la mayor parte
del codigo en las clases existentes y anular uno o mas metodos con
el fin de personalizar la aplicacion a sus necesidades. Un concepto
fundamental en el entorno de aplicacion es el Template Method :
Metodo Plantilla el que normalmente se oculta debajo de las cubiertas e impulsa la aplicacion llamando a los diversos metodos en la
clase base (algunos de los cuales usted ha anulado con el fin de crear
la aplicacion).
Por ejemplo, cuando se crea un applet esta utilizando un entorno de aplicacion: hereda de JApplet y y luego anular init().
El mecanismo applet (que es un metodo plantilla) se encarga del
resto mediante la elaboracion de la pantalla, el manejo del ciclo de
eventos, cambiar el tama
no, etc.
6.1

M
etodo Plantilla

Una caracterstica importante del Metodo Plantilla es que esta definido


en la clase base y no puede ser cambiado. Este algunas veces es un
metodo Privado pero este es siempre practicamente final. Llama
otros metodos de la clase base (los que usted anula) con el fin de
48

hacer su trabajo, pero se le suele llamar solo como parte de un


proceso de inicializacion (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 inicializacion necesaria y despues de iniciar el motor (el metodo plantilla) que ejecuta la aplicacion (en una aplicacion GUI, este motor sera el bucle principal del evento). El programador cliente
simplemente proporciona definiciones para customize1() y customize2() y la aplicacion esta listo para funcionar.
Veremos Template Method : Metodo 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 lnea de comandos. Este abre cada archivo, excepto el u
ltimo
para la lectura, y el u
ltimo para la escritura. El entorno procesara
49

cada archivo de entrada utilizando una poltica indeterminada y


escribir la salida al u
ltimo archivo. Heredar para personalizar
este entorno para crear dos aplicaciones separadas:
1) Convierte todas las letras en cada archivo a may
usculas.
2) Busca los archivos de las palabras dadas en el primer archivo.

4: Al frente de una implementaci


on

Tanto Proxy y State : Estado proporcionan una clase sustituta


que se utiliza en el codigo; la clase real que hace el trabajo se esconde detras de esta clase sustituta. Cuando llama un metodo en
el surrogate : sustituto, este simplemente gira y llama al metodo
en la implementacion de la clase. Estos dos patrones son tan similares que el Proxy es simplemente un caso especial de State. Uno
es probado a solo agrupar a los dos juntos en un patron llamado
Surrogate : sustituto, pero el termino proxy tiene un significado
antiguo y especializado, que probablemente explica la razon de los
dos patrones diferentes.
La idea basica es simple: de una clase base, el sustituto se deriva
junto con la clase o clases que proporcionan la implementacion real:

Cuando se crea un objeto sustituto, se da una implementacion a


la cual enviar todas las llamadas a metodos.
Estructuralmente, la diferencia entre Proxy y State es simple: un
Proxy tiene una sola implementacion, mientras State tiene mas de
uno. La aplicacion de los patrones se considera (en Design Patterns
50

: Patrones de Dise
no) que es distinta: Proxy es usado para controlar
el acceso a esta implementacion, mientras State le permite cambiar
la implementacion de forma dinamica. Sin embargo, si expande su
nocion de controlando el acceso a la implementacion, 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 speaking for : Hablar por la clase que esta refiriendose al metodo llama
a continuacion, la idea basica es satisfecha (tenga en cuenta que esta
declaracion esta en contradiccion con la definicion de Proxy en GoF).
Sin embargo, es conveniente tener una interfaz com
un para que Implementation se vea obligado a cumplir con todos los metodos que

51

Proxy necesita llamar.


Por supuesto, en Python tenemos un mecanismo de delegacion
integrado, lo que hace que el Proxy a
un mas 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 utilizacion de getattr ( ) es que Proxy2 es
completamente generico, y no vinculada a cualquier implementacion
particular (en Java, un proxy dinamico bastante complicado ha
sido inventado para lograr esto mismo).

52

7.2

State : Estado

El patron State a
nade mas implementaciones a Proxy, junto con una
manera de cambiar de una implementacion 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 implementacion se usa para una
parte, a continuacion, la segunda implementacion se intercambia y
ese es utilizado.
La diferencia entre Proxy y State esta en los problemas que se
resuelven. Los usos comunes para Proxy como se describe en Design
Patterns : patrones de dise
no son:
1. Proxy remoto. Este proxy para un objeto en un espacio de
direccion diferente. Se crea un proxy remoto de forma automatica
por el compilador RMI rmic ya que crea stubs y esqueletos.
2. Proxy virtual.Esto proporciona inicializacion relajada para
crear objetos costosos en la demanda.
3. Proxy de Protecci
on. Se usa cuando no se desea que el programador 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
umero 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
umero de llamadas a un metodo
en particular.
Usted podra mirar a una referencia de Python como un tipo de
proxy de proteccion, ya que controla el acceso al objeto real en el
monton (y asegura, por ejemplo, que no utilice una referencia nula).
[[reescribir esto: en Design Patterns : Dise
no 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 particular, utiliza una jerarqua de implementacion separada pero esto

54

me parece innecesario a menos que usted haya decidido que la implementacion no esta bajo su control (ciertamente una posibilidad, pero
si usted es due
no de todo el codigo no parece haber ninguna razon
para no beneficiarse de la elegancia y amabilidad de la clase base individual). En adicion, Proxy no necesita utilizar la misma clase base
para su implementacion, siempre y cuando el objeto proxy esta controlando acceso al objetarlo frente a favor. Independientemente
de los detalles, en ambos Proxy y State un sustituto esta pasando
la llamada al metodo a traves de un objeto de implementacion.]]

55

7.3

StateMachine

Mientras State : Estado tiene una manera de permitir que el programador cliente cambie la implementacion, StateMachine impone
una estructura para cambiar automaticamente la implementacion
de un objeto al siguiente. La implementacion actual representa el
estado en que un sistema esta, y el sistema se comporta de manera
diferente de un estado a otro (ya que utiliza State). Basicamente,
esta es una state machine : maquina de estados usando objetos.
El codigo que mueve el sistema de un estado a otro es a menudo
un Template Method : Metodo Plantilla, como se ve en el siguiente
entorno para una maquina basica estatal.
Cada estado puede ser run( ) para cumplir con su comportamiento, y (en este dise
no) tambien puede pasarlo a un objeto de
entrada por lo que le puede decir que nuevo estado para avanzar
basado en eso de entrada. La distincion clave entre este dise
no y
el siguiente es que aqu, cada objeto State decide lo que otros estados pueden avanzar , basado en la input : entrada, mientras que
en el posterior dise
no 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
na tabla State, y en el dise
no
posterior hay una sola tabla directora de transicion 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 permite decir que algo es un objeto State en el codigo, y proporcionar
un mensaje de error ligeramente diferente cuando no se implemen56

tan todos los metodos. Podramos haber conseguido basicamente el


mismo efecto diciendo:
c l a s s State : pass
Porque todava conseguiramos excepciones si run o next() fueran
llamados por un tipo derivado, y no haban sido implementados.
El StateMachine hace un seguimiento de la situacion actual, el
cual es inicializado por el constructor. El metodo runAll() toma
una lista de objetos Input. Este metodo no solo avazna al siguiente
estado, sino que tambien llama run( ) para cada objeto state : estado por lo tanto se puede ver que es una expansion de la idea del
patron State, ya que run( ) hace algo diferente dependiendo del
estado en que el sistema esta.
#: 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 ( )
#:
Tambien he tratado runAll( ) como un metodo plantilla. Esto
es tpico, pero ciertamente no es necesario posiblemente podra
querer anularlo, pero por lo general el cambio de comportamiento
se producira en State de run( ) en su lugar
En este punto se ha completado el marco basico para este estilo de StateMachine (donde cada estado decide los proximos estados). Como ejemplo, voy a utilizar una ratonera de fantasa que
57

puede moverse a traves de varios estados en el proceso de atrapar


un raton14 . Las clases raton y la informacion se almacenan en el
paquete mouse, incluyendo una clase en representacion de todas
los posibles movimientos que un raton puede hacer, que seran los
entradas a la state machine: maquina 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 observara que cmp ( ) se ha reemplazado para implementar una comparacion entre los valores de accion. Tambien, cada
posible jugada de un raton se enumera como un objeto de MouseAction, todos los cuales son los campos estaticos en MouseAction.
Para la creacion de codigo de prueba, una secuencia de entradas
de mouse esta provisto de un archivo de texto:
# :! c04 : mouse : MouseMoves . t x t
14 Ning
un

rat
on fue perjudicado en la creaci
on de este ejemplo.

58

mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
mouse
#:

appears
runs away
appears
enters trap
escapes
appears
enters trap
trapped
removed
appears
runs away
appears
enters trap
trapped
removed

Con estas herramientas en su lugar, ahora es posible crear la


primera version del programa mousetrap : ratonera. Cada subclase
State define su comportamiento run( ) y tambien establece su siguiente estado con una clausula 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 estaticos, y tambien establece el estado inicial. UnitTest crea un MouseTrap y luego prueba con todas las
entradas de un MouseMoveList.
Mientras el uso de las sentencias if dentro de los metodos next(
) es perfectamente razonable, la gestion de un gran n
umero de ellos
podra llegar a ser difcil. Otro enfoque es crear tablas dentro de
cada objeto State definiendo los diversos estados proximos basados
en la entrada.
Inicialmente, esto parece que debera ser bastante simple. Usted
debe ser capaz de definir una tabla estatica en cada subclase State
que define las transiciones en terminos de los otros objetos State.
Sin embargo, resulta que este enfoque genera dependencias de inicializacion cclicas. Para resolver el problema, He tenido que retrasar
la inicializacion de las tablas hasta la primera vez que se llama al
metodo next( ) para un objeto en particular State. Inicialmente,
los metodos next()
La clase StateT es una implementacion de State ((de modo que
la misma clase StateMachine puede ser utilizado en el ejemplo anterior) que a
nade un Map y un metodo para inicializar el mapa a
partir de una matriz de dos dimensiones. El metodo next() tiene
una implementacion de la clase base que debe ser llamado desde el
metodo next() clase derivada anulada, despues 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 codigo es identico la diferencia esta en los metodos
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 mas facil de leer de
forma rapida y comprender las transiciones de estado de mirar la
tabla.

63

7.4

Table-Driven State Machine

La ventaja del dise


no anterior es que toda la informacion acerca de
un estado, incluyendo la informacion de transicion de estado, se encuentra dentro de la clase propio Estado. Esto es generalmente un
buen principio de dise
no.
Sin embargo, en una state machine : maquina de estados pura,
la maquina puede ser completamente representada por una u
nica
tabla de transicion de estados. Esto tiene la ventaja de localizar
toda la informacion sobre la maquina 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 transicion de estados clasica.
El diagrama clasico de transicion-de-estados utiliza un crculo
para representar cada estado, y las lneas del state se
nalando a todos
los Estados en que state puede trasladarse. Cada lnea de transicion
se anota con condiciones para la transicion y una accion durante la
transicion. Aqu esta lo que parece:
(Diagrama State Machine simple)
Objetivos:
Traduccion directa del diagrama de estado
Vector del cambio: la representacion diagrama de estado
Implementacion Razonable
No hay exceso de estados (usted podra representar a cada cambio individual con un nuevo estado)
La simplicidad y la flexibilidad
Observaciones:
Estados son triviales ninguna informacion o funciones / datos,
solo una identidad.
No como el patron State!
La maquina 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 accion tambien deben ser externos a
los estados
Centralizar la descripcion en una sola tabla que contiene todas
las variaciones, para facilitar la configuracion.
Ejemplo:
State Machine y Table-Driven Code
Implementa una maquina expendedora
Utiliza varios, otros patrones
Separa codigo com
un state-machine de aplicacion especfica
(como metodo de plantilla)
Cada entrada causa buscar una solucion apropiada (como cadena de responsabilidad)
Pruebas y transiciones se encapsulan en objetos de funcion (objetos que contienen funciones)
Restriccion de Java: los metodos 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 realidad solo un marcador de posicion 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
s t r ( s e l f ) : r e t u r n s e l f . name
def
# :
7.4.2

Condiciones para la transici


on

En el diagrama de transicion de estados, una entrada se pone a


prueba para ver si satisface las condiciones necesarias para transferir al Estado bajo cuestion. Como antes, el Input es solo 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
ua el Input para decidir si esta fila en la tabla
es la transicion 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
on

Si Condition devuelve true, entonces se hace la transicion a un


nuevo estado, y como se hace esa transicion alg
un tipo de accion
se produce (en el dise
no anterior de state machine : maquina de
estado, este era el metodorun( )).
# 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 dimensiones, donde cada fila describe completamente un estado. El
primer elemento en la fila es el estado actual, y el resto de los elementos son cada uno una fila indicando lo que el tipo de la entrada
puede ser, la condicion que debe ser satisfecha para que este cambio
67

de estado a ser la correcta, la accion que ocurre durante la transicion,


y el nuevo estado para moverse dentro. Observe que el objeto Input
no solo se utiliza para su tipo, tambien es un objeto Messenger que
lleva la informacion 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
aquina b
asica

# 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
aquina 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 ,
F i r s t D i g i t . B = F i r s t D i g i t ( B ,
F i r s t D i g i t .C = F i r s t D i g i t ( C ,
F i r s t D i g i t .D = F i r s t D i g i t ( D ,

0)
1)
2)
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
i n i t ( s e l f , price , quantity ) :
def
s e l f . price = price
70

s e l f . quantity = quantity
s t r ( s e l f ) : r e t u r n I t e m S l o t . id
def
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
aquina

# 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 : maquina de estado se hace


mas grande, es el uso de una herramienta de automatizacion mediante el cual configura una tabla y deja que la herramienta genere el
codigo state machine para usted. Esto puede ser creado por s mismo
utilizando un lenguaje como Python, pero tambien hay, herramientas libres de codigo 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
umero de llamadas a metodos a un objeto en
particular.
3. Crear un programa similar a ciertos sistemas DBMS que solo
permiten un cierto n
umero de conexiones en cualquier momento.
Para implementar esto, utilizar un sistema de singleton al igual que
controla el n
umero de objetos conexion que crea. Cuando un
usuario ha terminado con una conexion, el sistema debe ser informado de manera que pueda comprobar que la conexion volvera a
ser reutilizado. Para garantizar esto, proporcionar un objeto proxy
en lugar de una referencia a la conexion real, y dise
nar el proxy de
74

manera que hara que la conexion para ser liberado de nuevo al sistema.
4. Usando State, hacer una clase llamada UnpredictablePerson que cambia el tipo de respuesta a su metodo hello( ) dependiendo de que tipo de Mood esta dentro. A
nadir un tipo adicional
de Mood llamado Prozac.
5. Crear una implementacion 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 informacion de entrada determina el siguiente
estado en que el sistema estara. 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 metodo
nextState( ) que tiene su propia tabla de transicion de estados.
The input to nextState( ) debe ser una sola palabra que sale de
un archivo de texto que contiene una palabra por lnea.
8. Modificar el ejercicio anterior para que la state machine pueda
ser configurada mediante la creacion / modificacion de una sola matriz multidimensional.
9- Modificar el ejercicio mood de la sesion anterior para que
se convierta en una state machine : maquina de estado usando
StateMachine.java
10. Crear un sistema elevador de state machine utilizando StateMachine.java
11. Crear un sistema de calefaccion / aire acondicionado usando
StateMachine.java
12. Un generator : generador es un objeto que produce otros ob75

jetos, al igual que una fabrica, excepto que la funcion generador no


requiere ning
un argumento. Cree un MouseMoveGenerator que
produce acciones correctas MouseMove como salidas cada vez que
la funcion 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 metodo iterator( ) para producir un iterador, pero este metodo
debe tomar un argumento int que especifica el n
umero de movimientos a producir antes de hasNext( ) que retorna false.

X: Decoradores:
Selecci
on Tipo din
amico

El uso de objetos en capas para a


nadir de forma din
amica
y transparente responsabilidades a los objetos individuales
se conoce como el patr
on decorator:decorador.
Se utiliza cuando la subclasificacion crea demasiadas (o inflexibles) clases.
Todos los decoradores que envuelven alrededor del objeto original
deben tener la misma interfaz basica.
Dynamic proxy/surrogate? : Proxy / sustituto Dinamico?
Esto explica la estructura de herencia singular.
Tradeoff: la codificacion es mas complicado cuando se utiliza
decoradores.

76

8.1

Estructura Decorador basico

8.2

Un ejemplo caf
e

Considere la posibilidad de bajar a la cafetera local, BeanMeUp,


por un cafe. Nor11malmente hay muchas bebidas diferentes que
se ofrecen expresos, cafes con leche, tes, cafes helados, chocolate
caliente para nombrar unos pocos, as como una serie de extras (que
cuestan extra tambien), tales como la crema batida o una inyeccion
extra de expreso. Usted tambien puede hacer ciertos cambios en su
bebida, sin costo adicional, como pedir cafe descafeinado en lugar
de cafe regular.
Con bastante claridad si vamos a modelar todas estas bebidas y
combinaciones, habra diagramas de clases de tama
no variable. As
que para mayor claridad nosotros solo consideraremos un subconjunto de los cafes: Expreso, cafe vienes, Caffe Latte, Cappuccino y
Cafe Mocha. Incluiremos 2 extras - crema batida (batida) y una
inyeccion extra de cafe expreso; y tres cambios - descafeinado, leche
al vapor (h
umeda) y espuma de leche (seco).

8.3

Clase para cada combinaci


on

Una solucion es crear una clase individual para cada combinacion.Cada


clase describe la bebida y es responsable por el costo, etc. El men
u
77

resultante es enorme, y una parte del diagrama de clases sera algo


como esto:

Aqui esta una de las combinaciones, una implementacion 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 metodo es encontrar la combinacion
particular que desea. As, una vez que haya encontrado la bebida
que le gustara, aqu es como usted lo utilizara, como se muestra en
la clase CoffeeShop en el siguiente codigo:
#: 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
class
class
class
class
class
class

CappuccinoDecaf : p a s s
CappuccinoDecafWhipped : p a s s
CappuccinoDry : p a s s
CappuccinoDryWhipped : p a s s
CappuccinoExtraEspresso : p a s s
CappuccinoExtraEspressoWhipped : p a s s
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
class
class
class
class

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

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

class
class
class
class
class

CafeLatteDecafWhipped : p a s s
CafeLatteExtraEspresso : pass
CafeLatteExtraEspressoWhipped : p a s s
CafeLatteWet : p a s s
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 esta la salida correspondiente:
Cappucino : \ $1 . 0 Cafe Mocha d e c a f whipped cream : \ $1 . 2 5
Se puede ver que la creacion de la combinacion particular que
desea es facil, ya que solo esta creando una instancia de una clase.
Sin embargo, hay una serie de problemas con este enfoque. En
primer lugar, las combinaciones son fijadas estaticamente para que
cualquier combinacion de un cliente quiza desee ordenar necesite ser
creado por adelantado. En segundo lugar, el men
u resultante es
tan grande que la b
usqueda de su combinacion particular es difcil
y consume mucho tiempo.

8.4

El enfoque decorador

Otro enfoque sera descomponer las bebidas en los diversos componentes, tales como expreso y leche espumada, y luego dejar que el
cliente combine los componentes para describir un cafe en particular.
Con el fin de hacer esto mediante programacion, utilizamos el
patron Decorador. Un decorador a
nade la responsabilidad de un
componente envolviendolo, pero el decorador se ajusta a la interfaz
del componente que encierra, por lo que la envoltura es transparente. Los Decoradores tambien se pueden anidar sin la perdida de
80

esta transparencia.

Metodos invocados en el Decorador a su vez pueden invocar


metodos en el componente, y puede realizar, por supuesto, el procesamiento antes o despues de la invocacion.
As que si a
nadimos los metodos getTotalCost() y getDescription() 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 siguiente manera, como se muestra en el siguiente codigo:
#: 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
u mas peque
no. Usted tiene un peque
no n
umero de componentes para elegir, pero el montaje de la descripcion del cafe entonces 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 Cafe Mocha descafeinado con crema batida requiere
una descripcion a
un mas larga.

8.5

Compromiso

El enfoque anterior toma demasiado tiempo para describir un cafe.


Tambien habra ciertas combinaciones que va a describir con regularidad, y sera conveniente tener una forma rapida 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 creacion de un men
u de tama
no razonable de opciones basicas, que a menudo funcionan exactamente como son, pero
83

si quera decorarlos (crema batida, descafeinado etc), entonces usted


usara decoradores para hacer las modificaciones. Este es el tipo de
men
u que se le presenta en la mayora de tiendas de cafe.

Aqu esta como crear una seleccion basica, as como una seleccion
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
i n i t ( s e l f , drinkComponent ) :
def
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
i n i t ( s e l f , drinkComponent ) :
def
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 seleccion basica es rapido y
facil, lo cual tiene sentido ya que seran descritos con regularidad.
Describiendo una bebida decorada es mas trabajo que cuando se
utiliza una clase por combinacion, pero claramente menos trabajo
que cuando solo usando decoradores.
El resultado final no es demasiadas clases, ni tampoco demasiados decoradores. La mayora de las veces es posible alejarse sin
utilizar ning
un decorador en absoluto, as tenemos los beneficios de
ambos enfoques.

8.6

Otras consideraciones

Que sucede si decidimos cambiar el men


u en una etapa posterior, tal como mediante la adicion de un nuevo tipo de bebida? Si
hubieramos utilizado la clase por enfoque de combinacion, el efecto
de la adicion de un ejemplo adicional como Syrup sera un crecimiento exponencial en el n
umero de clases. Sin embargo, las implicaciones para todos los enfoques decorador o de compromiso son los
mismos Se crea una clase extra.
Que tal el efecto de cambiar el costo de la leche al vapor y espuma de leche, cuando el precio de la leche sube? Teniendo una
clase para cada combinacion significa que usted necesita cambiar un
metodo en cada clase, y as mantener muchas clases. Mediante el uso
de decoradores, el mantenimiento se reduce mediante la definicion
de la logica en un solo lugar.

86

8.7

Ejercicios

1. A
nadir una clase Syrup al enfoque decorador descrito anteriormente. A continuacion, cree un Cafe Latte (usted necesitara usar
la leche al vapor con un expreso) con Syrup.
2. Repita el ejercicio 1 para el enfoque de compromiso.
3. Implementar el patron decorador para crear un restaurante de
Pizza, el cual tenga un men
u de opciones, as como la opcion de
dise
nar su propia pizza. Siga el enfoque de compromiso para crear
un men
u 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

Y: Iteradores:
Algoritmos de desacoplamiento de contenedores

Este captulo no ha tenido traduccion significativa todava.


Alexander Stepanov penso durante a
nos sobre el problema de las
tecnicas de programacion genericas antes de crear el STL (junto con
Dave Musser). Llego a la conclusion de que todos los algoritmos
estan definidos en las estructuras algebraicas lo que llamaramos
contenedores.
En el proceso, el se dio cuenta que los iteradores son fundamentales para el uso de algoritmos, porque desacoplan los algoritmos
del tipo especifico de contenedor con que el algoritmo actualmente
podra estar trabajando. Esto significa que usted puede describir
el algoritmo sin preocuparse de la secuencia particular en que esta
operando. Mas generalmente, cualquier codigo que usted escribe
utilizando iteradores es desacoplado de la estructura de datos que
el codigo esta manipulando, y por lo tanto su codigo es mas general
y reutilizable.
El uso de iteradores tambien apla su codigo en el campo de programacion funcional, cuyo objetivo es describir lo que un programa
esta haciendo a cada paso en lugar de como lo esta haciendo. Es
decir, usted dice usted dice tipo en lugar de describir el tipo. El
objetivo del STL de C ++ fue proporcionar este enfoque generico
para la programacion C ++ (como este enfoque exitoso sera en realidad, a
un esta por verse).
Si ha utilizado contenedores en Java (y es difcil escribir codigo
sin usarlos), usted tiene iteradores usados en la forma del Enumeration 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
Captulo 9, Holding Your Objects : Manteniendo Sus objetos, bajo
Iterators in Thinking in Java segunda edicion (descargable gratuitamente 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

tecnicas de programacion genericas / funcionales. Este captulo explorara estas tecnicas mediante la conversion de los algoritmos de
STL para Java, para su uso con la librera de contenedor Java 2.

9.1

Iteradores con seguridad de tipos

En Thinking in Java, segunda edicion, Muestro la creacion de un


contenedor de tipo seguro que solo aceptara un tipo particular de
objeto. Un lector, Linda Pazzaglia, pidio el otro componente de
tipo seguro obvio, un iterador que trabajara con los contenedores
basicos java.util, pero imponer la restriccion de que el tipo de objetos sobre los que itera sea de un tipo particular.
Si Java siempre incluye un mecanismo de plantilla, este tipo de
iterador tendra la ventaja a
nadida de ser capaz de devolver un tipo
especfico de objeto, pero sin las plantillas se ve obligado a retornar
Objects genericos, o requerir un poco de codificacion manual para
cada tipo que desea iterar. Tomare el enfoque anterior.
Una segunda decision de dise
no 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 problematico
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 resultados de una iteracion a la siguiente. El enfoque seguro es exigir al
usuario establecer el tipo durante la construccion del iterador.
Por u
ltimo, como construir el iterador? No podemos reescribir
las libreras de clases Java existentes que ya producen Enumerations y Iterators. Sin embargo, podemos utilizar el patron de
dise
no Decorator, y crear una clase que simplemente envuelve el
Enumeration o Iterator que se produce, generando un nuevo objeto que tiene el comportamiento de iteracion 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 patron Proxy,
pero es mas probable Decorator debido a su intencion). Aqu esta
el codigo:
# 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
abricas:
encapsular
la creaci
on de objetos

Cuando descubre que es necesario agregar nuevos tipos a un sistema,


el primer paso mas sensato es utilizar el polimorfismo para crear una
interfaz com
un a esos nuevos tipos. Esto separa el resto del codigo
en el sistema desde el conocimiento de los tipos especficos que esta
agregando. Nuevos tipos pueden a
nadirse sin molestar codigo existente ... o al menos eso parece. Al principio parecera que el u
nico
lugar que necesita cambiar el codigo en tal dise
no es el lugar donde
90

usted hereda un nuevo tipo, pero esto no es del todo cierto. Usted
todava debe crear un objeto de su nuevo tipo, y en el punto de
la creacion debe especificar el constructor exacto a utilizar. As, si
el codigo que crea objetos se distribuye a traves de su aplicacion,
usted tiene el mismo problema cuando a
nade nuevos tipos usted
todava debe perseguir todos los puntos de su codigo en asuntos de
tipos. Esto sucede para ser la creation : creacion del tipo que importa en este caso en lugar del use : uso del tipo (que es atendido
por el polimorfismo), pero el efecto es el mismo : la adicion de un
nuevo tipo puede causar problemas.
La solucion es forzar la creacion de objetos que se produzca a
traves de una factory : fabrica com
un antes que permitir que el
codigo creacional que se extendio por todo el sistema. Si todo el
codigo en su programa debe ir a traves de esta fabrica cada vez que
necesita crear uno de sus objetos, entonces todo lo que debe hacer
cuando a
nada un nuevo objeto es modificar la fabrica.
Ya que cada programa orientado a objetos crea objetos, y puesto
que es muy probable que se extienda su programa mediante la
adicion de nuevos tipos, sospecho que las fabricas pueden ser los
tipos mas universalmente u
tiles de los patrones de dise
no.

10.1

Simple m
etodo de f
abrica

Como ejemplo, vamos a revisar el sistema Shape. Un enfoque es


hacer la fabrica de un metodo 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 que
tipo de Shape para crear; que pasa a ser un String en este caso,
pero podra ser cualquier conjunto de datos. factory( ) es ahora
el u
nico otro codigo en el sistema que necesita ser cambiado cuando
untipo nuevo de Shape es agregado (los datos de inicializacion de los
objetos presumiblemente vendran de alguna parte fuera del sistema,
y no son una matriz de codificacion fija como en el ejemplo anterior).
Tenga en cuenta que este ejemplo tambien muestra el nuevo
Python 2.2 staticmethod( ) tecnica para crear metodos estaticos
en una clase.
Tambien he utilizado una herramienta que es nueva en Python
2.2 llamada un generator : generador. Un generador es un caso espe92

cial de una fabrica: es una fabrica que no toma ning


un argumento
con el fin de crear un nuevo objeto. Normalmente usted entrega
alguna informacion a una fabrica con el fin de decirle que tipo de
objeto para crear y como crearlo,pero generador tiene alg
un tipo de
algoritmo interno que le dice que y como construir. Esto genera de
la nada en vez de estar diciendo que crear.
Ahora, esto puede no parecer consistente con el codigo que usted
ve arriba:
f o r i i n shapeNameGen ( 7 )
parece que hay una inicializacion teniendo lugar. Aqu es donde
un generador es un poco extra
no cuando llama una funcion que
contiene una declaracion yield (yield es una nueva palabra clave
que determina que una funcion es un generador), esa funcion en
realidad devuelve un objeto generador que tiene un iterador. Este
iterador se utiliza implcitamente en la sentencia for anterior, por
lo que parece que se esta iterando a traves de la funcion generador,
no lo que devuelve. Esto se hizo para la conveniencia de uso.
Por lo tanto, el codigo que usted escribe es en realidad una especie
de fabrica, que crea los objetos generadores que hacen la generacion
real. Usted puede utilizar el generador de forma explcita si quiere,
por ejemplo:
gen = shapeNameGen ( 7 )
p r i n t gen . next ( )
As que next() es el metodo iterador que es realmente llamado
a generar el siguiente objeto, y que no toma ning
un argumento.
shapeNameGen( ) es la fabrica, y gen es el generador.
En el interior del generador de fabrica, 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 solo
funciona para el primer nivel de herencia de Item, as que si usted
fuera a heredar una nueva clase de Circle, no aparecera en la lista
generada por subclasses ( ). Si necesita crear una jerarqua mas
93

profunda de esta manera, debe recurse16 la lista

subclasses ( ).

Tambien tenga en cuenta que, en shapeNameGen( ) la declaracion


t y p e s = Shape .

subclasses

()

Solo se ejecuta cuando se produce el objeto generador; cada vez


que se llama al metodo next( ) de este objeto generador (que, como
se se
nalo anteriormente, puede suceder de manera implcita), solo
se ejecuta el codigo en el bucle for, por lo que no tiene ejecucion
derrochadora (como lo hara si esto fuera una funcion ordinaria).

10.2

F
abricas polim
orficas

El metodo estatico factory( ) en el ejemplo anterior obliga a todas


las operaciones de creacion que se centran en un solo lugar, as que
es el u
nico lugar que necesita cambiar el codigo. Esto es ciertamente
una solucion razonable, ya que arroja un cuadro alrededor del proceso de creacion de objetos. Sin embargo, el libro Design Patterns
enfatiza en que la razon para el patron de Factory Method es para
que diferentes tipos de fabricas pueden ser subclases de la fabrica
basica (el dise
no anterior se menciona como un caso especial). Sin
embargo, el libro no proporciona un ejemplo, pero en su lugar justamente repite el ejemplo utilizado para el Abstract Factory (usted
vera un ejemplo de esto en la siguiente seccion). Aqu ShapeFactory1.py esta modificado por lo que los metodos de fabrica estan
en una clase separada como funciones virtuales. Observe tambien
que las clases especficas de Shape se cargan dinamicamente 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
on en la programaci
on, usar funciones recursivas (que se repiten) en la
creaci
on 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 metodo de fabrica aparece en su propia clase, ShapeFactory, como el metodo create( ). Los diferentes tipos de formas
deben crear cada uno su propia fabrica con un metodo create(
) para crear un objeto de su propio tipo. La creacion real de formas se realiza llamando ShapeFactory.createShape( ), que es un
metodo estatico que utiliza el diccionario en ShapeFactory para
encontrar el objeto de fabrica apropiado basado en un identificador
que se le pasa. La fabrica se utiliza de inmediato para crear el objeto
shape : forma, pero se puede imaginar un problema mas complejo
donde se devuelve el objeto de fabrica apropiado y luego utilizado
por la persona que llama para crear un objeto de una manera mas
sofisticada. Ahora bien, parece que la mayor parte del tiempo usted
no necesita la complejidad del metodo de fabrica polimorfico, y un
solo metodo estatico en la clase base (como se muestra en ShapeFactory1.py) funcionara bien. Observe que ShapeFactory debe
ser inicializado por la carga de su diccionario con objetos de fabrica,
que tiene lugar en la clausula de inicializacion estatica de cada una
de las implementaciones de forma.

10.3

F
abricas abstractas

El patron Abstract Factory : Fabrica abstracta se parece a los objetos de fabrica que hemos visto anteriormente, con no uno, sino
varios metodos de fabrica. Cada uno de los metodos de fabrica crea
un tipo diferente de objeto. La idea es que en el punto de la creacion
del objeto de fabrica, usted decide como se usaran todos los objetos creados por esa fabrica. El ejemplo dado en Design Patterns
implementa portabilidad a traves de diferentes interfaces graficas
de usuario (GUI): crea un objeto de fabrica apropiada a la interfaz grafica de usuario que se esta trabajando, ya partir de entonces
cuando se pregunta por un men
u, boton, control deslizante, etc. se
creara automaticamente la version adecuada de ese tema para la
interfaz grafica de usuario. De esta manera usted es capaz de aislar, en un solo lugar, el efecto de cambiar de una interfaz grafica de
usuario a otra.
Como otro ejemplo, supongamos que usted esta creando un en96

torno de juego de uso general y usted quiere ser capaz de soportar


diferentes tipos de juegos. As es como puede parecer utilizando una
fabrica 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
class
def
def

A b s t r a c t Factory :
GameElementFactory :
makePlayer ( s e l f ) : p a s s
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
uan con los objetos
Obstacle pero hay diferentes tipos de jugadores y obstaculos, dependiendo de que tipo de juego esta jugando. Usted determina el
tipo de juego al elegir un determinado GameElementFactory, y
luego el GameEnvironment controla la configuracion y el desarrollo del juego. En este ejemplo, la configuracion 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
esta dise
nado para ser heredado, aunque podra muy posiblemente
tener sentido hacer eso.
Esto tambien contiene ejemplos de Double Dispatching : Despacho doble y el Factory Method : Metodo de fabrica, ambos de los
cuales se explicaran mas adelante.
Claro, la plataforma anterior deObstacle, Player y GameElementFactory (que fue traducido de la version Java de este ejemplo)
es innecesaria que solo es requerido para lenguajess que tienen
comprobacion de tipos estaticos. Siempre y cuando las clases de
98

Python concretas siguen la forma de las clases obligatorias, no necesitamos 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 :
i n i t ( self , factory ):
def
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 implementacion; ya que Python hace su la comprobacion de tipo en tiempo de ejecucion, 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 comparacion,
sin embargo. La primera de ellas agrega suficiente informacion
u
til sobre el patron que vale la pena mantener alg
un 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 continuacion, la herencia solo 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 GnomesAndFairies a GameEnvironment.py
4. Modificar ShapeFactory2.py para que utilice una Abstract Factory para crear diferentes conjuntos de formas (por ejemplo, un tipo
particular de objeto de fabrica crea formas gruesas, otra crea formas delgadas, pero cada objeto fabrica puede crear todas las formas: crculos, cuadrados, triangulos, etc.).

100

11

6 : Objetos de funci
on

En Advanced C++:Programming Styles And Idioms (Addison-Wesley,


1992), Jim Coplien acu
na el termino functor que es un objeto cuyo
u
nico proposito es encapsular una funcion (ya que Functor tiene
un significado en matematicas, Voy a utilizar el termino mas explcito
function object). El punto es desacoplar la eleccion de la funcion que
se llamara desde el sitio en que esa funcion se llama.
Este termino se menciona pero no se utiliza en Design Patterns.
Sin embargo, el tema del objeto de funcion se repite en una serie de
patrones en ese libro.

11.1

Comando: la elecci
on de la operaci
on en tiempo de
ejecuci
on

Este es el objeto de funcion en su sentido mas puro: un metodo


que es un objeto 17 . Al envolver un metodo en un objeto, usted
puede pasarlo a otros metodos u objetos como un parametro, para
decirles que para realizar esta operacion en particular en el proceso
de cumplir con su peticion.
#: 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
on 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 accion deseada a un metodo u objeto. En el ejemplo anterior, esto proporciona una manera de hacer cola en un conjunto
de acciones a realizar colectivamente. En este caso, ello le permite
crear dinamicamente un nuevo comportamiento, algo que solo puede
hacer normalmente escribiendo nuevo codigo, pero en el ejemplo anterior se podra hacer mediante la interpretacion de un script (ver el
patron Interpreter : interprete si lo que necesita hacer se pone muy
complejo).
Design Patterns dice que Los comandos son un reemplazo orientado a objetos para devoluciones de llamada18 . Sin embargo, Creo
que la palabra vuelta es una parte esencial del concepto de devoluciones de llamada. Es decir, Creo que una devolucion de llamada
en realidad se remonta al creador de la devolucion de llamada. Por
otro lado, con un objeto Command normalmente se acaba de crear
y entregar a alg
un metodo o un objeto, y no esta conectado de otra
forma el transcurso del tiempo con el objeto Command. Esa es
mi opinion sobre ella, de todos modos. Mas adelante en este libro,
18 P
agina

235

102

Combino un grupo de patrones de dise


no bajo el ttulo de devoluciones de llamada.

11.2

Estrategia: elegir el algoritmo en tiempo de ejecuci


on

Strategy parece ser una familia de clases Command, todo heredado


de la misma base. Pero si nos fijamos en Command, vera que tiene
la misma estructura: una jerarqua de objetos de funcion. La diferencia esta en la forma en que se utiliza esta jerarqua. 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 metodo que esta
siendo llamado, y la parte que vara es aislado en el objeto funcion.
Me atrevera a decir que Command proporciona flexibilidad. mientras usted esta escribiendo el programa, visto que la flexibilidad de
Strategy esta en tiempo de ejecucion.
Strategy tambien agrega un Contexto que puede ser una clase
sustituta que controla la seleccion 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 metodo de plantilla TM afirma distincion que este tiene mas de un metodo para llamar, hace las cosas
por partes. Ahora bien, no es probable que la estrategia de objeto tendra mas de una llamada al metodo; considerar sistema de
cumplimiento de la orden de Shalloway.con informacion de los pases
en cada estrategia.
Ejemplo Estrategia de Python estandar: sort( ) toma un segundo argumento opcional que act
ua como un objeto de comparacion;
esta es una estrategia.

105

11.3

Cadena de responsabilidad

Chain of Responsibility : Cadena de responsabilidad podra ser pensado como una generalizacion dinamica de recursividad utilizando
objetos Strategy. Usted hace una llamada, y cada Strategy en una
secuencia vinculada trata de satisfacer la llamada. El proceso termina cuando una de las estrategias es exitosa o termina la cadena.
En la recursividad, un metodo se llama a s mismo una y otra vez
hasta que se alcance una condicion de terminacion; con Chain of
Responsibility : Cadena de responsabilidad, un metodo se llama a s
mismo, que (moviendo por la cadena de Strategies) llama una implementacion diferente del metodo, etc., hasta que se alcanza una
condicion de terminacion. La condicion de terminacion 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 proporcionar un resultado por defecto as que usted debe ser capaz de
determinar el exito o el fracaso de la cadena) o una de las Strategies
: Estrategias tiene exito.
En lugar de llamar a un solo metodo para satisfacer una solicitud,
m
ultiples metodos 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
dinamicamente, por lo que tambien podra pensar en ello como una
mas general, declaracion switch dinamicamente construida.
En el GoF, hay una buena cantidad de discusion sobre como crear
la cadena de responsabilidad como una lista enlazada. Ahora bien,
cuando nos fijamos en el patron realmente no debera importar como
se mantiene la cadena; eso es un detalle de implementacion. Ya que
GoF fue escrito antes de la Librera de plantillas estandar (STL :
Standard Template Library ) fue incorporado en la mayora de los
compiladores de C ++, La razon de esto mas probable es (1) no
haba ninguna lista y por lo tanto tuvieron que crear una y (2) las
estructuras de datos a menudo se ense
nan como una habilidad fundamental en el mundo academico, y la idea de que las estructuras de
datos deben ser herramientas estandar disponibles con el lenguaje
de programacion puede no haber ocurrido a los autores GoF. Yo
sostengo que la implementacion de Chain of Responsibility como
una cadena (especficamente, una lista enlazada) no a
nade nada a
106

la solucion y puede facilmente ser implementado utilizando una lista


estandar de Python, como se muestra mas abajo. Ademas, usted
vera que he ido a alg
un esfuerzo para separar las partes de gestion de
la cadena de la implementacion de las distintas Strategies, de modo
que el codigo puede ser mas facilmente reutilizado.
En StrategyPattern.py, anteriormente, lo que probablemente
quiere es encontrar automaticamente una solucion. Chain of Responsibility : Cadena de Responsabilidad proporciona una manera
de hacer esto por el encadenamiento de los objetos Strategy juntos y proporcionando un mecanismo para que recursivamente automaticamente a traves 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
+ algorithm

name

# 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
ChainLink ( s o l u t i o n s
ChainLink ( s o l u t i o n s
ChainLink ( s o l u t i o n s
]

,
,
,
,

LeastSquares ( ) ) ,
NewtonsMethod ( ) ) ,
Bisection ()) ,
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 u
nica solucion tras otro hasta que uno coincida.
Usted debe ser capaz de a
nadir dinamicamente soluciones para el
sistema experto. La prueba para la solucion simplemente debe ser
un juego de string, pero cuando una solucion se ajusta, el sistema
experto debe devolver el tipo apropiado de objeto ProblemSolver.

12

7: Cambiando la interfaz.

A veces el problema que usted esta resolviendo es tan simple como:


Yo no tengo la interfaz que quiero. Dos de los patrones en Design
Patterns : Dise
no de Patrones resuelven este problema: Adapter :
Adaptador toma un tipo y produce una interfaz a alg
un otro tipo.
Facade: Fachada crea una interfaz para un conjunto de clases, simplemente para proporcionar una manera mas comoda para hacer
frente a una biblioteca o un paquete de recursos.
12.1

Adapter : Adaptador

Cuando tienes this, y usted necesita that, Adapter : Adaptador resuelve el problema. El u
nico requisito es producir un that, y hay un
n
umero de maneras que usted puede lograr esta adaptacion.
#: 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 termino proxy aqu, porque
en Design Patterns afirman que un proxy debe tener una interfaz
identica con el objeto que es para un sustituto : surrogate. Sin embargo, si tiene las dos palabras juntas: adaptador de proxy, tal
vez sea mas razonable.

12.2

Facade : 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 basicamente lo que logra
Facade. Si usted tiene una coleccion 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 u
til
para el programador cliente y que solo presenta lo que sea necesario.
Facade se suele implementar como fabrica abstracta singleton.
Claro, usted puede conseguir facilmente este efecto mediante la
creacion de una clase que contiene metodos de fabrica 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:
i n i t ( s e l f , x ) : pass
def
# 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 seccion utilizando la investigacion del libro de Larman]

12.3

Ejercicios

1. Crear una clase adaptador que carga automaticamente una matriz


bidimensional de objetos en un diccionario como pares clave-valor.

113

13

C
odigo Tabla impulsada:
flexibilidad de configuraci
on

Codigo de la tabla dirigido por el uso de clases internas anonimas


Vease el ejemplo ListPerformance en TIJ del Captulo 9.
Tambien GreenHouse.py

114

14

Devoluciones de Llamada

Desacoplamiento comportamiento codigo.


Observer y una categora de devoluciones de llamada llamado
Despacho m
ultiple (no en Design Patterns ) incluyendo el Visitor
de Design Patterns.

14.1

Observer : Observador

Al igual que las otras formas de devolucion de llamada, este contiene un punto de gancho donde se puede cambiar el codigo. La
diferencia es de naturaleza completamente dinamica del observador.
A menudo se utiliza para el caso especfico de los cambios basados
en el cambio de otro objeto de estado, pero es tambien la base de
la gestion de eventos. Cada vez que desee desacoplar la fuente de
la llamada desde el codigo de llamada de forma totalmente dinamica.
El patron observador resuelve un problema bastante com
un: Que
pasa si un grupo de objetos necesita actualizar a s mismos cuando
alg
un objeto cambia de estado? Esto se puede ver en el modelovista aspecto de MVC (modelo-vista-controlador) de Smalltalk, o
el Documento - Ver Arquitectura casi equivalente. Supongamos
que usted tiene alg
unos datos (el documento) y mas 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
un que su
solucion se ha hecho una parte de la libreria estandar java.util.
Hay dos tipos de objetos que se utilizan para implementar el
patron 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 Esta
bien, todo el mundo debe revisar y, potencialmente, actualizarse,
La clase Observable realiza esta tarea mediante una llamada al
metodo notifyObservers( ) para cada uno en la lista. El metodo
notifyObservers( ) es parte de la clase base Observable.

115

En realidad, hay dos cosas que cambian en el patron observador: la cantidad de objetos de observacion y la forma se produce
una actualizacion. Es decir, el patron observador permite modificar
ambos sin afectar el codigo circundante.

Observer es una clase interfaz que solo tiene una funcion


miembro, update( ). Esta funcion es llamada por el objeto que
esta siendo observado, cuando ese objeto decide que es hora de actualizar todos sus observadores. Los argumentos son opcionales; usted
podra tener un update( ) sin argumentos y eso todava encajara
en el patron observador; sin embargo, esto es mas general permite al objeto observado pasar el objeto que causo la actualizacion
(ya que un Observer puede ser registrado con mas de un objeto
observado) y cualquier informacion adicional si eso es u
til, en lugar
de forzar el objeto Observer que busca alrededor para ver quien
esta actualizando y para ir a buscar cualquier otra informacion que
necesita.
El objeto observado que decide cuando y como hacer la actualizacion sera llamado Observable.
Observable tiene una bandera para indicar si se ha cambiado.
En un dise
no mas simple, no habra ninguna bandera; si algo paso,
cad uno sera notificado. La bandera le permite esperar, y solo
notificar al Observers cuando usted decide sea el momento adecuado. Notese, sin embargo, que el control del estado de la bandera
es protected, de modo que solo 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 repitio 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 traves del set y
vuelve a llamar a la funcion 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
un lugar en el codigo de la clase derivada llamar setChanged( ). Esta es la funcion miembro que establece la
bandera cambiado, lo que significa que cuando se llama notifyObservers( ) todos los observadores seran, de hecho, seran notificados. Cuando usted llama setChanged( ) depende de la logica
de su programa.

117

14.1.1

Observando Flores

Dado que Python no tiene componentes de la librera estandar para


apoyar el patron observador (como hace Java), primero tenemos que
crear una. Lo mas sencillo de hacer es traducir la librera estandar
de Java Observer y Observable clases. Esto tambien proporciona
la traduccion mas facil a partir de codigo Java que utiliza estas libreras.
Al tratar de hacer esto, nos encontramos con un obstaculo menor,
que es el hecho de que Java tiene una palabra clave synchronized
que proporciona soporte integrado para la sincronizacion hilo. Ciertamente se podra lograr lo mismo a mano utilizando codigo 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 rapidamente tedioso de escribir y de leer.
Peter Norvig me proporciono una solucion mucho mas 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 funcion synchronized( ) toma un metodo y lo envuelve en
una funcion que a
nade la funcionalidad mutex. El metodo es llamado dentro de esta funcion:
r e t u r n apply ( method , a r g s )
y como la sentencia return pasa a traves de la clausula finally,
el mutex es liberado.
Esto es de alguna manera el patron de dise
no decorador, pero
mucho mas facil 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 metodo con un mutex.


synchronized( ) es una funcion de conveniencia que aplica synchronized( ) para una clase entera, o bien todos los metodos de la
clase (por defecto) o metodos 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 mas consistente para
utilizar la herencia, por tanto la clase base Synchronization es
proporcionada.
He aqu una prueba sencilla del modulo 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 uns 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 doublywrapped 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 Synchronization, 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 metodos sincronizados en masa, y la clase E utiliza la funcion
de conveniencia para sincronizar m y g. Tenga en cuenta que dado
que m termina siendo sincronizado en dos ocasiones, ello se entro y
salio dos veces para cada llamada, que no es muy deseable [puede
haber una correccion 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
def
def
def
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 = [ ]
setChanged ( s e l f ) : s e l f . changed = 1
clearChanged ( s e l f ) : s e l f . changed = 0
hasChanged ( s e l f ) : r e t u r n s e l f . changed
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 librera, aqu esta un ejemplo de el patron observador:
#: 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 interes incluyen que una Flower puede
abrir o cerrar. Debido al uso del idioma de la clase interna, estos
dos eventos pueden ser fenomenos observables por separado. OpenNotifier 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 tambien es muy u
til para definir
mas de un tipo de Observer, en Bee y Hummingbird, ya que
tanto las clases pueden querer observar independientemente aberturas Flower y cierres. Observe como 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
patron observador: la capacidad de cambiar el comportamiento en
tiempo de ejecucion mediante el registro de forma dinamica y anular
el registro Observers con Observables.
Si usted estudia el codigo de arriba veras que OpenNotifier y
CloseNotifier utiliza la interfaz Observable basica. Esto significa
que usted podra heredar otras clases Observable completamente
diferentes; la u
nica conexion 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 Captulo


14 en Thinking in Java, 2nd Edicion. Las cajas se colocan en una
cuadrcula en la pantalla y cada uno se inicializa a un color aleatorio.
En adicion, cada caja implements la interfaz Observer y es registrado 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 automaticamente metodo update( )
de cada objeto Observer. Dentro de este metodo, las comprobaciones 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. Vease mas adelante
una version que tiene la interfaz grafica de usuario, pero no los observadores, 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 documentacion en lnea para
Observable que es un poco confuso, ya que parece que se puede
utilizar un objeto ordinario Observablepara manejar las actualizaciones. Pero esto no funciona; intentalo 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 codigo la clase derivada llamada setChanged( ). Este es el metodo que establece la bandera
changed : cambiado, lo que significa que cuando se llama notifyObservers( ) todos los observadores seran, de hecho, seran notificado. En el ejemplo anterior, setChanged( ) es simplemente llamado dentro de notifyObservers( ), pero podra utilizar cualquier
criterio que desee para decidir cuando llamar setChanged( ).
BoxObserver contiene un solo objeto Observable llamado notifier, y cada vez que se crea un objeto OCBox, esta vinculada a
notifier. En OCBox al hacer clic con el mouse el metodo notifyObservers( ) es llamado, pasando el objeto se hace clic en un
argumento para que todas las cajas que reciben el mensaje (en su
metodo update( ) ) de que se ha hecho clic saben y pueden decidir
si cambiar s o no. Usando una combinacion de codigo en notifyObservers( ) y update( ) se puede trabajar algunos esquemas
bastante complejos.
129

Podra parecer que la forma en que los observadores son notificados debe ser congelada en tiempo de compilacion en el metodo
notifyObservers( ). Ahora bien, si se mira mas de cerca el codigo
anterior usted vera que el u
nico lugar en BoxObserver o OCBox
cuando es consciente de que usted esta trabajando con una BoxObservable esta en el punto de la creacion del objeto Observable
de ah en adelante todo lo que utiliza la interfaz basica Observable.
Esto significa que usted podra heredar otras clases Observable y
intercambiarlos en tiempo de ejecucion si desea cambiar el comportamiento de notificacion luego.
Aqu esta una version de lo anterior que no utiliza el patron Observador, escrito por Kevin Altis usando PythonCard, y colocado
aqu como un punto de partida para una traduccion 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 mas 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 herramienta que ajuste automaticamente todos los metodos en una
clase para proporcionar una rastreo de ejecucion, de manera que
se puede ver el nombre del metodo y cuando entro y salio.
2. Crear un dise
no minimalista Observador-observable en dos
clases. Basta con crear el mnimo en las dos clases, luego demostrar
su dise
no mediante la creacion de un Observable y muchos Observers, y hacer que el Observable para actualizar los Observers.
3. Modifica BoxObserver.py para convertirlo en un juego sencillo. 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
umero de clics que un solo jugador utiliza
para convertir el campo en un solo color. Tambien puede restringir
el color de un jugador a la primera que fue elegido.

15

11 : Despacho M
ultiple

Cuando se trata de m
ultiples tipos que estan interactuando, un programa puede un programa puede obtener todo desordenado. Por
ejemplo, considere un sistema que analiza y ejecuta expresiones
matematicas. Usted desea ser capaz de decir Number + Number,
Number * Number, etc., donde Number es la clase base para
una familia de objetos numericos. Pero cuando usted dice a + b,
y no conoce el tipo exacto de uno u otro a o b, as que como se
puede conseguir que interact
uen correctamente?

133

La respuesta comienza con algo que probablemente no piensas:


Python solo realiza despacho individual. Es decir, si esta realizando
una operacion en mas de un objeto cuyo tipo es desconocido, Python
puede invocar el mecanismo de enlace dinamico a uno solo 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 dinamico.
La solucion es llamada multiple dispatching : despacho m
ultiple.
Recuerde que el polimorfismo puede ocurrir solo a traves de llamadas
a funciones miembro, as que si quiere que el doble de despacho
ocurra, debe haber dos llamadas a la funcion miembro: el primero
para determinar el primero de tipo desconocido, y el segundo para
determinar el segundo de tipo desconocido. Con despacho m
ultiple,
usted debe tener una llamada a un metodo polimorfico para determinar cada uno de los tipos. Generalmente, va a configurar una
configuracion tal que una sola llamada de funcion miembro produce mas de una dinamica llamada a la funcion miembro y por lo
tanto determina mas de un tipo en el proceso. Para obtener este
efecto, usted necesita trabajar con mas de una llamada a un metodo
polimorfico: usted necesitara una llamada para cada despacho. Los
metodos en el siguiente ejemplo se llaman compete( ) y eval( ), y
son ambos miembros del mismo tipo. (En este caso habra solo dos
despachos, que se conocen como doble despacho). Si esta trabajando
con dos jerarquas de tipos diferentes que estan interactuando, entonces usted habra de tener una llamada a un metodo polimorfico
en cada jerarqua.
Aqu esta un ejemplo de despacho m
ultiple:
#: 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 .
future
import g e n e r a t o r s
from
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
s t r ( s e l f ) : r e t u r n s e l f . name
def
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 traduccion bastante literal de la version de Java, y
una de las cosas que usted puede notar es que la informacion sobre
las distintas combinaciones se codifica en cada tipo de Item. En realidad, termina siendo una especie de tabla excepto que se extiende
a traves de todas las clases. Esto no es muy facil de mantener si
alguna vez espera modificar el comportamiento o para a
nadir una
nueva clase Item. En su lugar, puede ser mas sensible a hacer la
tabla explcita, 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


future
import g e n e r a t o r s
from
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 .
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

class

# 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 facilmente como un solo objeto.

138

15.1

Visitor, un tipo de despacho m


ultiple

La suposicion es que usted tiene una jerarqua primaria de clases


que es fija; tal vez es de otro proveedor y no puede hacer cambios en esa jerarqua. Sin embargo, usted tena como a
nadir nuevos
metodos polimorficos a esa jerarqua, lo que significa que normalmente habra que a
nadir algo a la interfaz de la clase base. As que
el dilema es que usted necesita agregar metodos a la clase base, pero
no se puede tocar la clase base. Como se obtiene alrededor de esto?.
El patron de dise
no 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 u
ltima
seccion.
El patron visitante le permite extender la interfaz del tipo primario mediante la creacion de una jerarqua 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 continuacion, llamar a la funcion miembro de visitor
dinamicamente 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 Inhabitant : 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
ultiple.
2. Modificar el ejemplo de arriba para hacer las interacciones mas
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 que armas ganar y perder en
cada interaccion (como en PaperScissorsRock.py). Agregar una
funcion miembro battle( ) a Project que lleva dos Inhabitants
y coinciden unos contra los otros. Ahora cree una funcion miembro meeting( ) para Project que crea grupos de Dwarf, Elf y
Manager y batallas contra los grupos entre s hasta que solo 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
usqueda en la tabla. La forma mas 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
usqueda diciendo:
((Map)map.get(o1.getClass())).get(o2.getClass()).
Observe lo facil que es volver a configurar el sistema. Cuando
es mas apropiado utilizar este enfoque vs. difciles de codificacion
los despachos dinamicos? Se puede crear un sistema que tiene la
sencillez sintactica de uso del despacho dinamico, pero utiliza una
b
usqueda en la tabla?
4. Modificar Ejercicio 2 para utilizar la tecnica de tabla de consulta descrito en el Ejercicio 3.

141

16

12 : Patr
on Refactorizaci
on

Este captulo no ha tenido traduccion significativa todava.


En este captulo se analizara el proceso de resolucion de un problema mediante la aplicacion de patrones de dise
no de una forma
evolutiva. Es decir, un primer dise
no de corte sera utilizado para
la solucion inicial, y luego esta solucion sera examinada y diversos
patrones de dise
no se aplicaran al problema (algunos de los cuales
trabajaran, y algunos de los cuales no). La pregunta clave que siempre se pregunto en la b
usqueda de soluciones mejoradas es que 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 codigo mas de dise
nos a nivel de patron).
Se comienza con una solucion, y luego cuando se descubre que no
contin
ua para satisfacer sus necesidades, lo arregla. Por supuesto,
esta es una tendencia natural, pero en la programacion informatica
que ha sido muy difcil de lograr con programas de procedimiento, y
la aceptacion de la idea de que podemos refactorizar codigo y dise
nos
se a
nade al cuerpo de prueba de que la programacion 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 clasificar en un solo compartimiento, por lo que la informacion de tipo
especfico se pierde. Pero mas tarde, la informacion de tipo especfico
debe ser recuperada para ordenar adecuadamente la basura. En la
solucion inicial, RTTI(descrito en el captulo 12 de Thinking in Java,
Segunda edicion) es utilizado.
Esto no es un dise
no trivial, ya que tiene una restriccion a
nadida.
Eso es lo que hace que sea interesante se parece mas a los problemas desordenados que es probable que encuentre en su trabajo. La
restriccion 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 clasificacion


de esa basura. Aqu es donde entra en juego RTTI : usted tiene un
monton de piezas anonimas de basura, y el programa se da cuenta
exactamente de que 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
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

( wt )

s t a t i c double v a l = 0 . 1 0 f
i n i t ( s e l f , double wt ) : . i n i t
def
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
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

( wt )

( wt )

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 codigo fuente disponibles para este libro, este
archivo se colocara en el subdirectorio recyclea que se ramifica
desde el subdirectorio c12 (para el Captulo 12). La herramienta
de desembalaje se encarga de colocarlo en el subdirectorio correcto.
La razon para hacer esto es que este captulo reescribe este ejemplo
particular, un n
umero de veces y poniendo cada version en su propio directorio (utilizando el paquete por defecto en cada directorio
para que invocando el programa es facil), los nombres de clase no se
enfrentaran.
Varios objetos ArrayList se crean para mantener referencias
Trash. Claro, ArrayLists en realidad mantenga Objects por lo
que sostendran nada en absoluto. La razon por la que tienen Trash
(o algo derivado de Trash) es solo porque usted ha sido cuidadoso
para no poner en nada excepto Trash. Si usted hace poner algo
equivocado en el ArrayList, usted no conseguira ninguna compilacion advertencias de tiempo o errores usted descubrira solo
a traves de una excepcion en tiempo de ejecucion.
Cuando las referencias Trash son a
nadidas, pierden sus identidades especficas y se vuelven simplemente Object references
145

(son upcast). Sin embargo, debido polimorfismo del comportamiento


apropiado se sigue produciendo cuando los metodos dinamicamente
enlazados son llamados a traves de la Iterator sorter, una vez que
el Object resultante ha sido lanzado de nuevo a Trash. sumValue( ) tambien toma un Iterator para realizar operaciones en
cada objeto en el ArrayList.
Parece una tontera upcast los tipos de Trash en una retencion
de contenedores de referencias de tipo base, y luego dar la vuelta y
abatido. Por que 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 sera facil 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
no: funciona. Esto
podra estar bien, siempre y cuando se trata de una solucion de un
solo tiro Ahora bien, un programa u
til tiende a evolucionar con el
tiempo, por lo que debe preguntar: Que pasa si la situacion cambia? Por ejemplo, el carton es ahora un valioso producto reciclable,
as que como eso sera integrado en el sistema (especialmente si el
programa es grande y complicado).Desde lo anterior, la codificacion
tipo de verificacion en la declaracion switch podra ser esparcido en
todo el programa, usted debe ir a buscar todo ese codigo cada vez
que se agrega un nuevo tipo, y si se le pasa una el compilador no le
dara ninguna ayuda se
nalando un error.
La clave para el mal uso de RTTI aqu es que cada tipo se pone a
prueba. Si usted esta buscando solo un subconjunto de tipos porque
ese subconjunto necesita un tratamiento especial, eso probablemente
esta muy bien. Pero si usted esta buscando para cada tipo dentro
de una sentencia switch, entonces usted esta probablemente perdiendo un punto importante, y definitivamente hacer su codigo menos
mantenible. En la siguiente seccion vamos a ver como este programa
ha evolucionado a lo largo de varias etapas para llegar a ser mucho
mas flexible. Esto debe resultar un ejemplo valioso en el dise
no del
programa.

146

16.2

Mejorando el dise
no

Las soluciones en Design Patterns se organizan en torno a la pregunta Que va a cambiar a medida que evoluciona este programa?
Esta suele ser la pregunta mas importante que usted puede preguntar acerca de cualquier dise
no. Si usted puede construir su sistema
en torno a la respuesta, los resultados seran de dos vertientes: no solo
su sistema permite un facil (y barato) mantenimiento, pero tambien
se pueden producir componentes que son reutilizables, de modo que
los otros sistemas se pueden construir de forma mas economica. Esta
es la promesa de la programacion orientada a objetos, pero esto no
sucede automaticamente; se requiere el pensamiento y la vision de
su parte. En esta seccion veremos como este proceso puede suceder
durante el refinamiento de un sistema.
A la pregunta Que va a cambiar? para el sistema de reciclaje
es una respuesta com
un: se a
nadiran mas tipos al sistema. El objetivo del dise
no, entonces, es hacer de esta adicion de tipos lo menos
doloroso posible. En el programa de reciclaje, nos gustara encapsular todos los lugares donde se menciona la informacion de tipo
especfico, as (si no por otra razon) los cambios se pueden localizar
a esas encapsulaciones. Resulta que este proceso tambien limpia el
resto del codigo considerablemente.

16.2.1

Hacer m
as objetos

Esto nos lleva a un principio de dise


no orientado a objetos en general que escuche por primera vez hablado por Grady Booch: Si
el dise
no es demasiado complicado, hacer mas objetos. Esto es simultaneamente contrario a la intuicion y ridculamente simple, y sin
embargo es la gua mas u
til que he encontrado. (Es posible observar
que hacer mas objetos a menudo es equivalente a agregar otro
nivel de indireccion.) En general, si usted encuentra un lugar con
codigo desordenado, tenga en cuenta que tipo de clase limpiara eso.
A menudo, el efecto secundario de la limpieza del codigo sera un
sistema que tiene mejor estructura y es mas 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 tambien un lugar donde
usted debe cambiar el codigo cada vez que se agrega un nuevo tipo.
Si com
unmente se a
naden nuevos tipos, una mejor solucion es un
solo metodo que toma toda la informacion 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
patron creacional (de los cuales hay varios). El patron especfico
que se aplicara aqu es una variante del Factory Method : Metodo
de fabrica. Aqu, el metodo de fabrica es un miembro static de
Trash, pero mas com
unmente es un metodo que se anula en la
clase derivada.
La idea del metodo de fabrica es que se le pasa la informacion
esencial que necesita saber para crear su objeto, a continuacion,
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 polimorficamente. As, usted ni siquiera necesita saber el
tipo exacto de objeto que se crea. De hecho, el metodo de fabrica
lo esconde de usted para evitar el mal uso accidental. Si desea utilizar el objeto sin polimorfismo, debe utilizar explcitamente RTTI
y fundicion.
Pero hay un peque
no problema, especialmente cuando se utiliza el
enfoque mas complicado (no se muestra aqu) de hacer que el metodo
de fabrica en la clase base y anulando en las clases derivadas. Que
148

pasa si la informacion requerida en la clase derivada requiere argumentos mas o diferentes? La creacion de mas objetos resuelve este
problema. Para implementar el metodo de fabrica, la clase Trash
consigue un nuevo metodo llamado factory. Para ocultar los datos
creacionales, hay una nueva clase llamada Messenger que lleva
toda la informacion necesaria para el metodo factory para crear
el objeto Trash apropiado (hemos empezado haciendo referencia a
Messenger como un patron de dise
no, pero es bastante simple que
no puede elegir elevarlo a ese estado). Aqu esta una simple implementacion 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 u
nico trabajo de un objeto Messenger es mantener la informacion para el metodo factory( ). Ahora, si hay una situacion en
la que factory( ) necesita informacion mas 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 adicion de nuevos datos y nuevos constructores, o en el la manera
orientada a objetos mas tpica de las subclases.
El metodo factory( ) para este sencillo ejemplo se ve as:
s t a t i c Trash f a c t o r y ( Messenger i ) :
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 determinacion del tipo exacto de objeto es simple, pero
se puede imaginar un sistema mas complicado en el que factory( )
utiliza un algoritmo elaborado. El punto es que esta ahora escondido en un lugar, y usted sabe llegar a este lugar cuando se agregan
nuevos tipos.
La creacion de nuevos objetos es ahora mucho mas 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 declaracion todava necesitara ser modificada, pero que puede ser eliminada si
la creacion del objeto Messenger esta 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 ejecucion, pero proporciona la mayor flexibilidad.
Se puede ver en el codigo que el problema vector de cambio
de la fabrica es responsable de resolver: si agrega nuevos tipos al
sistema (el cambio), el u
nico codigo que debe ser modificado esta
dentro de la fabrica, por lo que la fabrica asla el efecto de ese cambio.

150

16.3

Un patr
on para la creaci
on de prototipos

Un problema con el dise


no anterior es que todava requiere una
ubicacion central donde deben ser conocidos todos los tipos de los
objetos: dentro del metodo factory(). Si regularmente se agregan
nuevos tipos al sistema, el metodo factory( ) debe cambiarse para
cada nuevo tipo. Cuando usted descubre algo como esto, es u
til para
tratar de avanzar un paso mas y mover toda la informacion sobre
el tipo incluyendo su creacion en la clase que representa ese
tipo. De esta manera, la u
nica cosa que necesita hacer para agregar
un nuevo tipo al sistema es heredar una sola clase.
Para mover la informacion relativa a la creacion de tipo en cada
tipo especfico de Trash, se utilizara el patron prototipo (del libro
Design Patterns). La idea general es que usted tiene una secuencia
principal de los objetos, uno de cada tipo que usted esta interesado en hacer. Los objetos en esta secuencia solo se utilizan para
la fabricacion de nuevos objetos, utilizando una operacion que no
es diferente del esquema clone( ) incorporado en clase raz Object
de Java. En este caso, vamos a nombrar el metodo de clonacion
tClone( ). Cuando este listo para hacer un nuevo objeto, presumiblemente usted tiene alg
un tipo de informacion que establece el tipo
de objeto que desea crear, a continuacion, se mueve a traves de la
secuencia maestra comparando su informacion con cualquier informacion 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 informacion modificable para la creacion.
Cada objeto sabe como exponer la informacion adecuada y la forma
de clonarse a s mismo. As, el metodo factory( ) no necesita ser
cambiado cuando se a
nade un nuevo tipo al sistema.
Un enfoque al problema del prototipado es agregar un n
umero
de metodos para apoyar la creacion de nuevos objetos. Ahora bien,
en Java 1.1 ya hay apoyo para la creacion de nuevos objetos si tiene
una referencia al objeto Class. Con Java 1.1 reflection : reflexion
(introducido en el captulo 12 de Thinking in Java, segunda edicion)
puede llamar a un constructor, incluso si tiene solo una referencia
al objeto Class. Esta es la solucion perfecta para el problema del
151

prototipado.
La lista de los prototipos sera representada indirectamente por
una lista de referencias a todos los objetos de Class que desea crear.
En adicion, si el prototipado falla, el metodo factory( ) asumira
que es porque un objeto particular Class no estaba en la lista, y
se tratara de cargarlo. Al cargar los prototipos de forma dinamica
como este, la clase Trash no necesita saber con que tipos esta trabajando, por lo que no necesita ninguna modificacion al agregar nuevos
tipos. Esto permite que sea facilmente reutilizable durante todo el
resto del captulo.
# 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
public
public
id =
data

String id
double data
Messenger ( S t r i n g name , double v a l ) :
name
= val

# :
La clase basica Trash y sumValue( ) permanecen como antes.
El resto de la clase soporta el patron de prototipado. Primero ve
dos clases internas (que se hacen static, as que son las clases internas solamente para los propositos de la organizacion de codigo)
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 version diferente de la clase Messenger que el de la discusion 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 que objeto desea hacer. Este
se utiliza para que la informacion leda desde un archivo se pueda
convertir en objetos.
Una vez que haya descubierto que tipo de Trash crear, a continuacion, los metodos de reflexion entran en juego. El metodo
getConstructor( ) toma un argumento que es un array de referencias Class. Este array representa los argumentos, en su debido
orden, para el constructor que usted esta buscando. Aqu, el array
es creado de forma dinamica usando Jvaa 1.1 la sintaxis de creacion
de array:
C l a s s [ ] : double . c l a s s
Este codigo asume que cada tipo Trash tiene un constructor
que toma un double (y observe que double.class es distinto de
Double.class). Tambien es posible, por una solucion mas 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 dinamica con el metodo newInstance( ),
lo cual toma un array de Object conteniendo los argumentos reales.
Este array se crea de nuevo utilizando la sintaxis de Java 1.1:
Object [ ] { Double ( Messenger . data )
En este caso, sin embargo, el double debe ser colocado 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 solo para los tipos primitivos.
Una vez que entienda como hacerlo, el proceso de crear un nuevo
objeto dado solo una referencia Class es muy simple. Reflexion
tambien le permite llamar metodos en esta misma manera dinamica.
Por supuesto, la referencia apropiada Class podra no estar en la
lista trashTypes. En este caso, el return en el bucle interno no se
ejecuta y que se retirara al final. Aqu, el programa trata de rectificar la situacion mediante la carga del objeto Class dinamicamente
y agregarlo a la lista trashTypes. Si a
un as no se puede encontrar algo anda realmente mal, pero si la carga tiene exito, entonces el
metodo factory se llama de forma recursiva para volver a intentarlo.
Como vera, la belleza de este dise
no es que el codigo no necesita
ser cambiado, independientemente de las diferentes situaciones en
que se utilizara (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 u


nico que se requiere
de cada nueva subclase de Trash es que contiene un constructor que
toma un argumento double. Java reflexion se encarga de todo lo
155

demas.

Estos
son los diferentes tipos de Trash, cada uno en su propio
archivo, pero parte del paquete Trash (de nuevo, para facilitar la
reutilizacion dentro del captulo):
# 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
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

( wt )

# :
# 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
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

( wt )

# :
# 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
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

( wt )

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
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

( wt )

# :
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 informacion sobre los objetos Trash sera ledo desde un archivo


exterior. El archivo cuenta con toda la informacion necesaria sobre
cada pieza de basura en una sola lnea 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 sera encontrada.
Este archivo se lee utilizando la herramienta StringList definida
previamente, y cada lnea es recogido aparte usando el metodo
String indexOf( ) para producir el ndice del :. Esto se utiliza
primero con el metodo String substring( ) para extraer el nombre
del tipo de basura, y al lado para obtener el valor que se convirtio en
un double con el metodo static Double.valueOf( ). El metodo
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 reutilizara en todo este captulo:
# 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 objetos Trash. Sin embargo, otros tipos de contenedores pueden ser
utilizados tambien. Para permitir esto, la primera version de fillBin( ) hace una referencia a un Fillable, lo cual es simplemente
una interface que soporta un metodo 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 mayora
de los ejemplos, tiene sentido a
nadir un segundo metodo fillBin(
) sobrecargado que toma un Collection. Cualquier Collection a
continuacion, 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 u
nico trabajo de esta clase es conectar el
metodo addTrash( ) de Fillable a Collections add( ). Con
esta clase en la mano, el metodo 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 vera esto despues, en DynaTrash.py.)

16.6

Reciclaje con prototipos

Ahora se puede ver la version revisada de RecycleA.py utilizando


la tecnica 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 ()
init ( self ):
def
# 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 simplemente son importados.
El proceso de abrir el archivo de datos que contiene descripciones
Trash y el analisis de ese archivo han sido envuelto en el metodo
static ParseTrash.fillBin( ), por lo que ahora ya no es parte de
nuestro enfoque de dise
no. Vera que en el resto del captulo, no
importa que se agregan nuevas clases, ParseTrash.fillBin( ) continuara funcionando sin cambios, lo que indica un buen dise
no.

161

En terminos de creacion de objetos, este dise


no 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
un cardboard :
carton, a pesar de que cardboard esta en la lista! Esto sucede debido a el uso de RTTI, que busca solo los tipos que le indican que
debe buscar. La pista que RTTI esta siendo mal utilizada es que
cada tipo en el sistema se esta probando, en lugar de un solo tipo o
subconjunto de tipos. Como se vera mas adelante, hay maneras de
utilizar polimorfismo en lugar de cuando se esta probando para cada
tipo. Pero si usa RTTI mucho de esta manera, y a
nade un nuevo
tipo a su sistema, usted puede olvidar facilmente hacer los cambios
necesarios en su programa y producir un error difcil de encontrar.
As que vale la pena tratar de eliminar RTTI en este caso, no solo
por razones esteticas produce codigo mas mantenible.

16.7

Haciendo abstracci
on de uso

Con la creacion fuera del camino, es el momento de abordar el resto


del dise
no: donde se utilizan las clases. Dado que es el acto de la
clasificacion en los contenedores que es particularmente feo y expuesto, por que no tomar ese proceso y ocultarlo dentro de una
clase? Este es el principio de Si debe hacer algo feo, al menos localizar la fealdad dentro de una clase. Se parece a esto:

La inicializacion de objetos TrashSorter ahora debe ser cambiado cada vez que un nuevo tipo de Trash se a
nade al modelo. Usted
podra imaginar que la clase TrashSorter podra 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 ArrayLists 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. Como
el metodo estaticamentecodificado trata con el hecho de que un
nuevo tipo ha sido a
nadido? Para solucionar esto, la informacion de
tipo debe ser removido de sort( ) de manera que todo lo que que
necesita hacer es llamar a un metodo generico que se ocupa de los
detalles del tipo. Esto, por supuesto, es otra manera de describir
un metodo dinamicamente enlazado. As sort( ) simplemente se
movera a traves de la secuencia y llamar a un metodo dinamicamente
enlazado para cada ArrayList. Dado que el trabajo de este metodo
es tomar las piezas de basura en que esta interesado, este es llamado
grab(Trash). La estructura ahora queda como:

TrashSorter necesita llamar cada metodo grab() y obtener un


resultado diferente dependiendo de que tipo de Trash ArrayList
actual esta sosteniendo. Es decir, cada ArrayList debe ser consciente del tipo que contiene. El enfoque clasico 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 tena un mecanismo de tipo parametrizado ese
163

probablemente sera el enfoque mas sencillo. Pero en lugar de la


codificacion manual de todas las clases que tal mecanismo debe estar construyendo para nosotros, mayor observacion puede producir
un mejor enfoque.
Un principio basico de dise
no en programacion orientada a objetos es: Usar los miembros de datos para la variacion en el estado,
utilice el polimorfismo para la variacion en el comportamiento. Su
primer pensamiento podra ser que el metodo grab( ) ciertamente
se comporta de manera diferente para un ArrayList que contiene
Paper que para uno que sostiene Glass. Pero lo que hace es estrictamente dependiente del tipo, y nada mas. Esto podra interpretarse
como un estado diferente, y dado que Java tiene una clase para representar el tipo (Class) Esto se puede utilizar para determinar el
tipo de Trash que sostendra a Tbin particular .
El constructor para este Tbin requiere que le entregue la Class
de su eleccion. Esto le dice al ArrayList que tipo se supone que
debe mantener. Entonces el metodo 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 version 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
i n i t ( s e l f , C l a s s binType ) : type = binType
def
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 ()
init ( self ):
def
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 constructor que tipo debe agarrar. El metodo grab() revisa este tipo
contra el objeto que se pasa. Tenga en cuenta que en este dise
no,
grab() solo acepta objetos Trash de este modo usted consigue la
165

comprobacion de tipos en tiempo de compilacion del tipo base, pero


usted podra tambien apenas aceptar Object y todava funcionara.
TbinList sostiene un conjunto de referencias Tbin, as que sort(
) puede iterar a traves de los Tbins cuando esta buscando una
pareja para el objeto Trash lo habeis transmitido. Si este no encuentra una pareja, crea un nuevo Tbin para el tipo que no ha sido
enconstrado, y hace una llamada recursiva a s mismo la proxima
vez, se encontrara el nuevo bin.
Note la generalidad de este codigo: no cambia en absoluto si
se a
naden nuevos tipos. Si la mayor parte de su codigo no necesita
cambiar cuando se a
nade un nuevo tipo (o alg
un otro cambio ocurre)
entonces usted tiene un sistema facilmente extensible.

166

16.8

Despacho m
ultiple

El dise
no anterior es ciertamente satisfactorio. La adicion de nuevos
tipos al sistema consiste en a
nadir o modificar clases distintas sin
causar cambios en el codigo que se propagan por todo el sistema.
En adicion, RTTI no esta mal utilizada como lo estaba en RecycleA.py. Sin embargo, es posible ir un paso mas alla y tomar un
punto de vista purista sobre RTTI y decir que debe ser eliminada
por completo de la operacion de clasificar la basura en los contenedores.
Para lograr esto, primero debe tomar la perspectiva de que todas las actividades de tipo dependiente tal como la deteccion del
tipo de un pedazo de basura y ponerla en el recipiente apropiado
deben ser controladas a traves del polimorfismo y enlace dinamico.
Los ejemplos anteriores primero ordenados por tipo, entonces actuaron en las secuencias de elementos que eran todos de un tipo
particular. Pero cada vez que usted se encuentra eligiendo tipos
particulares, detengase y piense. Toda la idea de polimorfismo
(dinamicamente enlazado con llamadas a metodos) es encargarse
de la informacion de tipo especfico para usted. As que por que la
b
usqueda de tipos?
La respuesta es algo que probablemente no piensa: Python solo
realiza despacho individual. Es decir, si esta realizando una operacion en mas de un objeto cuyo tipo es desconocido, Python invocara el mecanismo de enlace dinamico en solo uno de esos tipos.
Esto no resuelve el problema, as que usted termina la deteccion
de algunos tipos manualmente y produciendo eficazmente su propio
comportamiento de enlace dinamico.
La solucion es llamada multiple dispatching : Despacho m
ultiple
lo cual significa la creacion de una configuracion tal que una u
nica
llamada al metodo produce mas de una llamada a un metodo dinamico
y por lo tanto determina mas de un tipo en el proceso. Para conseguir este efecto, usted necesita trabajar con mas de una jerarqua
de tipos: usted necesitara una jerarqua de tipos para cada envo.
El siguiente ejemplo trabaja con dos jerarquas: la familia Trash
existente y una jerarqua de los tipos de contenedores de basura en
167

que la basura sera colocada. Esta segunda jerarqua no siempre es


evidente y en este caso esto necesitaba ser creado con el fin de producir despacho m
ultiple (en este caso habra solo dos despachos, lo
cual hace referencia como double dispatching : despacho doble).

168

16.8.1

La implementaci
on del doble despacho

Recuerde que el polimorfismo puede ocurrir solo a traves de llamadas a metodos, as que si quiere que se produzca el despacho
doble, deben existir dos llamadas a metodos: uno utilizado para
determinar el tipo dentro de cada jerarqua. En la jerarqua Trash
habra un nuevo metodo llamado addToBin(), que toma un argumento 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 vera el doble despacho.

La nueva jerarqua es TypedBin, y que contiene su propio metodo


llamado add() que tambien es utilizado polimorficamente. Pero aqu
esta un giro adicional: add() esta sobrecargado para tomar argumentos de los diferentes tipos de trash : basura. As que una parte
esencial del esquema de doble despacho tambien implica una sobrecarga. El redise
no del programa produce un dilema: ahora es
necesario para la clase base Trash contener un metodo addToBin(
). Un enfoque consiste en copiar todo el codigo y cambiar la clase
base. Otro enfoque, que puede tomar cuando usted no tiene el control del codigo fuente, es poner el metodo addToBin( ) dentro de
un interface, dejar Trash solo, y heredar nuevos tipos especficos
de Aluminum, Paper, Glass, y Cardboard. Este es el enfoque

169

que se tomara aqu.


La mayora de las clases en este dise
no 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 metodo addToBin( ) en la interfaz interface
TypedBinMember es implementado, pero parece que el codigo 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
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

( wt )

# :
# 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 :
i n i t ( s e l f , double wt ) : .
def
170

init

( 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 : 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
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

( wt )

# :
# 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
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

( wt )

# :
El codigo 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 codigo
es diferente. (Aunque este codigo se beneficiara 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 esta
dentro de este metodo usted sabe que es Aluminum, o Paper,
etc. Durante la llamada a add( ), esta informacion se pasa a traves
del tipo de this. El compilador resuelve la llamada a la version
correcta sobrecargada de add( ). Pero puesto que tb[i] produce
una referencia al tipo base TypedBin, esta llamada va a terminar
llamando a un metodo diferente dependiendo del tipo de TypedBin que esta 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 metodos sobrecargados add( ) retornan
false. Si el metodo no esta sobrecargado en una clase derivada,
continuara retornando false, y el llamador (addToBin( ), en este
172

caso) asumira que el objeto actual Trash no se ha a


nadido con exito
a un contenedor, y continuar buscando el contenedor correcto.
En cada una de las subclases de TypedBin, solo un metodo sobrecargado es anulado, de acuerdo con el tipo de bin que esta siendo
creado. Por ejemplo, CardboardBin anula add(DDCardboard).
El metodo anulado agrega el objeto trash : basura a su contenedor y
retorna true, mientras todo el resto de los metodos add( ) en CardboardBin continua para devolver false, ya que no se han anulado.
Este es otro caso en el que un mecanismo de tipo parametrizado
en Java permitira la generacion automatica de codigo. (Con C++
templates, usted no tendra que escribir explcitamente las subclases o colocar el metodo addToBin( ) en Trash.)
Puesto que para este ejemplo los tipos de basura se han personalizado y colocado en un directorio diferente, usted necesitara un
archivo de datos de basura diferente para hacer que funcione. Aqu
esta 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 Typed175

Bins, junto con el metodo sortIntoBins( ), que es donde todo el


doble despacho toma lugar. Usted puede ver que una vez que la
estructura esta configurada, la clasificacion en los distintos TypedBins es muy facil. En adicion, la eficiencia de dos llamadas al
metodo dinamico es probablemente mejor que cualquier otra forma
usted podra ordenar.
Note la facilidad de uso de este sistema en main( ), as como la
completa independencia de cualquier informacion de tipo especfico
dentro de main( ). Todos los otros metodos que hablan solo a la
interfaz de la clase base Trash seran igualmente invulnerable a los
cambios en los tipos Trash.
Los cambios necesarios para agregar un nuevo tipo son relativamente aislados: modifica TypedBin, heredar el nuevo tipo de
Trash con su metodo addToBin( ), luego heredar un nuevo TypedBin (esto es realmente solo una copia y sencilla edicion), y por
u
ltimo a
nadir un nuevo tipo en la inicializacion agregada de TrashBinSet.

16.9

El patr
on Visitor : visitante

Ahora considerar la aplicacion de un patron de dise


no que tiene un
objetivo completamente diferente al problema de clasificacion de basura.
Para este patron, ya no estamos preocupados con la optimizacion
de la adicion de nuevos tipos de Trash para el sistema. Ciertamente, este patron hace que la adicion de un nuevo tipo de Trash
mas complicado. El supuesto es que usted tiene una jerarqua de
clases primaria que es fija; quizas es de otro proveedor y no puedes
realizar cambios en esa jerarqua. Sin embargo, usted tena como
a
nadir nuevos metodos polimorficos a esa jerarqua, lo cual significa
que normalmente tena que a
nadir algo a la interfaz de la clase base.
As el dilema es que usted necesita a
nadir metodos a la clase base,
pero no puede tocar la clase base. Como se obtiene alrededor de
esto?

176

El patron de dise
no que resuelve este tipo de problema es llamado un visitor : visitante (la final en el libro Design Patterns
: Patrones de Dise
no), y se basa en el esquema de despacho doble
mostrado en la u
ltima seccion.
El patron visitor : visitante le permite extender la interfaz del
tipo primario mediante la creacion de una jerarqua 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 continuacion, llaman el visitante del metodo
dinamicamente enlazado. Se ve as:

177

Ahora, si v es una referencia Visitable para un objeto Aluminum, el codigo:


P r i c e V i s i t o r pv = P r i c e V i s i t o r ( )
v . a c c e p t ( pv )
utiliza despacho doble para causar dos llamadas a metodos polimorficos:
el primero para seleccionar la version de Aluminum de accept( ),
y el segundo dentro de accept( ) cuando la version especifica de
178

visit( ) es llamada de forma dinamica usando la clase base Visitor


referencia v.
Esta configuracion significa que la nueva funcionalidad puede ser
a
nadido al sistema en forma de nuevas subclases de Visitor. La
jerarqua Trash no necesita ser tocada. Este es el principal beneficio del patron visitante: usted puede agregar nueva funcionalidad
polimorfica a una jerarqua de clases sin tocar esa jerarqua (una vez
que los metodos accept( ) se han instalado). Tenga en cuenta que
el beneficio es u
til aqu, pero no es exactamente lo que empezamos
a lograr, as que a primera vista podra decidir que esta no es la
solucion deseada.
Pero mire una cosa que se ha logrado: la solucion visitante Evita
la clasificacion de la secuencia Trash maestro en secuencias escritas
individuales. As, usted puede dejar todo en la u
nica secuencia
maestra y simplemente pasar a traves de esa secuencia utilizando el
visitante apropiado para lograr el objetivo. Aunque este comportamiento parece ser un efecto secundario del visitante, Esto nos da
lo que queremos (evitando RTTI).
El despacho doble en el patron visitante se ocupa de determinar
tanto el tipo de Trash y el tipo de Visitor. En el siguiente ejemplo, 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
version 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 metodo accept(
):
#
#
#
#

c12 : t r a s h v i s i t o r : V i s i t a b l e . py
An c l a s s 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
def
def
def
def
# :

Visitor :
visit ( self
visit ( self
visit ( self
visit ( self

16.10

Un decorador reflexivo

,
,
,
,

Aluminum a )
Paper p )
Glass g )
Cardboard c )

En este punto, usted podra seguir el mismo criterio que se utilizo


para el despacho doble y crear nuevos subtipos de Aluminum, Paper, Glass, y Cardboard que implementan el metodo accept( ).
Por ejemplo, el nuevo Visitable Aluminum se vera 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 ) : .
def accept ( s e l f , V i si to r v ) :
v. visit ( self )

init

( wt )

# :
Sin embargo, Parece que estamos encontrando una explosion
de interfaces: Trash basico, versiones especiales para el despacho doble, y ahora las versiones mas especiales para los visitantes.
180

Claro, esta explosion de interfaces es arbitraria uno podra


simplemente poner los metodos adicionales de la clase Trash. Si
ignoramos que en lugar podemos ver la oportunidad de utilizar el
patron Decorador : Parece como que debera ser posible crear un
Decorador que puede ser envuelto alrededor de un objeto ordinario
Trash y producira la misma interfaz que Trash y agrega el metodo
extra accept( ). De hecho, es un ejemplo perfecto del valor de Decorador.
El doble despacho crea un problema, no obstante. Como se basa
en la sobrecarga de ambos accept( ) y visit( ), esto parecera requerir codigo especializado para cada version diferente del metodo
accept( ). Con las plantillas de C ++, esto sera bastante facil de
lograr (ya que las plantillas generan automaticamente codigo de tipo
especializado) pero Python no tiene tal mecanismo al menos no
parece. Sin embargo, reflexion le permite determinar la informacion
de tipo en tiempo de ejecucion, y llegar a resolver muchos problemas que pareceran requerir plantillas (aunque no tan simplemente).
Aqu esta 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 Esta

fue una soluci


on creada por Jaroslav Tulach en una clase de dise
no 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 ( )
# :
[[Descripcion del uso de Reflexion]]
La u
nica otra herramienta que necesitamos es un nuevo tipo de
adaptador Fillable que automaticamente decora los objetos a medida que se crean a partir del archivo original Trash.dat. Pero esto
bien podra ser un decorador de s mismo, la decoracion 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
un no se han creado.
El resto del programa crea tipos Visitor especficos y los enva
a traves de una lista u
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 como se a
nade la visitabilidad simplemente
creando un tipo diferente de bin usando el decorador. Observe
tambien que el adaptador FillableCollection tiene la apariencia
de ser utilizado como decorador (para ArrayList) en esta situacion.
Ahora bien, cambia completamente la interfaz del ArrayList, visto
que la definicion de Decorador es que la interfaz de la clase decorada
a
un debe estar all despues de la decoracion.
Tenga en cuenta que la forma del codigo del cliente (que se muestra en la clase Test) ha cambiado de nuevo, a partir de los enfoques
originales al problema. Ahora solo 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 identificacion de tipo en tiempo de ejecucion
que no sea el molde inevitable a Trash al tirar cosas fuera de la secuencia. Esto, tambien, podra ser eliminado con la implementacion
de tipos parametrizados en Java.
Una manera en que usted puede distinguir esta solucion de la
solucion de despacho doble descrita anteriormente es tener en cuenta
que, en la solucion del doble despacho, solamente uno de los metodos
sobrecargados, add( ), fue anulado cuando se creo cada subclase,
185

mientras que aqu cada uno de los metodos visit( ) sobrecargados


es anulado en cada subclase de Visitor.

16.10.1

M
as acoplamiento?

Hay mucho mas codigo aqu, y hay acoplamiento definitivo entre la


jerarqua Trash y la jerarqua Visitor. Ahora bien, tambien hay
alta cohesion 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
no. Claro, en este caso funciona bien solo
si esta agregando nuevos Visitors, pero se pone en el camino al
agregar nuevos tipos de Trash.
Bajo acoplamiento entre clases y alta cohesion dentro de una
clase es sin duda un objetivo de dise
no importante. Aplicado sin
pensar, sin embargo, puede impedirle el logro de un dise
no mas elegante.Parece que algunas clases, inevitablemente, tienen una cierta
intimidad con cada uno. Estos a menudo ocurren en parejas que
quizas podran ser llamados couplets : coplas; por ejemplo, los contenedores y los iteradores. El par anterior Trash-Visitor parece
ser otro como couplet.

16.11

RTTI considerado da
nino?

Varios dise
nos en este captulo intentan eliminar RTTI, lo cual
podra darle la impresion de que se considera perjudicial (la condenacion 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 razon por la que nuestros dise
nos
eliminan RTTI se debe a la mala aplicacion de esa caracterstica que
impide extensibilidad, mientras que el objetivo declarado era capaz
de a
nadir un nuevo tipo al sistema con el menor impacto en circundante codigo como sea posible. Dado que RTTI es a menudo mal
usado por tener que buscar todo tipo u
nico en su sistema, provoca
codigo que no sea extensible: cuando se agrega un nuevo tipo, usted
tiene que ir a buscar por todo el codigo en el que se usa RTTI, y si

186

se olvida de alguno usted no conseguira la ayuda del compilador.


Sin embargo, RTTI no crea automaticamente el codigo no extensible. Vamos a revisar el reciclador de basura una vez mas. Esta
vez, una nueva herramienta sera 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
no (sugerido
por Larry OBrien) es que el TypeMap agrega dinamicamente un
nuevo par cada vez que encuentra un nuevo tipo, por lo que cada
vez que a
nade un nuevo tipo al sistema (incluso si se agrega el nuevo
tipo en tiempo de ejecucion), se adapta.
Nuestro ejemplo nuevamente se basara en la estructura de los
tipos Trash en package c12.Trash (y el archivo Trash.dat utilizado 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 definicion para TypeMap es simple. Contiene un HashMap, y el metodo add( ) hace la mayora 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 esta
presente en el HashMap. Si es as, ese ArrayList se extrae y el
objeto se a
nade al ArrayList. Si no, el objeto Class y un nuevo
ArrayList se a
naden 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 metodo filler( ) es interesante porque Se aprovecha del dise


no
de ParseTrash.fillBin( ), que no solo tratar de llenar un ArrayList sino cualquier cosa que implementa la interfaz Fillable con
su metodo 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 anonima (descrito en el captulo 8 de Thinking in Java, segunda edicion) es
utilizada. Usted nunca necesita una clase llamada para implementar Fillable, solo necesita una referencia a un objeto de esa clase,
por lo que este es un uso apropiado de las clases internas anonimas.
Una cosa interesante sobre este dise
no es que a pesar de que no
fue creado para manejar la clasificacion, fillBin( ) esta realizando
tipo cada vez que se inserta un objeto Trash dentro de bin.
Gran parte de class DynaTrash debe estar familiarizado partir 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 clasificacion interna. Dando un paso a traves de TypeMap y operando en cada
ArrayList individual se convierte en un asunto sencillo.
Como puede ver, la adicion de un nuevo tipo al sistema no afectara este codigo en absoluto, y el codigo en TypeMap es completamente independiente. Esta es ciertamente la solucion mas peque
na
del problema, y podra decirse que el mas elegante tambien. No
depende mucho de de RTTI, pero observe que cada par clave-valor
en el HashMap esta en busca de un solo tipo. En adicion, no
hay manera que usted puede olvidar a
nadir el codigo adecuado a
este sistema cuando se agrega un nuevo tipo, ya que no hay ning
un
codigo que necesite agregar.

189

16.12

Resumen

Surgir con un dise


no como TrashVisitor.py que contiene una mayor
cantidad de codigo que los dise
nos anteriores puede parecer en un
principio ser contraproducente. Vale la pena notar lo que estas
tratando de lograr con varios dise
nos. Los patrones de dise
no 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. Quizas el cambio ocurre porque
el programa se coloca en un nuevo entorno o porque algo en el entorno actual cambia: (esto podra ser: El usuario quiere a
nadir
una nueva forma para el diagrama actualmente en la pantalla). O,
como en este caso, el cambio podra ser la evolucion del cuerpo del
codigo. Mientras que las versiones anteriores del ejemplo de clasificacion de basura enfatizaron la adicion de nuevos tipos de Trash
al sistema, TrashVisitor.py le permite a
nadir facilmente nuevas
funcionalidades sin molestar a la jerarqua Trash. Hay mas codigo
en TrashVisitor.py, pero la adicion 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 codigo para hacer que suceda con
mas 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
no inicial. La informacion
necesaria probablemente no aparecera hasta las u
ltimas fases del
proyecto: a veces solo en las fases de dise
no o de implementacion
se descubre una necesidad mas profunda o mas sutil en su sistema.
En el caso de la adicion de nuevos tipos (el cual fue el foco de la
mayora de los ejemplos reciclar) usted puede darse cuenta de que
necesita una jerarqua de herencia particular solo cuando se esta en
la fase de mantenimiento y de comenzar la ampliacion del sistema!
Una de las cosas mas importantes que aprendera mediante el estudio de los patrones de dise
no parece ser un cambio de actitud de
lo que se ha promovido hasta ahora en este libro. Es decir: Programacion Orientada a Objetos es todo acerca de polimorfismo.
Esta declaracion puede producir el sindrome dos a
nos de edad, con
un martillo (todo se ve como un clavo). Dicho de otra manera, es
bastante dficil obtener polimorfismo, y una vez que lo hace, trate
190

de emitir todos sus dise


nos en un molde particular.
Que patrones de dise
no dicen que la Programacion Orientada a
Objetos no se trata solo de polimorfismo?. Esto se trata de la separacion de cosas que cambian de los objetos que permanecen igual.
Polimorfismo es una manera especialmente importante para hacer
esto, y resulta ser u
til si el lenguaje de programacion apoya directamente el polimorfismo (por lo que no tiene que conectarlo usted
mismo, lo que tendera a hacer que sea prohibitivamente caro). Pero
los patrones de dise
no en general muestran otras maneras de lograr
el objetivo basico, y una vez que sus ojos se han abierto a esto usted
comenzara a buscar mas dise
nos creativos.
Desde que el libro Design Patterns salio e hizo tal impacto, la
gente ha estado buscando otros patrones. Usted puede esperar ver
mas de estos aparecer con el tiempo. Estos son algunos sitios recomendados por by Jim Coplien, de fama C ++ (http://www.belllabs.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
Tambien tenga en cuenta que ha habido una conferencia anual
sobre los patrones de dise
no, 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
nade la clase Plastic a TrashVisitor.py
2. A
nade la clase Plastic a DynaTrash.py
3. Crear un decorador como VisitableDecorator, pero para el
ejemplo de despacho m
ultiple, junto con una clase decorador adaptador como la creada para VisitableDecorator. Construir el
resto del ejemplo y demostrar que funciona.

17

Proyectos

Este captulo no ha tenido traduccion significativa todava.


Una serie de proyectos mas desafiantes para que usted pueda
resolver.
[[Algunos de estos pueden convertirse en ejemplos en el libro, por lo
que en alg
un momento podra 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 informacion. Este Blackboard particular dibuja
un laberinto, y es usado como informacion que vuelve sobre la estructura de un laberinto desde las ratas que lo estan buscando.
Ahora cree el propio laberinto. Como un laberinto real, este objeto revela muy poca informacion sobre si mismo dada una coordenada, que le dira si hay paredes o espacios en las cuatro direcciones
inmediatamente que coordinan, pero no mas. 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, informara paredes y espacios alrededor de esa coordenada.
Ademas, 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 informacion actual
y el laberinto para solicitar neva informacion sobre la base de la
posicion actual de la rata. Sin embargo, cada vez que una rata llega
a un punto de decision 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 callejon sin salida,
termina en s despues de informar los resultados de su b
usqueda final al Blackboard.
El objetivo es trazar un mapa completo del laberinto, pero tambien
usted debe determinar si la condicion final sera encontrada naturalmente o si el blackboard debe ser responsable de la decision.
Un ejemplo de implementacion de Jeremy Meyer:
# c13 : Maze . py
c l a s s Maze ( Canvas ) :
p r i v a t e Vector l i n e s # a l i n e i s a char 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 , y1)
# 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 ( x1, y ) )
i f ( ratCanMove )
Rat ( p r i s o n , x1, 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 inicializacion de laberinto:

197

17.1.1

Otros Recursos para Laberinto

Una discusion de algoritmos para crear laberintos as como el codigo


fuente de Java para implementarlas :
http://www.mazeworks.com/mazegen/mazegen.htm
Una discusion de algoritmos para la deteccion de colisiones y
otros comportamientos de movimiento individual/grupal de los objetos fsicos autonomos:
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 decodificacion XML.

198