Pensando en Python
Pensando en Python
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
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
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
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
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:
1 www.sharelatex.com
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
Introducci
on
2.1
El sndrome Y2K
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
3.1
Visi
on General de Python
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
#: 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
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
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
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
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
4.1
Qu
e es un Patr
on?
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
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
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
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
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
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
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
32
34
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
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
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
40
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
5.8
Ejercicios
M
etodo Plantilla
Ejercicios
: 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
51
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
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
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
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
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
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
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
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
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)
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
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
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
7.9
Ejercicios
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
X: Decoradores:
Selecci
on Tipo din
amico
76
8.1
8.2
Un ejemplo caf
e
8.3
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.
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
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
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
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
89
10
5: F
abricas:
encapsular
la creaci
on de objetos
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
subclasses ( ).
subclasses
()
10.2
F
abricas polim
orficas
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
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
100
11
6 : Objetos de funci
on
11.1
Comando: la elecci
on de la operaci
on en tiempo de
ejecuci
on
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
11.2
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
name
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
,
,
,
,
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
12
7: Cambiando la interfaz.
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
# 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
113
13
C
odigo Tabla impulsada:
flexibilidad de configuraci
on
114
14
Devoluciones de Llamada
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.
117
14.1.1
Observando Flores
# 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
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
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
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
#
#
#
#
#
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
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
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
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
16.1
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
( 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
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
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
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
16.4
Subclases Trash
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 )
( wt )
# :
Se puede ver que, aparte del constructor, no hay nada de especial
en cualquiera de estas clases.
16.5
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
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
16.7
Haciendo abstracci
on de uso
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:
164
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
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.
169
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
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
16.9
El patr
on Visitor : visitante
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
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 )
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
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
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
16.10.1
M
as acoplamiento?
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
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 .
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
189
16.12
Resumen
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
Ratas y Laberintos
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
Decorador XML
198