Pensando en Python PDF
Pensando en Python PDF
Pensando en Python PDF
en
Python
Bruce Eckel
Presidente MindView,Inc.
September 23, 2016
1
Contents
1 Prólogo 7
2 Introducción 8
2.1 El sı́ndrome Y2K . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2 Contexto y composición . . . . . . . . . . . . . . . . . . . . . . . 10
4 El concepto Patrón 20
4.1 ¿Qué es un Patrón? . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.2 Taxonomı́a Patrón . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.3 Estructuras Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.4 Criterios de Diseño . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.5 Singleton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.6 Clasificación de Patrones . . . . . . . . . . . . . . . . . . . . . . . 30
4.7 El desafı́o para el desarrollo . . . . . . . . . . . . . . . . . . . . . 31
4.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5 2: Pruebas Unitarias 32
5.1 Escribir pruebas primero . . . . . . . . . . . . . . . . . . . . . . . 34
5.2 Simples pruebas de Python . . . . . . . . . . . . . . . . . . . . . 35
5.3 Un framework muy simple . . . . . . . . . . . . . . . . . . . . . . 36
5.4 Escribir pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.5 Pruebas de caja blanca y caja negra . . . . . . . . . . . . . . . . 41
5.6 Ejecución de Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.7 Ejecutar Pruebas Automáticamente . . . . . . . . . . . . . . . . 48
5.8 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2
7.5 La tabla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
7.5.1 La máquina básica . . . . . . . . . . . . . . . . . . . . . . 68
7.6 Simple máquina expendedora . . . . . . . . . . . . . . . . . . . . 69
7.7 Prueba de la máquina . . . . . . . . . . . . . . . . . . . . . . . . 73
7.8 Herramientas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
7.9 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
8 X: Decoradores:
Selección Tipo dinámico 76
8.1 Estructura Decorador basico . . . . . . . . . . . . . . . . . . . . . 77
8.2 Un ejemplo café . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
8.3 Clase para cada combinación . . . . . . . . . . . . . . . . . . . . 77
8.4 El enfoque decorador . . . . . . . . . . . . . . . . . . . . . . . . . 80
8.5 Compromiso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8.6 Otras consideraciones . . . . . . . . . . . . . . . . . . . . . . . . 86
8.7 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9 Y: Iteradores:
Algoritmos de desacoplamiento de contenedores 88
9.1 Iteradores con seguridad de tipos . . . . . . . . . . . . . . . . . . 89
10 5: Fábricas:
encapsular
la creación de objetos 90
10.1 Simple método de fábrica . . . . . . . . . . . . . . . . . . . . . . 91
10.2 Fábricas polimórficas . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.3 Fábricas abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . 96
10.4 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
3
14.3 Ejercicios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
17 Proyectos 192
17.1 Ratas y Laberintos . . . . . . . . . . . . . . . . . . . . . . . . . 192
17.1.1 Otros Recursos para Laberinto . . . . . . . . . . . . . . . 198
17.2 Decorador XML . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
4
La composición de esta traducción se realizó utilizando LATEX, (gracias al
editor online ShareLatex)1 . El lector es totalmente libre de hacer correcciones
(en caso de algún error de sintaxis en la traslación de inglés a español) dentro
de la traducción en beneficio de la comunidad.
“Supongo que si eso es útil para usted. Ese libro nunca fue terminado y lo
que yo escribirı́a ahora es muy diferente. Pero si funciona para usted, eso está
bien.”
1 www.sharelatex.com
5
Grupo de trabajo académico GNU/Linux Universidad Distrital.
Semillero de Investigación en Tecnologı́a Libre.
Traducido por :
Leidy Marcela Aldana Burgos. (Estudiante de Ingenierı́a de
Sistemas)
Código Académico : 20151020019
Bogotá, Colombia.
Agradecimientos
Se reconoce la colaboración para las respectivas correcciones de esta traducción
hechas por:
6
1 Prólogo
El material en este libro inició en conjunción con el seminario de Java
que yo he dado por varios años, un par de veces con Larry O’Brien,
luego con Bill Venners. Bill y yo hemos dado muchas repeticiones de
este seminario y lo hemos cambiado a través de los años ya que am-
bos hemos aprendido más acerca de patrones y sobre dar el seminario.
Este libro no parará aquı́, tampoco. Originalmente, este material era parte
de un libro de C++, luego de un libro de Java, entonces este fue separado de su
propio libro basado en Java, y finalmente después de mucho examinar, decidı́
que la mejor manera para crear inicialmente mi escrito de patrones de diseño es
escribir primero esto en Python (ya que sabemos que Python hace un lenguaje
ideal de prototipos!) y luego traducir las partes pertinentes del libro de nuevo
en la versión de Java. He tenido la experiencia antes de emitir una idea en
un lenguaje más potente, luego traducir de nuevo en otro lenguaje, y he encon-
trado que esto es mucho más fácil para obtener información y tener la idea clara.
7
2 Introducción
Este es un libro sobre el proyecto en el que he estado trabajando du-
rante años, ya que básicamente nunca empecé a tratar de leer Design
Patterns (Patrones de Diseño) (Gamma,Helm,Johnson y Vlissides,
Addison-Wesley, 1995), comúnmente conocida como Gang of Four 2
o solo GoF).
Además, Supongo que tiene algo más que una comprensión de la sintaxis de
Python. Usted debe tener una buena comprensión de los objetos y lo que son
ellos, incluyendo el polimorfismo.
Por otro lado, al pasar por este libro vas a aprender mucho acerca de la
programación orientada a objetos al ver objetos utilizados en muchas situaciones
diferentes. Si su conocimiento de objetos es elemental, este se consiguirá mucho
más fuerte en el proceso de comprensión de los diseños en este libro.
2 Esta es una referencia irónica para un evento en China después de la muerte de Mao
Tze Tung, cuando cuatro personas incluyendo la viuda de Mao hicieron un juego de poder, y
fueron demonizados por el Partido Comunista de China bajo ese nombre.
8
2.1 El sı́ndrome Y2K
En un libro que tiene “técnicas de resolución de problemas” en su subtitulo,
vale la pena mencionar una de las mayores dificultades encontradas en la pro-
gramación: la optimización prematura. Cada vez traigo este concepto hacia
adelante, casi todo el mundo está de acuerdo en ello. Además, todo el mundo
parece reservar en su propia mente un caso especial “a excepción de esto que
me he enterado es un problema particular.”
La razón por la que llamo a esto el sı́ndrome Y2K debo hacerlo con ese
especial conocimiento. Los computadores son un misterio para la mayorı́a de
la gente, ası́ que cuando alguien anunció que los tontos programadores de com-
putadoras habı́an olvidado poner suficientes dı́gitos para mantener las fechas
más allá del año 1999, de repente todo el mundo se convirtió en un experto
en informática ”estas cosas no son tan difı́cil después de todo, si puedo ver
un problema tan obvio.” Por ejemplo, mi experiencia fue originalmente en in-
genierı́a informática, y empecé a cabo mediante la programación de sistemas
embebidos. Como resultado, sé que muchos sistemas embebidos no tienen idea
que fecha u hora es, e incluso si lo hacen esos datos a menudo no se utilizan
en los cálculos importantes. Y sin embargo, me dijeron en términos muy claros
que todos los sistemas embebidos iban a bloquearse en 01 de enero 2000 3 .En
lo que puedo decir el único recuerdo que se perdió en esa fecha en particular
fue el de las personas que estaban prediciendo pérdida – que es como si nunca
hubieran dicho nada de eso.
a bloquearse a continuación, también. Pero como casi todo el mundo tenı́a la experiencia de
su máquina Windows estrellándose todo el tiempo sin resultados particularmente graves, esto
no parece llevar el mismo drama de la catástrofe inminente.
9
2.2 Contexto y composición
Uno de los términos que verá utilizar una y otra vez en literatura de patrones
de diseño es context. De hecho, una definición común de un patrón de diseño
es: ”Una solución a un problema en un contexto.” Los patrones GoF a menudo
tienen un ”objeto de contexto” que el programador interactúa con el cliente. En
cierto momento se me ocurrió que dichos objetos parecı́an dominar el paisaje
de muchos patrones, y ası́ comencé preguntando de qué trataban.
El objeto de contexto a menudo actúa como una pequeña fachada para ocul-
tar la complejidad del resto del patrón, y, además, a menudo será el controlador
que gestiona el funcionamiento del patrón. Inicialmente, me parecı́a que no era
realmente esencial para la implementación, uso y comprensión del patrón. Sin
embargo, Recordé una de las declaraciones más dramáticas realizadas en el GoF:
“Preferirı́a composición a la herencia.” El objeto de contexto le permite utilizar
el patrón en una composición, y eso puede ser su valor principal.
10
3 Un rápido curso para programadores
Este libro asume que usted es un programador experimentado, y es
mejor si usted ha aprendido Python a través de otro libro. Para todos
los demás, este capitulo da una rápida introducción al lenguaje.
Python está diseñado para ser muy limpio para escribir y especialmente para
leer. Usted encontrará que es muy fácil leer su propio código mucho después de
que lo ha escrito, y también para leer el código de otras personas. Esto se logra
parcialmente a través de la sintaxis limpia, al punto, pero un factor mayor en la
legibilidad del código es la identación – la determinación del alcance en Python
viene determinada por la identación. Por ejemplo:
#: c01 : i f . py
response = ” yes ”
i f r e s p o n s e == ” y e s ” :
print ” affirmative ”
val = 1
print ” continuing . . . ”
#:˜
El ‘#’ denota un comentario que va hasta el final de la linea, al igual que
C++ y Java ‘//’ comenta.
11
La cláusula condicional termina con dos puntos, y esto indica que lo que
sigue será un grupo de sentencias identadas, que son la parte “entonces” de la
sentencia if. En este caso hay una declaración de “imprimir” el cual envı́a el
resultado a la salida estándar, seguido de una asignación a una variable llamada
val. La declaración posterior no esta identada ası́ ya no es parte del if. Iden-
tando puede anidar a cualquier nivel, al igual que los corchetes en C ++ o Java,
pero a diferencia de esos lenguajes no hay ninguna opción (y ningún argumento)
acerca de dónde se colocan los corchetes –el compilador obliga el código de cada
uno para ser formateado de la misma manera, lo cual es una de las principales
razones de legibilidad consistente de Python.
Python normalmente tiene sólo una declaración por lı́nea (se puede poner
más separándolos con punto y coma), por lo que el punto y coma de terminación
no es necesario. Incluso desde el breve ejemplo anterior se puede ver que el
lenguaje está diseñado para ser tan simple como sea posible, y sin embargo
sigue siendo muy legible.
#: c01 : l i s t . py
l i s t = [ 1 , 3 , 5 , 7 , 9 , 11 ]
print l i s t
l i s t . append ( 1 3 )
for x in l i s t :
print x
#:˜
La primera linea crea una lista. Puede imprimir la lista y esto mostrará ex-
actamente como usted pone esto (en contraste, recuerde que yo tuve que crear
una clase especial Arrays2 en Thinking in Java, 2da Edición en orden para im-
primir arrays en Java). Las listas son como contenedores de Java – usted puede
12
añadir elementos nuevos a ellos (aquı́, es usado append()) y van a cambiar
automáticamente el tamaño de sı́ mismos. La sentencia for crea un iterador x
que toma cada valor de la lista.
Usted puede crear una lista de números con la función range(),ası́ que si
usted realmente necesita imitar de C for, se puede.
Tenga en cuenta que no hay declaraciones de tipo –los nombres de los obje-
tos aparecen simplemente, y Python infiere su tipo por la forma en que se usan.
Es como si Python está diseñado para que usted sólo necesite pulsar las teclas
que sean absolutamente necesarias. Usted encontrará después de haber traba-
jado con Python por un corto tiempo que usted ha estado utilizando una gran
cantidad de ciclos cerebrales analizando punto y coma, corchetes, y todo tipo
de otra palabrerı́a adicional que fue exigido por su lenguaje de programación
diferente de Python pero no describe en realidad lo que se suponı́a que hiciera
su programa.
3.1.2 Funciones
Para crear una función en Python, use la palabra clave def, seguido por el nom-
bre de la función y la lista de argumentos, y dos puntos para empezar el cuerpo
de la función. Aquı́ esta el primer ejemplo convertido en una función:
#: c01 : myFunction . py
d e f myFunction ( r e s p o n s e ) :
val = 0
i f r e s p o n s e == ” y e s ” :
print ” affirmative ”
val = 1
print ” continuing . . . ”
return val
p r i n t myFunction ( ” no ” )
p r i n t myFunction ( ” y e s ” )
#:˜
Observe que no hay información de tipo en la firma de la función – todo lo
que se especifica es el nombre de la función y los identificadores de argumentos,
pero no los tipos de argumentos o tipo que devuelve. Python es un lenguaje
debilmente tipado, lo que significa que pone los requisitos mı́nimos posibles en
la introducción de caracteres. Por ejemplo, usted podrı́a pasar y devolver difer-
entes tipos desde la misma función:
13
#: c01 : d i f f e r e n t R e t u r n s . py
def d i f f e r e n t R e t u r n s ( arg ) :
i f a r g == 1 :
r e t u r n ” one ”
i f a r g == ” one ” :
return 1
print differentReturns (1)
p r i n t d i f f e r e n t R e t u r n s ( ” one ” )
#:˜
Las únicas limitaciones sobre un objeto que se pasa a la función, son que
la función puede aplicar sus operaciones a ese objeto, pero aparte de eso, no
importa. Aquı́, la misma función aplica el operador ‘+’para enteros y cadenas:
#: c01 : sum . py
d e f sum ( arg1 , a r g 2 ) :
return arg1 + arg2
p r i n t sum ( 4 2 , 4 7 )
p r i n t sum ( ’ spam ’ , ” e g g s ” )
#:˜
Cuando el operador ’+’ es usado con cadenas, esto significa concatenación,
(si, Python soporta la sobrecarga de operadores, y esto hace un buen trabajo
del mismo).
3.1.3 Cadenas
El ejemplo anterior también muestra un poco sobre manejo de cadenas de
Python, que es el mejor lenguaje que he visto. Usted puede usar comillas sim-
ples o dobles para representar cadenas, lo cual es muy agradable porque si usted
rodea una cadena con comillas dobles puede incluir comillas simples y viceversa:
#: c01 : s t r i n g s . py
p r i n t ”That i s n ’ t a h o r s e ”
p r i n t ’ You a r e not a ” V i k i n g ” ’
p r i n t ”””You ’ r e j u s t pounding two
coconut halves to g e t he r .”””
p r i n t ’ ’ ’ ”Oh no ! ” He e x c l a i m e d .
” I t ’ s t h e blemange ! ” ’ ’ ’
p r i n t r ’ c : \ python \ l i b \ u t i l s ’
#:˜
Tenga en cuenta que Python no fue nombrado después de la serpiente, sino
más bien la compañı́a de comedia Monty Python, y ası́ los ejemplos están
prácticamente obligados a incluir referencias Python-esque.
14
La triple cita de sintaxis cita todo, incluyendo saltos de lı́nea. Esto hace
que sea especialmente útil para hacer las cosas como la generación de páginas
web (Python es un lenguaje CGI especialmente bueno), ya que usted puede sólo
triple citar la página completa que desee sin ninguna otra edición.
La ‘r’ justo antes significa una cadena “raw : en bruto”, que toma las bar-
ras invertidas : \\, literalmente ası́ que usted no tiene que poner en una barra
inversa extra a fin de insertar una barra invertida literal.
#: c01 : s t r i n g F o r m a t t i n g . py
v a l = 47
p r i n t ”The number i s %d” % v a l
val2 = 63.4
s = ” v a l : %d , v a l 2 : %f ” % ( val , v a l 2 )
print s
#:˜
Como se puede ver en el segundo caso, si usted tiene más de un argumento
entre parentesis (esto forma una tupla, que es una lista que no puede ser mod-
ificado – también puede utilizar las listas regulares para múltiples argumentos,
pero tuplas son tı́picas).
15
3.1.4 Clases
Como todo lo demás en Python, la definición de una clase usa una mı́nima sin-
taxis adicional. Usted usa la palabra clave class, y en el interior del cuerpo se
utiliza def para crear métodos. Aquı́ está una clase simple:
#: c01 : S i m p l e C l a s s . py
c l a s s Simple :
def i n i t ( self , str ):
p r i n t ” I n s i d e t h e Simple c o n s t r u c t o r ”
self . s = str
# Two methods :
d e f show ( s e l f ) :
print s e l f . s
d e f showMsg ( s e l f , msg ) :
p r i n t msg + ’ : ’ ,
s e l f . show ( ) # C a l l i n g a n o t h e r method
if name == ” m a i n ” :
# C r e a t e an o b j e c t :
x = Simple ( ” c o n s t r u c t o r argument ” )
x . show ( )
x . showMsg ( ”A message ” )
#:˜
Ambos métodos tienen “self” como su primer argumento. C++ y Java am-
bos tienen un primer argumento oculto en sus métodos de clase, el cual apunta
al objeto para el método que fue llamado y se puede acceder usando la pal-
abra clave this. Los métodos de Python también utilizan una referencia al
objeto actual, pero cuando usted está definiendo un método debe especificar
explı́citamente la referencia como el primer argumento. Tradicionalmente, la
referencia se llama self pero usted podrı́a utilizar cualquier identificador que
desee (si usted no utiliza self probablemente confundirá a mucha gente, sin em-
bargo). Si necesita hacer referencia a campos en el objeto u otros métodos en el
objeto, debe utilizar self en la expresión. Sin embargo, cuando usted llama un
método para un objeto como en x.show(), no entrega la referencia al objeto –
que esta hecha para usted.
16
Todo el código al fondo se pone en marcha por una cláusula if, la cual
comprueba para ver si algo llamado name es equivalente a main . De
nuevo, los dobles guiones bajos indican nombres especiales. La razón de if es
que cualquier archivo también puede ser utilizado como un módulo de librerı́a
dentro de otro programa (módulos se describen en breve). En ese caso, usted
sólo quiere las clases definidas, pero usted no quiere el código en la parte infe-
rior del archivo a ejecutar. Esta declaración en particular if sólo es verdadera
cuando está ejecutando este archivo directamente; eso es, si usted dice en la
lı́nea de comandos:
Python S i m p l e C l a s s . py \ n e w l i n e
Sin embargo, si este archivo se importa como un módulo en otro programa,
no se ejecuta el código main .
Herencia
Si usted va a heredar de una clase, usted debe decirle a Python para traer
esa clase en el nuevo archivo. Python controla sus espacios de nombre tan
agresivamente como lo hace Java,y de manera similar (aunque con la incli-
nación de Python para la sencillez). Cada vez que se crea un archivo, se crea
implı́citamente un módulo (que es como un paquete en Java) con el mismo nom-
bre que el archivo. Por lo tanto, no se necesitó la palabra clave package en
Python. Cuando se desea utilizar un módulo, sólo dice import y da el nombre
del módulo. Python busca el PYTHONPATH del mismo modo que Java busca
el CLASSPATH (pero por alguna razón, Python no tiene el mismo tipo de di-
ficultades al igual que Java) y lee en el archivo. Para referirse a cualquiera de
las funciones o clases dentro de un módulo, usted le da el nombre del módulo,
un perı́odo, y el nombre de la función o clase. Si usted no quiere la molestia de
17
calificar el nombre, puede decir:
Donde “name(s)” puede ser una lista de nombres separada por comas.
Usted hereda una clase (o clases – Python soporta herencia multiple) enu-
merando el nombre(s) : name(s) de la clase dentro de paréntesis después del
nombre de la clase heredera. Tenga en cuenta que la clase Simple, la cual
reside en el archivo (y por lo tanto, el módulo) llamado SimpleClass se pone
en este nuevo espacio de nombres utilizando una sentencia import:
#: c01 : Simple2 . py
from S i m p l e C l a s s import Simple
c l a s s Simple2 ( Simple ) :
def i n i t ( self , str ):
p r i n t ” I n s i d e Simple2 c o n s t r u c t o r ”
# You must e x p l i c i t l y c a l l
# t h e base−c l a s s c o n s t r u c t o r :
Simple . i n i t ( self , str )
def display ( s e l f ) :
s e l f . showMsg ( ” C a l l e d from d i s p l a y ( ) ” )
# O v e r r i d i n g a base−c l a s s method
d e f show ( s e l f ) :
p r i n t ” Overridden show ( ) method”
# C a l l i n g a base−c l a s s method from i n s i d e
# t h e o v e r r i d d e n method :
Simple . show ( s e l f )
class Different :
d e f show ( s e l f ) :
p r i n t ”Not d e r i v e d from Simple ”
if name == ” m a i n ” :
x = Simple2 ( ” Simple2 c o n s t r u c t o r argument ” )
x . display ()
x . show ( )
x . showMsg ( ” I n s i d e main ” )
d e f f ( o b j ) : o b j . show ( ) # One−l i n e d e f i n i t i o n
f (x)
f ( Different ())
#:˜
Simple2 se hereda de Simple, y en el constructor, el constructor de la
clase base es llamado. En display(), showMsg() puede ser llamado como un
método de self, debe calificar por completo el nombre y pasar self como el
primer argumento, como se muestra en la llamada al constructor de la clase
base. Esto también puede verse en la versión anulada de show( ).
18
En main , usted puede ver (cuando corre el programa) que el construc-
tor de la clase base es llamado. También puede ver que el método showMsg()
es válido en las clases derivadas, del mismo modo que se puede esperar con la
herencia.
19
4 El concepto Patrón
“Los patrones de diseño ayudan a aprender de los éxitos de los demás
en lugar de sus propios fracasos”
4
La última parte de este libro contiene un ejemplo del proceso de evolución del
diseño, comenzando con una solución inicial y moviéndose a través de la lógica
y el proceso de la evolución de la solución a los diseños más apropiados. El
programa mostrado (una simulación de clasificación de basura) ha evolucionado
con el tiempo, puede mirar en dicha evolución como un prototipo de la forma
en que su propio diseño puede comenzar como una solución adecuada a un
problema particular y evolucionar hacia un enfoque flexible para una clase de
problemas.
20
usted abstrae algo usted está aislando detalles particulares, y una de las moti-
vaciones más convincentes detrás de esto es separar las cosas que cambian de
cosas que se quedan igual. Otra manera de poner esto es que una vez usted
encuentra alguna parte de su programa que es probable que cambie por una
razón u otra, usted querrá mantener esos cambios con respecto a la propagación
de otros cambios a través de su código. Esto no sólo hace el código mucho más
económico de mantener, pero también resulta que por lo general es más simple
de entender (lo cual resulta en menores costes).
21
1.Idioma:
Cómo escribimos código en un lenguaje particular, para hacer este tipo partic-
ular de cosas. Esto podrı́a ser algo tan común como la forma en que codifica el
proceso de paso a paso a través de una matriz en C (y no se salga del final).
2.Diseño Especifico:
la solución que se nos ocurrió para resolver este problema en particular. Esto
podrı́a ser un diseño inteligente, pero no intenta ser general.
3.Diseño Estándar:
una manera de resolver este tipo de problema. Un diseño que se ha vuelto más
general, tı́picamente a través de la reutilización.
4. Patrón de Diseño:
cómo resolver toda una clase de problema similar. Esto normalmente sólo
aparece después de la aplicación de un diseño estándar un número de veces,
y después de ver un patrón común a través de estas aplicaciones.
Siento que esto ayuda a poner las cosas en perspectiva, y para mostrar donde
algo podrı́a encajar. Sin embargo, esto no dice que uno es mejor que otro. No
tiene sentido tratar de tomar todas las soluciones de problemas y generalizarlas
a un patrón de diseño – no es un buen uso de su tiempo, y no se puede forzar el
descubrimiento de patrones de esa manera; ellos tienden a ser sutiles y aparecen
con el tiempo.
22
4.3 Estructuras Diseño
Una de las luchas que he tenido con los patrones de diseño es su clasificación -
A menudo he encontrado el enfoque GoF a ser demasiado oscuro, y no siempre
muy servicial. Ciertamente, los patrones creacionales son bastante sencillos:
¿cómo se va a crear sus objetos? Esta es una pregunta que normalmente nece-
sita preguntarse, y el nombre que lleva directamente a ese grupo de patrones.
Pero encuentro Structural and Behavioral : Estructurales y de comportamiento
a ser distinciones mucho menos útiles. No he sido capaz de mirar un problema y
decir ”Claramente, se necesita un patrón estructural aquı́”, por lo que la clasifi-
cación no me lleva a una solución (Voy a admitir fácilmente que yo pueda estar
perdiendo algo aquı́).
Con ese fin, he empezado a intentar reunir las estructuras básicas de diseño,
y tratar de ver si hay una manera de relacionar aquellas estructuras a los diver-
sos patrones de diseño que aparecen en sistemas bien pensados. Corrientemente,
sólo estoy tratando de hacer una lista, pero eventualmente espero hacer pasos
hacia la conexión de estas estructuras con los patrones (o Puedo llegar con un en-
foque totalmente diferente – esta se encuentra todavı́a en su etapa de formación)
Aquı́ 6 está la lista actual de candidatos, solo algunos de los cuales llegarán
al final de la lista. Siéntase libre de sugerir otros, o posiblemente, las relaciones
con los patrones.
23
• Variación en el Comportamiento
• Notificación
• Transacción
• Espejo: “Capacidad para mantener un universo paralelo(s) en el paso
con el mundo de oro”
• Sombra: “Sigue su movimiento y hace algo diferente en un medio difer-
ente” (Puede ser una variación de Proxy).
24
no es útil. Sin embargo, la mayorı́a de los desarrolladores trabajan en
sistemas especı́ficos, y la búsqueda de la generalidad no siempre sirven
bien. La mejor ruta para la generalidad es a través de la comprensión
de ejemplos especı́ficos bien definidos. Por lo tanto, este principio actúa
como el desempate entre alternativas de diseño de otro modo igualmente
viables. Por supuesto, es totalmente posible que la solución más simple es
la más general.
• La reflexividad: (mi término sugerido). Una abstracción por clase, una
clase por la abstracción. También podrı́a ser llamado Isomorfismo.
• Independencia o Ortogonalidad. Expresar ideas independientes de
forma independiente. Esto complementa Separación, Encapsulación y
Variación, y es parte del mensaje de bajo Acoplamiento-alta de Cohesión.
• Una vez y sólo una vez: Evitar la duplicación de la lógica y la estructura
donde la duplicación no es accidental, es decir, donde ambas piezas de
código expresan la misma intención por la misma razón.
4.5 Singleton
Posiblemente el patrón de diseño más simple es el Singleton, el cual es una man-
era de proporcionar un y sólo un objeto de un tipo particular. Para lograr esto,
usted debe tomar el control de la creación de objetos fuera de las manos del
programador. Una forma cómoda de hacerlo es delegar una sola instancia de
una clase interna privada anidada:
#: c01 : S i n g l e t o n P a t t e r n . py
c l a s s OnlyOne :
class OnlyOne :
def i n i t ( s e l f , arg ) :
s e l f . val = arg
def str ( self ):
return ‘ s e l f ‘ + s e l f . val
i n s t a n c e = None
def i n i t ( s e l f , arg ) :
i f not OnlyOne . i n s t a n c e :
OnlyOne . i n s t a n c e = OnlyOne . OnlyOne ( a r g )
else :
25
OnlyOne . i n s t a n c e . v a l = a r g
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i n s t a n c e , name )
x = OnlyOne ( ’ s a u s a g e ’ )
print x
y = OnlyOne ( ’ eggs ’ )
print y
z = OnlyOne ( ’ spam ’ )
print z
print x
print y
print ‘x ‘
print ‘y ‘
print ‘z ‘
output = ’ ’ ’
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>s a u s a g e
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>e g g s
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076B7AC>spam
< m a i n . OnlyOne i n s t a n c e a t 0076C54C>
< m a i n . OnlyOne i n s t a n c e a t 0076DAAC>
< m a i n . OnlyOne i n s t a n c e a t 0076AA3C>
’’’
#:˜
Debido a que la clase interna se llama con un doble subrayado, este es pri-
vado por lo que el usuario no puede acceder directamente a ella. La clase interna
contiene todos los métodos que normalmente se ponen en la clase si no se va
a ser un singleton, y luego se envuelve en la clase externa la cual controla la
creación mediante el uso de su constructor. La primera vez que usted crea un
OnlyOne, inicializa instance, pero después de eso sólo le ignora.
26
Una variación en esta técnica utiliza el método de la clase new añadido
en Python 2.2:
#: c01 : NewSingleton . py
c l a s s OnlyOne ( o b j e c t ) :
class OnlyOne :
def init ( self ):
s e l f . v a l = None
def str ( self ):
return ‘ s e l f ‘ + s e l f . val
i n s t a n c e = None
def new ( c l s ) : # new always a c l a s s m e t h o d
i f not OnlyOne . i n s t a n c e :
OnlyOne . i n s t a n c e = OnlyOne . OnlyOne ( )
r e t u r n OnlyOne . i n s t a n c e
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i n s t a n c e , name )
def s e t a t t r ( s e l f , name ) :
r e t u r n s e t a t t r ( s e l f . i n s t a n c e , name )
x = OnlyOne ( )
x . val = ’ sausage ’
print x
y = OnlyOne ( )
y . v a l = ’ eggs ’
print y
z = OnlyOne ( )
z . v a l = ’ spam ’
print z
print x
print y
#<hr>
output = ’ ’ ’
< m a i n . OnlyOne instance at 0 x00798900>s a u s a g e
< m a i n . OnlyOne instance at 0 x00798900>e g g s
< m a i n . OnlyOne instance at 0 x00798900>spam
< m a i n . OnlyOne instance at 0 x00798900>spam
< m a i n . OnlyOne instance at 0 x00798900>spam
’’’
#:˜
Alex Martelli hace la observación de que lo que realmente queremos con un
Singleton es tener un único conjunto de datos de estado de todos los objetos.
Es decir, puede crear tantos objetos como desee y, siempre y cuando todos se
refieren a la misma información de estado y luego lograr el efecto de Singleton.
27
Él logra esto con lo que él llama Borg 10 , lo cual se logra configurando todas las
dict s a la misma pieza estática de almacenamiento:
#: c01 : B o r g S i n g l e t o n . py
# Alex M a r t e l l i ’ s ’ Borg ’
c l a s s Borg :
s h a r e d s t a t e = {}
def init ( self ):
self . dict = self . shared state
c l a s s S i n g l e t o n ( Borg ) :
def i n i t ( s e l f , arg ) :
Borg . init ( self )
s e l f . val = arg
def s t r ( s e l f ) : return s e l f . val
x = S i n g l e t o n ( ’ sausage ’ )
print x
y = S i n g l e t o n ( ’ eggs ’ )
print y
z = S i n g l e t o n ( ’ spam ’ )
print z
print x
print y
print ‘x ‘
print ‘y ‘
print ‘z ‘
output = ’ ’ ’
sausage
eggs
spam
spam
spam
< m a i n . S i n g l e t o n i n s t a n c e a t 0079EF2C>
< m a i n . S i n g l e t o n i n s t a n c e a t 0079E10C>
< m a i n . S i n g l e t o n i n s t a n c e a t 00798F9C>
’’’
#:˜
Esto tiene un efecto identico como SingletonPattern.py, pero este es más
elegante. En el primer caso, deben conectarse en el comportamiento Singleton a
cada una de sus clases, pero Borg está diseñado para ser reutilizado fácilmente
10 Del programa de televisión Star Trek: The Next Generation. Los Borg son un colectivo
28
a través de la herencia.
#: c01 : S i n g l e t o n D e c o r a t o r . py
class SingletonDecorator :
def i n i t ( self , klass ):
self . klass = klass
s e l f . i n s t a n c e = None
def c a l l ( s e l f , ∗ a r g s , ∗ ∗ kwds ) :
i f s e l f . i n s t a n c e == None :
s e l f . i n s t a n c e = s e l f . k l a s s ( ∗ a r g s , ∗ ∗ kwds )
return s e l f . instance
c l a s s foo : pass
foo = SingletonDecorator ( foo )
x=f o o ( )
y=f o o ( )
z=f o o ( )
x . val = ’ sausage ’
y . v a l = ’ eggs ’
z . v a l = ’ spam ’
print x . val
print y . val
print z . val
print x is y is z
#:˜
[[ Descripción ]]
#: c01 : S i n g l e t o n M e t a C l a s s . py
c l a s s S i n g l e t o n M e t a C l a s s ( type ) :
def i n i t ( c l s , name , b a s e s , d i c t ) :
super ( SingletonMetaClass , c l s )\
11 Sugerido por Chih Chung Chang.
29
. i n i t ( name , b a s e s , d i c t )
original new = c l s . new
d e f my new ( c l s , ∗ a r g s , ∗ ∗ kwds ) :
i f c l s . i n s t a n c e == None :
cls . instance = \
o r i g i n a l n e w ( c l s , ∗ a r g s , ∗ ∗ kwds )
return c l s . instance
c l s . i n s t a n c e = None
c l s . new = s t a t i c m e t h o d ( my new )
c l a s s bar ( o b j e c t ) :
metaclass = SingletonMetaClass
def i n i t ( s e l f , val ) :
s e l f . val = val
def str ( self ):
return ‘ s e l f ‘ + s e l f . val
x=bar ( ’ s a u s a g e ’ )
y=bar ( ’ eggs ’ )
z=bar ( ’ spam ’ )
print x
print y
print z
print x is y is z
#:˜
[[Descripción prolongada, detallada, informativa de lo que son metaclases y
cómo funcionan, por arte de magia insertado aquı́]]
Ejercicio
Modificar BorgSingleton.py para que utilice un método new () de clase.
30
2. Estructural: diseñando objetos para satisfacer determinadas restric-
ciones del proyecto. Estos funcionan con la forma en que los objetos están
conectados con otros objetos para asegurar que los cambios en el sistema no
requieren cambios en esas conexiones.
El libro Design Patterns tiene una sección por cada uno de sus 23 patrones
junto con uno o más ejemplos para cada uno, normalmente en C ++, pero a
veces en Smalltalk. (Usted encontrará que esto no importa demasiado puesto
que puedes traducir fácilmente los conceptos de cualquier lenguaje en Python.)
Este libro no repetirá todos los patrones mostrados en Design Patterns ya que el
libro se destaca por su cuenta y deberı́a ser estudiado por separado. En lugar de
ello, este libro dará algunos ejemplos que deberı́a proporcionarle una sensación
decente para lo que son los patrones y por qué son tan importantes.
Después de años de mirar estas cosas, ello comenzó a ocurrir para mi que
los patrones utilizan para sı́ mismos principios básicos de organización, distintos
de (y más fundamental que) los descritos en Design Patterns. Estos princip-
ios se basan en la estructura de las implementaciones, que es donde he visto
grandes similitudes entre los patrones (más que aquellos expresados en Design
Patterns). Aunque nosotros generalmente tratamos de evitar la implementación
en favor de la interfaz, he encontrado que a menudo es más fácil que pensar, y
especialmente para aprender acerca de los patrones en términos de estos prin-
cipios estructurales. Este libro tratará de presentar los patrones basados en su
estructura en lugar de las categorı́as presentadas en Design Patterns.
Articulo: http://www.embedded.com/98/9807br.htm
31
http://collaboration.csc.ncsu.edu/laurie/
4.8 Ejercicios
1. SingletonPattern.py siempre crea un objeto, incluso si nunca se ha uti-
lizado. Modifique este programa para usar lazy initialization, por lo que el
objeto singleton sólo se crea la primera vez que se necesita.
5 2: Pruebas Unitarias
Este capı́tulo no ha tenido traducción significativa todavı́a.
32
general, producir un producto mejor, más rápido.
33
el aumento de la calidad de su código!).
34
la especificación en términos concretos y verificables.
2. Proporcionar un ejemplo de cómo se debe utilizar el código; de
nuevo, esto es un funcionamiento, ejemplo probado, mostrando
normalmente todas las llamadas a métodos importantes, en lugar
de sólo una descripción académica de una librerı́a.
3. Proporcionar una forma de verificar cuando se termina el código
(cuando todas las pruebas se ejecutan correctamente).
Ası́, si usted escribe las pruebas primero entonces la prueba se
convierte en una herramienta de desarrollo, no sólo un paso de ver-
ificación que se puede omitir si sucede que se siente cómodo sobre
el código que acabas de escribir (un consuelo, he encontrado, que es
usualmente equivocado).
#: SanityCheck . py
import s t r i n g , glob , os
# Do not i n c l u d e t he f o l l o w i n g i n t h e automatic
# tests :
e x c l u d e = ( ” SanityCheck . py ” , ” BoxObserver . py ” , )
35
os . c h d i r ( dirname )
try :
pyprogs = [ p f o r p i n g l o b . g l o b ( ’ ∗ . py ’ )
i f p not i n e x c l u d e ]
i f not pyprogs : r e t u r n
p r i n t ’ [ ’ + os . getcwd ( ) + ’ ] ’
f o r program i n pyprogs :
p r i n t ’ \ t ’ , program
os . system ( ” python %s > tmp” % program )
f i l e = open ( program ) . r ead ( )
output = open ( ’ tmp ’ ) . read ( )
# Append output i f i t ’ s not a l r e a d y t h e r e :
i f f i l e . f i n d ( ” output = ’ ’ ’ ” ) == −1 and \
l e n ( output ) > 0 :
d i v i d e r = ’# ’ ∗ 50 + ’ \ n ’
f i l e = f i l e . r e p l a c e ( ’# ’ + ’ : ˜ ’ , ’#<hr>\n ’ )
f i l e += ” output = ’ ’ ’ \ n” + \
open ( ’ tmp ’ ) . r ead ( ) + ” ’ ’ ’ \ n”
open ( program , ’ w ’ ) . w r i t e ( f i l e )
finally :
os . c h d i r ( d i r )
if name == ” m a i n ” :
os . path . walk ( ’ . ’ , v i s i t o r , None )
#:˜
Sólo tiene que ejecutar esto desde el directorio raı́z de los lista-
dos de código para el libro; ello descenderá en cada subdirectorio
y ejecutar el programa allı́. Una forma sencilla de comprobar las
cosas es redirigir la salida estándar a un archivo, entonces, si hay
cualquier error serán la única cosa que aparece en la consola durante
la ejecución del programa.
36
una manera de crear y ejecutar fácilmente y pruebas, e informar fra-
caso si algo se rompe (el éxito no producirá resultados distintos de
salida normal que puede ocurrir durante la ejecución de la prueba).
Mi uso previsto de este marco es en makefiles, y make aborta si
hay un valor de retorno distinto de cero de la ejecución de un co-
mando. El proceso de construcción consistirá en la compilación de
los programas y la ejecución de pruebas unitarias, y si make recibe
a través de todo el camino exitosamente, entonces el sistema será
validado, de lo contrario, se anulará en el lugar de la falta, para que
pueda proporcionar cualquier granularidad que necesita escribiendo
el mayor número de pruebas como quiera, cada una cubriendo tanto
o tan poco como usted encuentra necesario.
# t e s t : UnitTest . py
# The b a s i c u n i t t e s t i n g c l a s s
c l a s s UnitTest :
s t a t i c String testID
s t a t i c List e r r o r s = ArrayList ()
# Override cleanup () i f t e s t object
# c r e a t i o n a l l o c a t e s non−memory
# r e s o u r c e s t h a t must be c l e a n e d up :
def cleanup ( s e l f ) :
# Verify the truth of a c on d it io n :
p r o t e c t e d f i n a l v o i d a f f i r m ( b o o l e a n c o n d i t i o n ){
i f ( ! condition )
e r r o r s . add ( ” f a i l e d : ” + t e s t I D )
# :˜
37
El único método de prueba [[Hasta ahora]] es affirm( )13 , el
cual es protected de modo que pueda ser utilizado de la clase que
hereda. Todo lo que este método hace es verificar que algo es true.
Si no, añade un error a la lista, informando que la prueba actual
(establecida por la static testID, que es fijado por el programa de
pruebas de funcionamiento que deberá ver dentro de poco) ha fra-
casado. Aunque esto no es una gran cantidad de información — es
posible que también desee tener el número de lı́nea, lo que podrı́a ser
extraı́do de una excepción — puede ser suficiente para la mayorı́a
de las situaciones.
# c02 : TestDemo . py
# Creating a t e s t
c l a s s TestDemo :
p r i v a t e s t a t i c i n t objCounter = 0
p r i v a t e i n t i d = ++objCounter
p u b l i c TestDemo ( S t r i n g s ) :
p r i n t ( s + ” : count = ” + i d )
def close ( s e l f ) :
13 Yo habı́a llamado originalmente esta assert(), pero esa palabra llegó a ser reservada en
38
p r i n t ( ” C l e a n i n g up : ” + i d )
d e f someCondition ( s e l f ) : r e t u r n 1
p u b l i c s t a t i c c l a s s Test ( UnitTest ) :
TestDemo t e s t 1 = TestDemo ( ” t e s t 1 ” )
TestDemo t e s t 2 = TestDemo ( ” t e s t 2 ” )
def cleanup ( s e l f ) :
test2 . close ()
test1 . close ()
def testA ( s e l f ) :
p r i n t ‘ ‘ TestDemo . t e s t A ”
a f f i r m ( t e s t 1 . someCondition ( ) )
def testB ( s e l f ) :
p r i n t ‘ ‘ TestDemo . t e s t B ”
a f f i r m ( t e s t 2 . someCondition ( ) )
a f f i r m ( TestDemo . objCounter != 0 )
# Causes t he b u i l d t o h a l t :
#! p u b l i c v o i d t e s t 3 ( ) : a f f i r m ( 0 )
# :˜
El método test3() está comentado, porque, como verá, hace que
la acumulación automática de código fuente de árboles de este libro
se detuviera.
39
método close() que sugiere que se utiliza como parte de la limpieza
del objeto, ası́ este es llamado en el método reemplazado cleanup()
en Test.
Se puede ver que escribir código de prueba requiere muy poco es-
fuerzo adicional, y ningún conocimiento distinto del utilizado para
escribir las clases ordinarias.
t e s t 1 : count = 1
t e s t 2 : count = 2 r a t h e r than p u t t i n g i t i n and s t r i p p i n g i t out as i s
TestDemo . t e s t A
C l e a n i n g up : 2
C l e a n i n g up : 1
t e s t 1 : count = 3
t e s t 2 : count = 4
TestDemo . t e s t B
C l e a n i n g up : 4
C l e a n i n g up : 3
Todo el ruido de salida es tan lejos como el éxito o el fracaso de
la unidad de pruebas se refiere. Sólo si una o más de la unidad de
40
pruebas fallan el programa devuelve un valor distinto de cero para
terminar el proceso de make después se producen los mensajes de
error. Por lo tanto, se puede optar por producir una salida o no,
como se adapte a sus necesidades, y la clase de prueba llega a ser
un buen lugar para poner cualquier código de impresión que pueda
necesitar — si usted hace esto, se tiende a mantener dicho código
alrededor en lugar de ponerlo dentro y despojarlo afuera como se
hace normalmente con código de seguimiento.
# c02 : TestDemo2 . py
# I n h e r i t i n g from a c l a s s t h a t
# a l r e a d y has a t e s t i s no problem .
c l a s s TestDemo2 ( TestDemo ) :
p u b l i c TestDemo2 ( S t r i n g s ) : . i n i t ( s )
# You can even use t h e same name
# as th e t e s t c l a s s i n t h e base c l a s s :
p u b l i c s t a t i c c l a s s Test ( UnitTest ) :
def testA ( s e l f ) :
print ‘ ‘ TestDemo2 . t e s t A ”
a f f i r m ( 1 + 1 == 2 )
def testB ( s e l f ) :
p r i n t ‘ ‘ TestDemo2 . t e s t B ”
a f f i r m ( 2 ∗ 2 == 4 )
# :˜
Incluso el nombre de la clase interna puede ser el mismo. En el
código anterior, todas las afirmaciones son siempre verdaderas por
lo que las pruebas nunca fallarán.
41
Esto significa que el código de prueba tiene un acceso completo a la
parte interna de la clase que está siendo probado (por lo que podrı́a
ser llamado más apropiadamente las pruebas ”caja transparente”).
Pruebas de caja blanca sucede automáticamente cuando usted hace
la clase de prueba de unidad como una clase interna de la clase que
está probando, ya que las clases internas tienen automáticamente
acceso a todos sus elementos de clase exteriores, incluso los que son
private.
# c02 : T e s t a b l e . py
c l a s s Testable :
42
p ri v at e void f1 ( ) :
d e f f 2 ( s e l f ) : # ” F r i e n d l y ” : package a c c e s s
d e f f 3 ( s e l f ) : # Also package a c c e s s
def f4 ( s e l f ) :
# :˜
Normalmente, el único método que podrı́a ser accesible directa-
mente para el programador-cliente es f4(). Sin embargo, si usted
pone su prueba de caja negra en el mismo directorio, automáticamente
se convierte en parte de un mismo paquete (en este caso, el paquete
por defecto ya que no se especifica ninguno) y entonces tiene un
acceso inapropiado:
# c02 : TooMuchAccess . py
c l a s s TooMuchAccess ( UnitTest ) :
Testable t s t = Testable ()
def test1 ( s e l f ) :
t s t . f 2 ( ) # Oops !
t s t . f 3 ( ) # Oops !
t s t . f 4 ( ) # OK
# :˜
Puede resolver el problema moviendo TooMuchAcces.py en su
propio subdirectorio, de este modo poniendo esto en su propio pa-
quete por defecto (por lo tanto un paquete diferente de Testable.py).
Por supuesto, cuando usted hace esto, entonces Testable debe estar
en su propio paquete, de modo que pueda ser importado (tenga en
cuenta que también es posible importar una clase ”paquete-menos”,
dando el nombre de clase en la declaración import y asegurando
que la clase está en su CLASSPATH):
# c02 : t e s t a b l e : T e s t a b l e . py
package c02 . t e s t a b l e
c l a s s Testable :
p ri v at e void f1 ( ) :
d e f f 2 ( s e l f ) : # ” F r i e n d l y ” : package a c c e s s
d e f f 3 ( s e l f ) : # Also package a c c e s s
43
def f4 ( s e l f ) :
# :˜
Aquı́ está la prueba de la caja-negra en su propio paquete, mostrando
como solamente los métodos públicos pueden ser llamados:
# c02 : t e s t : BlackBoxTest . py
c l a s s BlackBoxTest ( UnitTest ) :
Testable t s t = Testable ()
def test1 ( s e l f ) :
#! t s t . f 2 ( ) # Nope !
#! t s t . f 3 ( ) # Nope !
t s t . f 4 ( ) # Only p u b l i c methods a v a i l a b l e
# :˜
Tenga en cuenta que el programa anterior es de hecho muy sim-
ilar al que el programador-cliente escribirı́a para utilizar su clase,
incluyendo las importaciones y métodos disponibles. De modo que
hace que un buen ejemplo de programación. Claro, es más fácil
desde el punto de vista de codificación para simplemente hacer una
clase interna, y a menos que sea apasionado sobre la necesidad es-
pecı́fica de pruebas de caja negra es posible que sólo quiera seguir
adelante y utilizar las clases internas (con el conocimiento que si
usted necesita que más tarde puede extraer las clases internas en
clases de prueba de caja negra separadas, sin demasiado esfuerzo).
# t e s t : RunUnitTests . py
# D i s c o v e r i n g th e u n i t t e s t
# c l a s s and running each t e s t .
c l a s s RunUnitTests :
44
public s t a t i c void
r e q u i r e ( b o o l e a n r e q u i r e m e n t , S t r i n g errmsg ) :
i f ( ! requirement ) :
System . e r r . p r i n t l n ( errmsg )
System . e x i t ( 1 )
d e f main ( s e l f , S t r i n g [ ] a r g s ) :
r e q u i r e ( a r g s . l e n g t h == 1 ,
” Usage : RunUnitTests q u a l i f i e d −c l a s s ” )
try :
C l a s s c = C l a s s . forName ( a r g s [ 0 ] )
# Only f i n d s th e i n n e r c l a s s e s
# d e c l a r e d i n th e c u r r e n t c l a s s :
Class [ ] c l a s s e s = c . getDeclaredClasses ()
C l a s s ut = n u l l
f o r ( i n t j = 0 j < c l a s s e s . l e n g t h j ++):
# Skip i n n e r c l a s s e s t h a t a r e
# not d e r i v e d from UnitTest :
i f ( ! UnitTest . c l a s s .
isAssignableFrom ( c l a s s e s [ j ] ) )
continue
ut = c l a s s e s [ j ]
break # Finds th e f i r s t t e s t c l a s s o n l y
# I f i t found an i n n e r c l a s s ,
# t h a t c l a s s must be s t a t i c :
i f ( ut != n u l l )
require (
M o d i f i e r . i s S t a t i c ( ut . g e t M o d i f i e r s ( ) ) ,
” i n n e r UnitTest c l a s s must be s t a t i c ” )
# I f i t couldn ’ t f i n d th e i n n e r c l a s s ,
# maybe i t ’ s a r e g u l a r c l a s s ( f o r black−
# box t e s t i n g :
i f ( ut == n u l l )
i f ( UnitTest . c l a s s . i s A s s i g n a b l e F r o m ( c ) )
ut = c
r e q u i r e ( ut != n u l l ,
”No UnitTest c l a s s found ” )
require (
45
M o d i f i e r . i s P u b l i c ( ut . g e t M o d i f i e r s ( ) ) ,
” UnitTest c l a s s must be p u b l i c ” )
Method [ ] methods = ut . getDeclaredMethods ( )
f o r ( i n t k = 0 k < methods . l e n g t h k++):
Method m = methods [ k ]
# I g n o r e o v e r r i d d e n UnitTest methods :
i f (m. getName ( ) . e q u a l s ( ” c l e a n u p ” ) )
continue
# Only p u b l i c methods with no
# arguments and v o i d r e t u r n
# t y p e s w i l l be used as t e s t code :
i f (m. getParameterTypes ( ) . l e n g t h == 0 &&
m. getReturnType ( ) == v o i d . c l a s s &&
M o d i f i e r . i s P u b l i c (m. g e t M o d i f i e r s ( ) ) ) :
# The name o f th e t e s t i s
# used i n e r r o r messages :
UnitTest . t e s t I D = m. getName ( )
# A i n s t a n c e o f t he
# t e s t o b j e c t i s c r e a t e d and
# c l e a n e d up f o r each t e s t :
Object t e s t = ut . n ew I ns t an c e ( )
m. i n v o k e ( t e s t , Object [ 0 ] )
( ( UnitTest ) t e s t ) . c l e a n u p ( )
c a t c h ( Ex cep ti on e ) :
e . p r i n t S t a c k T r a c e ( System . e r r )
# Any e x c e p t i o n w i l l r e t u r n a nonzero
# v a l u e t o t h e c o n s o l e , so t h a t
# ’ make ’ w i l l a b o r t :
System . e r r . p r i n t l n ( ” Aborting make ” )
System . e x i t ( 1 )
# A f t e r a l l t e s t s i n t h i s c l a s s a r e run ,
# d i s p l a y any r e s u l t s . I f t h e r e were e r r o r s ,
# a b o r t ’ make ’ by r e t u r n i n g a nonzero v a l u e .
if ( UnitTest . e r r o r s . s i z e ( ) != 0 ) :
I t e r a t o r i t = UnitTest . e r r o r s . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) )
System . e r r . p r i n t l n ( i t . next ( ) )
46
System . e x i t ( 1 )
# :˜
47
5.7 Ejecutar Pruebas Automáticamente
5.8 Ejercicios
1. Instalar el código fuente árbol de este libro y asegurar que usted
tenga una utilidad make instalada en su sistema.
48
hacer su trabajo, pero se le suele llamar sólo como parte de un
proceso de inicialización (y por tanto el programador-cliente no es
necesariamente capaz de llamarlo directamente).
#: c03 : TemplateMethod . py
# Simple d e m o n s t r a t i o n o f Template Method .
c l a s s ApplicationFramework :
def init ( self ):
s e l f . templateMethod ( )
def templateMethod ( s e l f ) :
f o r i i n range ( 5 ) :
s e l f . customize1 ()
s e l f . customize2 ()
# Create a ” a p p l i c a t i o n ” :
c l a s s MyApp( ApplicationFramework ) :
def customize1 ( s e l f ) :
p r i n t ”Nudge , nudge , wink , wink ! ” ,
def customize2 ( s e l f ) :
p r i n t ” Say no more , Say no more ! ”
MyApp( )
#:˜
El constructor de la clase base es responsable de realizar la inicial-
ización necesaria y después de iniciar el ”motor” (el método plan-
tilla) que ejecuta la aplicación (en una aplicación GUI, este ”mo-
tor” serı́a el bucle principal del evento). El programador cliente
simplemente proporciona definiciones para customize1() y cus-
tomize2() y la ”aplicación” esta listo para funcionar.
6.2 Ejercicios
1. Crear un entorno que tome una lista de nombres de archivo en
la lı́nea de comandos. Este abre cada archivo, excepto el último
para la lectura, y el último para la escritura. El entorno procesará
49
cada archivo de entrada utilizando una polı́tica indeterminada y
escribir la salida al último archivo. Heredar para personalizar
este entorno para crear dos aplicaciones separadas:
1) Convierte todas las letras en cada archivo a mayúsculas.
2) Busca los archivos de las palabras dadas en el primer archivo.
50
: Patrones de Diseño) que es distinta: Proxy es usado para controlar
el acceso a esta implementación, mientras State le permite cambiar
la implementación de forma dinámica. Sin embargo, si expande su
noción de ”controlando el acceso a la implementación”, entonces los
dos encajan pulcramente juntos.
7.1 Proxy
Si implementamos Proxy siguiendo el diagrama anterir, se ve ası́:
#: c04 : ProxyDemo . py
# Simple d e m o n s t r a t i o n o f t he Proxy p a t t e r n .
c l a s s Implementation :
def f ( s e l f ) :
p r i n t ” Implementation . f ( ) ”
def g( s e l f ) :
p r i n t ” Implementation . g ( ) ”
def h( s e l f ) :
p r i n t ” Implementation . h ( ) ”
c l a s s Proxy :
def init ( self ):
s e l f . i m p l e m e n t a t i o n = Implementation ( )
# Pass method c a l l s t o t he i m p l e m e n t a t i o n :
def f ( s e l f ) : s e l f . implementation . f ()
def g ( s e l f ) : s e l f . implementation . g ()
def h( s e l f ) : s e l f . implementation . h ()
p = Proxy ( )
p. f ( ) ; p. g ( ) ; p. h()
#:˜
No es necesario que Implementation tenga la misma interfaz
que Proxy; siempre y cuando Proxy es de alguna manera “speak-
ing for” : ”Hablar por” la clase que está refiriéndose al método llama
a continuación, la idea básica es satisfecha (tenga en cuenta que esta
declaración está en contradicción con la definición de Proxy en GoF).
Sin embargo, es conveniente tener una interfaz común para que Im-
plementation se vea obligado a cumplir con todos los métodos que
51
Proxy necesita llamar.
#: c04 : ProxyDemo2 . py
# Simple d e m o n s t r a t i o n o f t he Proxy p a t t e r n .
c l a s s Implementation2 :
def f ( s e l f ) :
p r i n t ” Implementation . f ( ) ”
def g( s e l f ) :
p r i n t ” Implementation . g ( ) ”
def h( s e l f ) :
p r i n t ” Implementation . h ( ) ”
c l a s s Proxy2 :
def init ( self ):
s e l f . i m p l e m e n t a t i o n = Implementation2 ( )
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i m p l e m e n t a t i o n , name )
p = Proxy2 ( )
p. f (); p.g(); p.h();
#:˜
La belleza de la utilización de getattr ( ) es que Proxy2 es
completamente genérico, y no vinculada a cualquier implementación
particular (en Java, un ”proxy dinámico” bastante complicado ha
sido inventado para lograr esto mismo).
52
7.2 State : Estado
El patrón State añade más implementaciones a Proxy, junto con una
manera de cambiar de una implementación a otra durante tiempo
de vida del sustituto:
#: c04 : StateDemo . py
# Simple d e m o n s t r a t i o n o f t he S t a t e p a t t e r n .
c l a s s State d :
def i n i t ( s e l f , imp ) :
s e l f . i m p l e m e n t a t i o n = imp
d e f changeImp ( s e l f , newImp ) :
s e l f . i m p l e m e n t a t i o n = newImp
# D e l e g a t e c a l l s t o t he i m p l e m e n t a t i o n :
def g e t a t t r ( s e l f , name ) :
r e t u r n g e t a t t r ( s e l f . i m p l e m e n t a t i o n , name )
c l a s s Implementation1 :
def f ( s e l f ) :
p r i n t ” F i d d l e de dum , F i d d l e de dee , ”
def g( s e l f ) :
p r i n t ” E r i c t he h a l f a bee . ”
def h( s e l f ) :
p r i n t ”Ho ho ho , t e e hee hee , ”
c l a s s Implementation2 :
def f ( s e l f ) :
p r i n t ”We’ r e Knights o f th e Round Table . ”
def g( s e l f ) :
p r i n t ”We dance whene ’ e r we ’ r e a b l e . ”
def h( s e l f ) :
p r i n t ”We do r o u t i n e s and c h o r u s s c e n e s ”
d e f run ( b ) :
b. f ()
b. g ()
b . h()
b. g ()
53
b = S t a t e d ( Implementation1 ( ) )
run ( b )
b . changeImp ( Implementation2 ( ) )
run ( b )
#:˜
Se puede ver que la primera implementación se usa para una
parte, a continuación, la segunda implementación se intercambia y
ese es utilizado.
54
me parece innecesario a menos que usted haya decidido que la imple-
mentación no está bajo su control (ciertamente una posibilidad, pero
si usted es dueño de todo el código no parece haber ninguna razón
para no beneficiarse de la elegancia y amabilidad de la clase base in-
dividual). En adición, Proxy no necesita utilizar la misma clase base
para su implementación, siempre y cuando el objeto proxy está con-
trolando acceso al objetarlo “frente” a favor. Independientemente
de los detalles, en ambos Proxy y State un sustituto está pasando
la llamada al método a través de un objeto de implementación.]]
55
7.3 StateMachine
Mientras State : Estado tiene una manera de permitir que el pro-
gramador cliente cambie la implementación, StateMachine impone
una estructura para cambiar automáticamente la implementación
de un objeto al siguiente. La implementación actual representa el
estado en que un sistema está, y el sistema se comporta de manera
diferente de un estado a otro (ya que utiliza State). Basicamente,
esta es una ”state machine : máquina de estados” usando objetos.
#: c04 : s t a t e m a c h i n e : S t a t e . py
# A S t a t e has an o p e r a t i o n , and can be moved
# i n t o t he next S t a t e g i v e n an Input :
c l a s s State :
d e f run ( s e l f ) :
a s s e r t 1 , ” run not implemented ”
d e f next ( s e l f , i n p u t ) :
a s s e r t 1 , ” next not implemented ”
#:˜
Esta clase es clase es claramente innecesaria, pero que nos per-
mite decir que algo es un objeto State en el código, y proporcionar
un mensaje de error ligeramente diferente cuando no se implemen-
56
tan todos los métodos. Podrı́amos haber conseguido básicamente el
mismo efecto diciendo:
c l a s s State : pass
Porque todavı́a conseguirı́amos excepciones si run o next() fueran
llamados por un tipo derivado, y no habı́an sido implementados.
#: c04 : s t a t e m a c h i n e : StateMachine . py
# Takes a l i s t o f I n p u t s t o move from S t a t e t o
# S t a t e u s i n g a t e m p l a t e method .
c l a s s StateMachine :
def init ( self , initialState ):
s e l f . currentState = i n i t i a l S t a t e
s e l f . c u r r e n t S t a t e . run ( )
# Template method :
def runAll ( s e l f , inputs ) :
for i in inputs :
print i
s e l f . c u r r e n t S t a t e = s e l f . c u r r e n t S t a t e . next ( i )
s e l f . c u r r e n t S t a t e . run ( )
#:˜
También he tratado runAll( ) como un método plantilla. Esto
es tı́pico, pero ciertamente no es necesario – posiblemente podrı́a
querer anularlo, pero por lo general el cambio de comportamiento
se producirá en State de run( ) en su lugar
57
puede moverse a través de varios estados en el proceso de atrapar
un ratón14 . Las clases ratón y la información se almacenan en el
paquete mouse, incluyendo una clase en representación de todas
los posibles movimientos que un ratón puede hacer, que serán los
entradas a la state machine: máquina de estados:
c l a s s MouseAction :
def i n i t ( self , action ) :
s e l f . action = action
def s t r ( s e l f ) : return s e l f . action
def cmp ( s e l f , other ) :
r e t u r n cmp( s e l f . a c t i o n , o t h e r . a c t i o n )
# N e c e s s a r y when cmp or eq i s defined
# i n o r d e r t o make t h i s c l a s s u s a b l e as a
# d i c t i o n a r y key :
def hash ( se lf ):
r e t u r n hash ( s e l f . a c t i o n )
# S t a t i c f i e l d s ; an enumeration o f i n s t a n c e s :
MouseAction . a p p e a r s = MouseAction ( ” mouse a p p e a r s ” )
MouseAction . runsAway = MouseAction ( ” mouse runs away ” )
MouseAction . e n t e r s = MouseAction ( ” mouse e n t e r s t r a p ” )
MouseAction . e s c a p e s = MouseAction ( ” mouse e s c a p e s ” )
MouseAction . trapped = MouseAction ( ” mouse trapped ” )
MouseAction . removed = MouseAction ( ” mouse removed ” )
#:˜
Usted observará que cmp ( ) se ha reemplazado para imple-
mentar una comparación entre los valores de acción. También, cada
posible jugada de un ratón se enumera como un objeto de Mouse-
Action, todos los cuales son los campos estáticos en MouseAction.
58
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse escapes
mouse appears
mouse enters trap
mouse trapped
mouse removed
mouse appears
mouse runs away
mouse appears
mouse enters trap
mouse trapped
mouse removed
#:˜
Con estas herramientas en su lugar, ahora es posible crear la
primera versión del programa mousetrap : ratonera. Cada subclase
State define su comportamiento run( ) y también establece su sigu-
iente estado con una cláusula if-else:
c l a s s Waiting ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Waiting : B r o a d c a s t i n g c h e e s e s m e l l ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . a p p e a r s :
r e t u r n MouseTrap . l u r i n g
r e t u r n MouseTrap . w a i t i n g
59
c l a s s Luring ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Luring : P r e s e n t i n g Cheese , door open ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . runsAway :
r e t u r n MouseTrap . w a i t i n g
i f i n p u t == MouseAction . e n t e r s :
r e t u r n MouseTrap . t r a p p i n g
r e t u r n MouseTrap . l u r i n g
c l a s s Trapping ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Trapping : C l o s i n g door ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . e s c a p e s :
r e t u r n MouseTrap . w a i t i n g
i f i n p u t == MouseAction . trapped :
r e t u r n MouseTrap . h o l d i n g
r e t u r n MouseTrap . t r a p p i n g
c l a s s Holding ( S t a t e ) :
d e f run ( s e l f ) :
p r i n t ” Holding : Mouse caught ”
d e f next ( s e l f , i n p u t ) :
i f i n p u t == MouseAction . removed :
r e t u r n MouseTrap . w a i t i n g
r e t u r n MouseTrap . h o l d i n g
c l a s s MouseTrap ( StateMachine ) :
def init ( self ):
# I n i t i a l state
StateMachine . i n i t ( s e l f , MouseTrap . w a i t i n g )
# Static variable i n i t i a l i z a t i o n :
MouseTrap . w a i t i n g = Waiting ( )
MouseTrap . l u r i n g = Luring ( )
MouseTrap . t r a p p i n g = Trapping ( )
60
MouseTrap . h o l d i n g = Holding ( )
moves = map( s t r i n g . s t r i p ,
open ( ” . . / mouse/MouseMoves . t x t ” ) . r e a d l i n e s ( ) )
MouseTrap ( ) . r u n A l l (map( MouseAction , moves ) )
#:˜
La clase StateMachine simplemente define todos los posibles
estados como objetos estáticos, y también establece el estado ini-
cial. UnitTest crea un MouseTrap y luego prueba con todas las
entradas de un MouseMoveList.
61
from StateMachine import StateMachine
from MouseAction import MouseAction
c l a s s StateT ( S t a t e ) :
def init ( self ):
s e l f . t r a n s i t i o n s = None
d e f next ( s e l f , i n p u t ) :
i f s e l f . t r a n s i t i o n s . has key ( input ) :
return s e l f . t r a n s i t i o n s [ input ]
else :
r a i s e ” Input not s u p p o r t e d f o r c u r r e n t s t a t e ”
c l a s s Waiting ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Waiting : B r o a d c a s t i n g c h e e s e s m e l l ”
d e f next ( s e l f , i n p u t ) :
# Lazy i n i t i a l i z a t i o n :
i f not s e l f . t r a n s i t i o n s :
self . transitions = {
MouseAction . a p p e a r s : MouseTrap . l u r i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Luring ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Luring : P r e s e n t i n g Cheese , door open ”
d e f next ( s e l f , i n p u t ) :
# Lazy i n i t i a l i z a t i o n :
i f not s e l f . t r a n s i t i o n s :
self . transitions = {
MouseAction . e n t e r s : MouseTrap . t r a p p i n g ,
MouseAction . runsAway : MouseTrap . w a i t i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Trapping ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Trapping : C l o s i n g door ”
d e f next ( s e l f , i n p u t ) :
# Lazy i n i t i a l i z a t i o n :
i f not s e l f . t r a n s i t i o n s :
62
self . transitions = {
MouseAction . e s c a p e s : MouseTrap . w a i t i n g ,
MouseAction . trapped : MouseTrap . h o l d i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s Holding ( StateT ) :
d e f run ( s e l f ) :
p r i n t ” Holding : Mouse caught ”
d e f next ( s e l f , i n p u t ) :
# Lazy i n i t i a l i z a t i o n :
i f not s e l f . t r a n s i t i o n s :
self . transitions = {
MouseAction . removed : MouseTrap . w a i t i n g
}
r e t u r n StateT . next ( s e l f , i n p u t )
c l a s s MouseTrap ( StateMachine ) :
def init ( self ):
# I n i t i a l state
StateMachine . i n i t ( s e l f , MouseTrap . w a i t i n g )
# Static variable i n i t i a l i z a t i o n :
MouseTrap . w a i t i n g = Waiting ( )
MouseTrap . l u r i n g = Luring ( )
MouseTrap . t r a p p i n g = Trapping ( )
MouseTrap . h o l d i n g = Holding ( )
moves = map( s t r i n g . s t r i p ,
open ( ” . . / mouse/MouseMoves . t x t ” ) . r e a d l i n e s ( ) )
mouseMoves = map( MouseAction , moves )
MouseTrap ( ) . r u n A l l ( mouseMoves )
#:˜
El resto del código es idéntico – la diferencia está en los métodos
next() y la clase StateT.
63
7.4 Table-Driven State Machine
La ventaja del diseño anterior es que toda la información acerca de
un estado, incluyendo la información de transición de estado, se en-
cuentra dentro de la clase propio Estado. Esto es generalmente un
buen principio de diseño.
Objetivos:
64
• Al igual que en flyweight : peso mosca
• Cada estado puede pasar a muchos otros
• Funciones de Estado y de acción también deben ser externos a
los estados
• Centralizar la descripción en una sola tabla que contiene todas
las variaciones, para facilitar la configuración.
Ejemplo:
• State Machine y Table-Driven Code
• Implementa una máquina expendedora
• Utiliza varios, otros patrones
• Separa código común state-machine de aplicación especı́fica
(como método de plantilla)
• Cada entrada causa buscar una solución apropiada (como ca-
dena de responsabilidad)
• Pruebas y transiciones se encapsulan en objetos de función (ob-
jetos que contienen funciones)
• Restricción de Java: los métodos no son objetos de primera
clase.
65
7.4.1 La clase State
# c04 : s t a t e m a c h i n e 2 : S t a t e . py
c l a s s State :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
# :˜
66
# c04 : s t a t e m a c h i n e 2 : Input . py
# I n p u t s t o a s t a t e machine
c l a s s Input : p a s s
# :˜
La Condition evalúa el Input para decidir si esta fila en la tabla
es la transición correcta:
# c04 : s t a t e m a c h i n e 2 : C o n d i t i o n . py
# C o n d i t i o n f u n c t i o n o b j e c t f o r s t a t e machine
c l a s s Condition :
boolean condition ( input ) :
a s s e r t 1 , ” c o n d i t i o n ( ) not implemented ”
# :˜
# c04 : s t a t e m a c h i n e 2 : T r a n s i t i o n . py
# T r a n s i t i o n f u n c t i o n o b j e c t f o r s t a t e machine
class Transition :
def t r a n s i t i o n ( s e l f , input ) :
a s s e r t 1 , ” t r a n s i t i o n ( ) not implemented ”
# :˜
7.5 La tabla
Con estas clases en el lugar, podemos establecer una tabla de 3 di-
mensiones, donde cada fila describe completamente un estado. El
primer elemento en la fila es el estado actual, y el resto de los ele-
mentos son cada uno una fila indicando lo que el tipo de la entrada
puede ser, la condición que debe ser satisfecha para que este cambio
67
de estado a ser la correcta, la acción que ocurre durante la transición,
y el nuevo estado para moverse dentro. Observe que el objeto Input
no sólo se utiliza para su tipo, también es un objeto Messenger que
lleva la información a los objetos Condition y Transition :
# 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
I t e r a t o r i t =(( L i s t )map . g e t ( s t a t e ) ) . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) ) :
Object [ ] t r a n = ( Object [ ] ) i t . next ( )
i f ( i n p u t == t r a n [ 0 ] | |
i n p u t . g e t C l a s s ( ) == t r a n [ 0 ] ) :
i f ( t r a n [ 1 ] != n u l l ) :
Condition c = ( Condition ) tran [ 1 ]
i f ( ! c . condition ( input ))
c o n t i n u e #F a i l e d t e s t
i f ( t r a n [ 2 ] != n u l l )
(( Transition ) tran [ 2 ] ) . t r a n s i t i o n ( input )
stat e = ( State ) tran [ 3 ]
return
throw RuntimeException (
68
” Input not s u p p o r t e d f o r c u r r e n t s t a t e ” )
# :˜
7.6 Simple máquina expendedora
c l a s s State :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
State . q u i e s c e n t = State (” Quiesecent ”)
State . c o l l e c t i n g = State (” C o l l e c t i n g ”)
State . s e l e c t i n g = State (” S e l e c t i n g ”)
State . u n a v a i l a b l e = State (” Unavailable ”)
S t a t e . wantMore = S t a t e ( ”Want More ? ” )
S t a t e . noChange = S t a t e ( ” Use Exact Change Only ” )
S t a t e . makesChange = S t a t e ( ” Machine makes change ” )
c l a s s HasChange :
def i n i t ( s e l f , name ) : s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
c l a s s ChangeAvailable ( StateMachine ) :
def init ( self ):
StateMachine . i n i t ( S t a t e . makesChange , {
# Current s t a t e , i n p u t
( S t a t e . makesChange , HasChange . no ) :
# t e s t , t r a n s i t i o n , next s t a t e :
( n u l l , n u l l , S t a t e . noChange ) ,
( S t a t e . noChange , HasChange . y e s ) :
( n u l l , n u l l , S t a t e . noChange )
})
69
c l a s s Money :
def i n i t ( s e l f , name , v a l u e ) :
s e l f . name = name
s e l f . value = value
def s t r ( s e l f ) : r e t u r n s e l f . name
def getValue ( s e l f ) : return s e l f . value
c l a s s Quit :
def str ( s e l f ) : r e t u r n ” Quit ”
Quit . q u i t = Quit ( )
class Digit :
def i n i t ( s e l f , name , v a l u e ) :
s e l f . name = name
s e l f . value = value
def s t r ( s e l f ) : r e t u r n s e l f . name
def getValue ( s e l f ) : return s e l f . value
c l a s s F i r s t D i g i t ( Digit ) : pass
F i r s t D i g i t .A = F i r s t D i g i t ( ”A” , 0)
F i r s t D i g i t . B = F i r s t D i g i t ( ”B” , 1)
F i r s t D i g i t .C = F i r s t D i g i t ( ”C” , 2)
F i r s t D i g i t .D = F i r s t D i g i t ( ”D” , 3)
c l a s s ItemSlot :
id = 0
def i n i t ( s e l f , price , quantity ) :
s e l f . price = price
70
s e l f . quantity = quantity
def s t r ( s e l f ) : r e t u r n ‘ I t e m S l o t . id ‘
def getPrice ( s e l f ) : return s e l f . price
def getQuantity ( s e l f ) : return s e l f . quantity
d e f d e c r Q u a n t i t y ( s e l f ) : s e l f . q u a n t i t y −= 1
c l a s s VendingMachine ( StateMachine ) :
c h a n g e A v a i l a b l e = ChangeAvailable ( )
amount = 0
FirstDigit f i r s t = null
ItemSlot [ ] [ ] items = ItemSlot [ 4 ] [ 4 ]
# Conditions :
d e f notEnough ( s e l f , i n p u t ) :
i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
r e t u r n i t e m s [ i 1 ] [ i 2 ] . g e t P r i c e ( ) > amount
# Transitions :
def c l e a r S e l e c t i o n ( s e l f , input ) :
i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
ItemSlot i s = items [ i1 ] [ i2 ]
print (
” C l e a r i n g s e l e c t i o n : item ” + i s +
” costs ” + is . getPrice () +
” and has q u a n t i t y ” + i s . g e t Q u a n t i t y ( ) )
f i r s t = null
71
def dispense ( s e l f , input ) :
i 1 = f i r s t . getValue ( )
i 2 = input . getValue ( )
ItemSlot i s = items [ i1 ] [ i2 ]
p r i n t ( ” D i s p e n s i n g item ” +
is + ” costs ” + is . getPrice () +
” and has q u a n t i t y ” + i s . g e t Q u a n t i t y ( ) )
items [ i 1 ] [ i 2 ] . decrQuantity ( )
p r i n t ( ” Quantity ” +
i s . getQuantity ( ) )
amount −= i s . g e t P r i c e ( )
p r i n t ( ” Amount r e m a i n i n g ” +
amount )
d e f showTotal ( s e l f , i n p u t ) :
amount += ( ( Money ) i n p u t ) . g e t V a l u e ( )
p r i n t ” Total amount = ” + amount
d e f returnChange ( s e l f , i n p u t ) :
p r i n t ” Returning ” + amount
amount = 0
d e f showDigit ( s e l f , i n p u t ) :
f i r s t = ( F i r s t D i g i t ) input
p r i n t ” F i r s t D i g i t= ”+ f i r s t
72
: : S t a t e . c o l l e c t i n g , # Current s t a t e
# Input , t e s t , t r a n s i t i o n , next s t a t e :
: Quit . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: Money . c l a s s , n u l l ,
showTotal , S t a t e . c o l l e c t i n g ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
: : S t a t e . s e l e c t i n g , # Current s t a t e
# Input , t e s t , t r a n s i t i o n , next s t a t e :
: Quit . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: S e c o n d D i g i t . c l a s s , notEnough ,
clearSelection , State . c o l l e c t i n g ,
: SecondDigit . c l a s s , itemNotAvailable ,
clearSelection , State . unavailable ,
: SecondDigit . class , itemAvailable ,
d i s p e n s e , S t a t e . wantMore ,
: : S t a t e . u n a v a i l a b l e , # Current s t a t e
# Input , t e s t , t r a n s i t i o n , next s t a t e :
: Quit . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
: : S t a t e . wantMore , # Current s t a t e
# Input , t e s t , t r a n s i t i o n , next s t a t e :
: Quit . q u i t , n u l l ,
returnChange , S t a t e . q u i e s c e n t ,
: FirstDigit . class , null ,
showDigit , S t a t e . s e l e c t i n g ,
)
# :˜
7.7 Prueba de la máquina
vm = VendingMachine ( )
f o r input in [
73
Money . q u a r t e r ,
Money . q u a r t e r ,
Money . d o l l a r ,
F i r s t D i g i t . A,
S e c o n d D i g i t . two ,
F i r s t D i g i t . A,
S e c o n d D i g i t . two ,
F i r s t D i g i t . C,
SecondDigit . three ,
F i r s t D i g i t . D,
S e c o n d D i g i t . one ,
Quit . q u i t ] :
vm . n e x t S t a t e ( i n p u t )
# :˜
7.8 Herramientas
Otro enfoque, como su state machine : máquina de estado se hace
más grande, es el uso de una herramienta de automatización medi-
ante el cual configura una tabla y deja que la herramienta genere el
código state machine para usted. Esto puede ser creado por sı́ mismo
utilizando un lenguaje como Python, pero también hay, herramien-
tas libres de código abierto como Libero, en http://www.imatix.com
7.9 Ejercicios
1. Crear un ejemplo del ”proxy virtual”.
74
manera que hará que la conexión para ser liberado de nuevo al sis-
tema.
75
jetos, al igual que una fábrica, excepto que la función generador no
requiere ningún argumento. Cree un MouseMoveGenerator que
produce acciones correctas MouseMove como salidas cada vez que
la función generador se llama (es decir, el mouse debe moverse en la
secuencia apropiada, por lo que los movimientos posibles se basan
en el movimiento anterior – esto es otra state machine). Agregue
un método iterator( ) para producir un iterador, pero este método
debe tomar un argumento int que especifica el número de movimien-
tos a producir antes de hasNext( ) que retorna false.
8 X: Decoradores:
Selección Tipo dinámico
El uso de objetos en capas para añadir de forma dinámica
y transparente responsabilidades a los objetos individuales
se conoce como el patrón decorator:decorador.
76
8.1 Estructura Decorador basico
77
resultante es enorme, y una parte del diagrama de clases serı́a algo
como esto:
c l a s s Cappuccino :
def init ( self ):
s e l f . cost = 1
s e l f . d e s c r i p t i o n = ” Cappucino ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description
La clave para el uso de este método es encontrar la combinación
particular que desea. Ası́, una vez que haya encontrado la bebida
que le gustarı́a, aquı́ es cómo usted lo utilizarı́a, como se muestra en
la clase CoffeeShop en el siguiente código:
#: cX : d e c o r a t o r : n o d e c o r a t o r s : CoffeeShop . py
# C o f f e e example with no d e c o r a t o r s
c l a s s Espresso : pass
c l a s s DoubleEspresso : pass
78
c l a s s EspressoConPanna : p a s s
c l a s s Cappuccino :
def init ( self ):
s e l f . cost = 1
s e l f . d e s c r i p t i o n = ” Cappucino ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description
class CappuccinoDecaf : p a s s
class CappuccinoDecafWhipped : p a s s
class CappuccinoDry : p a s s
class CappuccinoDryWhipped : p a s s
class CappuccinoExtraEspresso : p a s s
class CappuccinoExtraEspressoWhipped : p a s s
class CappuccinoWhipped : p a s s
c l a s s CafeMocha : p a s s
c l a s s CafeMochaDecaf : p a s s
c l a s s CafeMochaDecafWhipped :
def init ( self ):
s e l f . cost = 1.25
self . description = \
” Cafe Mocha d e c a f whipped cream ”
def getCost ( s e l f ) :
return s e l f . cost
def getDescription ( s e l f ) :
return s e l f . description
class CafeMochaExtraEspresso : p a s s
class CafeMochaExtraEspressoWhipped : p a s s
class CafeMochaWet : p a s s
class CafeMochaWetWhipped : p a s s
class CafeMochaWhipped : p a s s
c l a s s CafeLatte : pass
c l a s s CafeLatteDecaf : pass
79
class CafeLatteDecafWhipped : p a s s
class CafeLatteExtraEspresso : pass
class CafeLatteExtraEspressoWhipped : p a s s
class CafeLatteWet : p a s s
class CafeLatteWetWhipped : p a s s
c l a s s CafeLatteWhipped : p a s s
c a p p u c c i n o = Cappuccino ( )
p r i n t ( c a p p u c c i n o . g e t D e s c r i p t i o n ( ) + ” : \$” +
‘ cappuccino . getCost ( ) ‘ )
cafeMocha = CafeMochaDecafWhipped ( )
p r i n t ( cafeMocha . g e t D e s c r i p t i o n ( )
+ ” : \$” + ‘ cafeMocha . g e t C o s t ( ) ‘ )
#:˜
y aquı́ está la salida correspondiente:
80
esta transparencia.
c l a s s Espresso ( Decorator ) :
cost = 0.75 f
description = ” espresso ”
p u b l i c E s p r e s s o ( DrinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , component )
def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + c o s t
def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) +
description
Usted combina los componentes para crear una bebida de la sigu-
iente manera, como se muestra en el siguiente código:
#: cX : d e c o r a t o r : a l l d e c o r a t o r s : CoffeeShop . py
# C o f f e e example u s i n g d e c o r a t o r s
c l a s s DrinkComponent :
81
def getDescription ( s e l f ) :
return s e l f . c l a s s . name
def getTotalCost ( s e l f ) :
return s e l f . c l a s s . cost
c l a s s Mug( DrinkComponent ) :
cost = 0.0
c l a s s D e c o r a t o r ( DrinkComponent ) :
def i n i t ( s e l f , drinkComponent ) :
s e l f . component = drinkComponent
def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + \
DrinkComponent . g e t T o t a l C o s t ( s e l f )
def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) + \
’ ’ + DrinkComponent . g e t D e s c r i p t i o n ( s e l f )
c l a s s Espresso ( Decorator ) :
cost = 0.75
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Decaf ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s FoamedMilk ( D e c o r a t o r ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s SteamedMilk ( D e c o r a t o r ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Whipped ( D e c o r a t o r ) :
cost = 0.25
82
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Chocolate ( Decorator ) :
cost = 0.25
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c a p p u c c i n o = E s p r e s s o ( FoamedMilk (Mug ( ) ) )
p r i n t cappuccino . g e t D e s c r i p t i o n ( ) . s t r i p ( ) + \
” : \$” + ‘ c a p p u c c i n o . g e t T o t a l C o s t ( ) ‘
cafeMocha = E s p r e s s o ( SteamedMilk ( C h o c o l a t e (
Whipped ( Decaf (Mug ( ) ) ) ) ) )
p r i n t cafeMocha . g e t D e s c r i p t i o n ( ) . s t r i p ( ) + \
” : \$” + ‘ cafeMocha . g e t T o t a l C o s t ( ) ‘
#:˜
Este enfoque, sin duda, proporciona la mayor flexibilidad y el
menú más pequeño. Usted tiene un pequeño número de compo-
nentes para elegir, pero el montaje de la descripción del café en-
tonces se vuelve bastante arduo.
8.5 Compromiso
El enfoque anterior toma demasiado tiempo para describir un café.
También habrá ciertas combinaciones que va a describir con regu-
laridad, y serı́a conveniente tener una forma rápida de describirlos.
83
si querı́a decorarlos (crema batida, descafeinado etc), entonces usted
usarı́a decoradores para hacer las modificaciones. Este es el tipo de
menú que se le presenta en la mayorı́a de tiendas de café.
Aquı́ está cómo crear una selección básica, ası́ como una selección
decorada:
#: cX : d e c o r a t o r : compromise : CoffeeShop . py
# C o f f e e example with a compromise o f b a s i c
# c o m b i n a t i o n s and d e c o r a t o r s
c l a s s DrinkComponent :
def getDescription ( s e l f ) :
return s e l f . c l a s s . name
def getTotalCost ( s e l f ) :
return s e l f . c l a s s . cost
c l a s s E s p r e s s o ( DrinkComponent ) :
cost = 0.75
c l a s s EspressoConPanna ( DrinkComponent ) :
cost = 1.0
c l a s s Cappuccino ( DrinkComponent ) :
cost = 1.0
c l a s s C a f e L a t t e ( DrinkComponent ) :
84
cost = 1.0
c l a s s CafeMocha ( DrinkComponent ) :
cost = 1.25
c l a s s D e c o r a t o r ( DrinkComponent ) :
def i n i t ( s e l f , drinkComponent ) :
s e l f . component = drinkComponent
def getTotalCost ( s e l f ) :
r e t u r n s e l f . component . g e t T o t a l C o s t ( ) + \
DrinkComponent . g e t T o t a l C o s t ( s e l f )
def getDescription ( s e l f ) :
r e t u r n s e l f . component . g e t D e s c r i p t i o n ( ) + \
’ ’ + DrinkComponent . g e t D e s c r i p t i o n ( s e l f )
c l a s s ExtraEspresso ( Decorator ) :
cost = 0.75
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Whipped ( D e c o r a t o r ) :
cost = 0.50
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Decaf ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Dry ( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
c l a s s Wet( D e c o r a t o r ) :
cost = 0.0
def i n i t ( s e l f , drinkComponent ) :
D e c o r a t o r . i n i t ( s e l f , drinkComponent )
85
c a p p u c c i n o = Cappuccino ( )
p r i n t c a p p u c c i n o . g e t D e s c r i p t i o n ( ) + ” : \$” + \
‘ cappuccino . getTotalCost ( ) ‘
86
8.7 Ejercicios
1. Añadir una clase Syrup al enfoque decorador descrito anterior-
mente. A continuación, cree un Café Latte (usted necesitará usar
la leche al vapor con un expreso) con Syrup.
2. Repita el ejercicio 1 para el enfoque de compromiso.
3. Implementar el patrón decorador para crear un restaurante de
Pizza, el cual tenga un menú de opciones, ası́ como la opción de
diseñar su propia pizza. Siga el enfoque de compromiso para crear
un menú que consiste en una Margherita, hawaianas, Regina, y
pizzas vegetarianas, con relleno (decoradores) de ajo, aceitunas,
espinacas, aguacate, queso feta y Pepperdews. Crear una pizza
hawaiana, ası́ como un Margherita decorado con espinacas, queso
feta, Pepperdews y aceitunas.
87
9 Y: Iteradores:
Algoritmos de desacoplamiento de contene-
dores
Este capı́tulo no ha tenido traducción significativa todavı́a.
88
técnicas de programación genéricas / funcionales. Este capı́tulo ex-
plorará estas técnicas mediante la conversión de los algoritmos de
STL para Java, para su uso con la librerı́a de contenedor Java 2.
89
original o Iterator, de modo que se puede utilizar en los mismos
lugares (puede argumentar que esto es en realidad un patrón Proxy,
pero es más probable Decorator debido a su intención). Aquı́ está
el código:
# u t i l : T y p e d I t e r a t o r . py
c l a s s TypedIterator ( I t e r a t o r ) :
p r i v a t e I t e r a t o r imp
p r i v a t e C l a s s type
def i n i t ( s e l f , I t e r a t o r i t , C l a s s type ) :
imp = i t
s e l f . type = type
d e f hasNext ( s e l f ) :
r e t u r n imp . hasNext ( )
10 5: Fábricas:
encapsular
la creación de objetos
Cuando descubre que es necesario agregar nuevos tipos a un sistema,
el primer paso más sensato es utilizar el polimorfismo para crear una
interfaz común a esos nuevos tipos. Esto separa el resto del código
en el sistema desde el conocimiento de los tipos especı́ficos que está
agregando. Nuevos tipos pueden añadirse sin molestar código exis-
tente ... o al menos eso parece. Al principio parecerı́a que el único
lugar que necesita cambiar el código en tal diseño es el lugar donde
90
usted hereda un nuevo tipo, pero esto no es del todo cierto. Usted
todavı́a debe crear un objeto de su nuevo tipo, y en el punto de
la creación debe especificar el constructor exacto a utilizar. Ası́, si
el código que crea objetos se distribuye a través de su aplicación,
usted tiene el mismo problema cuando añade nuevos tipos — usted
todavı́a debe perseguir todos los puntos de su código en asuntos de
tipos. Esto sucede para ser la creation : creación del tipo que im-
porta en este caso en lugar del use : uso del tipo (que es atendido
por el polimorfismo), pero el efecto es el mismo : la adición de un
nuevo tipo puede causar problemas.
#: c05 : s h a p e f a c t 1 : ShapeFactory1 . py
# A s i m p l e s t a t i c f a c t o r y method .
from future import g e n e r a t o r s
import random
c l a s s Shape ( o b j e c t ) :
# Create based on c l a s s name :
d e f f a c t o r y ( type ) :
#r e t u r n e v a l ( type + ” ( ) ” )
i f type == ” C i r c l e ” : r e t u r n C i r c l e ( )
91
i f type == ” Square ” : r e t u r n Square ( )
a s s e r t 1 , ”Bad shape c r e a t i o n : ” + type
factory = staticmethod ( factory )
c l a s s C i r c l e ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” C i r c l e . draw”
def erase ( s e l f ) : print ” Circle . erase ”
c l a s s Square ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” Square . draw”
d e f e r a s e ( s e l f ) : p r i n t ” Square . e r a s e ”
shapes = \
[ Shape . f a c t o r y ( i ) f o r i i n shapeNameGen ( 7 ) ]
f o r shape i n s h a p e s :
shape . draw ( )
shape . e r a s e ( )
#:˜
factory( ) toma un argumento que le permite determinar qué
tipo de Shape para crear; que pasa a ser un String en este caso,
pero podrı́a ser cualquier conjunto de datos. factory( ) es ahora
el único otro código en el sistema que necesita ser cambiado cuando
untipo nuevo de Shape es agregado (los datos de inicialización de los
objetos presumiblemente vendrán de alguna parte fuera del sistema,
y no son una matriz de codificación fija como en el ejemplo anterior).
92
cial de una fábrica: es una fábrica que no toma ningún argumento
con el fin de crear un nuevo objeto. Normalmente usted entrega
alguna información a una fábrica con el fin de decirle qué tipo de
objeto para crear y cómo crearlo,pero generador tiene algún tipo de
algoritmo interno que le dice qué y cómo construir. Esto ”genera de
la nada” en vez de estar diciendo qué crear.
f o r i i n shapeNameGen ( 7 )
parece que hay una inicialización teniendo lugar. Aquı́ es donde
un generador es un poco extraño – cuando llama una función que
contiene una declaración yield (yield es una nueva palabra clave
que determina que una función es un generador), esa función en
realidad devuelve un objeto generador que tiene un iterador. Este
iterador se utiliza implı́citamente en la sentencia for anterior, por
lo que parece que se está iterando a través de la función generador,
no lo que devuelve. Esto se hizo para la conveniencia de uso.
gen = shapeNameGen ( 7 )
p r i n t gen . next ( )
Ası́ que next() es el método iterador que es realmente llamado
a generar el siguiente objeto, y que no toma ningún argumento.
shapeNameGen( ) es la fábrica, y gen es el generador.
93
profunda de esta manera, debe recurse16 la lista subclasses ( ).
t y p e s = Shape . subclasses ()
Sólo se ejecuta cuando se produce el objeto generador; cada vez
que se llama al método next( ) de este objeto generador (que, como
se señaló anteriormente, puede suceder de manera implı́cita), sólo
se ejecuta el código en el bucle for, por lo que no tiene ejecución
derrochadora (como lo harı́a si esto fuera una función ordinaria).
#: c05 : s h a p e f a c t 2 : ShapeFactory2 . py
# Polymorphic f a c t o r y methods .
from future import g e n e r a t o r s
import random
c l a s s ShapeFactory :
16 utilizar la recursión en la programación, usar funciones recursivas (que se repiten) en la
creación de un programa
94
f a c t o r i e s = {}
d e f addFactory ( id , s h a p e F a c t o r y ) :
ShapeFactory . f a c t o r i e s . put [ i d ] = s h a p e F a c t o r y
addFactory = s t a t i c m e t h o d ( addFactory )
# A Template Method :
def createShape ( id ) :
i f not ShapeFactory . f a c t o r i e s . h a s k e y ( i d ) :
ShapeFactory . f a c t o r i e s [ i d ] = \
e v a l ( i d + ’ . Factory ( ) ’ )
r e t u r n ShapeFactory . f a c t o r i e s [ i d ] . c r e a t e ( )
createShape = staticmethod ( createShape )
c l a s s Shape ( o b j e c t ) : p a s s
c l a s s C i r c l e ( Shape ) :
d e f draw ( s e l f ) : p r i n t ” C i r c l e . draw”
def erase ( s e l f ) : print ” Circle . erase ”
c l a s s Factory :
def create ( s e l f ) : return Circle ()
c l a s s Square ( Shape ) :
d e f draw ( s e l f ) :
p r i n t ” Square . draw”
def erase ( s e l f ) :
p r i n t ” Square . e r a s e ”
c l a s s Factory :
d e f c r e a t e ( s e l f ) : r e t u r n Square ( )
d e f shapeNameGen ( n ) :
t y p e s = Shape . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d random . c h o i c e ( t y p e s ) . n a m e
s h a p e s = [ ShapeFactory . c r e a t e S h a p e ( i )
f o r i i n shapeNameGen ( 7 ) ]
f o r shape i n s h a p e s :
shape . draw ( )
shape . e r a s e ( )
95
#:˜
Ahora el método de fábrica aparece en su propia clase, ShapeFac-
tory, como el método create( ). Los diferentes tipos de formas
deben crear cada uno su propia fábrica con un método create(
) para crear un objeto de su propio tipo. La creación real de for-
mas se realiza llamando ShapeFactory.createShape( ), que es un
método estático que utiliza el diccionario en ShapeFactory para
encontrar el objeto de fábrica apropiado basado en un identificador
que se le pasa. La fábrica se utiliza de inmediato para crear el objeto
shape : forma, pero se puede imaginar un problema más complejo
donde se devuelve el objeto de fábrica apropiado y luego utilizado
por la persona que llama para crear un objeto de una manera más
sofisticada. Ahora bien, parece que la mayor parte del tiempo usted
no necesita la complejidad del método de fábrica polimórfico, y un
solo método estático en la clase base (como se muestra en Shape-
Factory1.py) funcionará bien. Observe que ShapeFactory debe
ser inicializado por la carga de su diccionario con objetos de fábrica,
que tiene lugar en la cláusula de inicialización estática de cada una
de las implementaciones de forma.
96
torno de juego de uso general y usted quiere ser capaz de soportar
diferentes tipos de juegos. Ası́ es cómo puede parecer utilizando una
fábrica abstracta:
#: c05 : Games . py
# An example o f t he A b s t r a c t Factory p a t t e r n .
c l a s s Obstacle :
def action ( s e l f ) : pass
c l a s s Player :
def interactWith ( s e l f , obstacle ) : pass
c l a s s Kitty ( Player ) :
def interactWith ( s e l f , obstacle ) :
p r i n t ” K i t t y has e n c o u n t e r e d a ” ,
obstacle . action ()
c l a s s KungFuGuy( P l a y e r ) :
def interactWith ( s e l f , obstacle ) :
p r i n t ”KungFuGuy now b a t t l e s a ” ,
obstacle . action ()
c l a s s Puzzle ( Obstacle ) :
def action ( s e l f ) :
print ” Puzzle ”
c l a s s NastyWeapon ( O b s t a c l e ) :
def action ( s e l f ) :
p r i n t ”NastyWeapon”
# The A b s t r a c t Factory :
class GameElementFactory :
def makePlayer ( s e l f ) : p a s s
def makeObstacle ( s e l f ) : p a s s
# Concrete f a c t o r i e s :
c l a s s K i t t i e s A n d P u z z l e s ( GameElementFactory ) :
d e f makePlayer ( s e l f ) : r e t u r n K i t t y ( )
97
d e f makeObstacle ( s e l f ) : r e t u r n P u z z l e ( )
c l a s s KillAndDismember ( GameElementFactory ) :
d e f makePlayer ( s e l f ) : r e t u r n KungFuGuy ( )
d e f makeObstacle ( s e l f ) : r e t u r n NastyWeapon ( )
c l a s s GameEnvironment :
def i n i t ( self , factory ):
s e l f . factory = factory
s e l f . p = f a c t o r y . makePlayer ( )
s e l f . ob = f a c t o r y . makeObstacle ( )
def play ( s e l f ) :
s e l f . p . i n t e r a c t W i t h ( s e l f . ob )
g1 = GameEnvironment ( K i t t i e s A n d P u z z l e s ( ) )
g2 = GameEnvironment ( KillAndDismember ( ) )
g1 . p l a y ( )
g2 . p l a y ( )
#:˜
En este entorno, los objetos Player interactúan con los objetos
Obstacle pero hay diferentes tipos de jugadores y obstáculos, de-
pendiendo de qué tipo de juego está jugando. Usted determina el
tipo de juego al elegir un determinado GameElementFactory, y
luego el GameEnvironment controla la configuración y el desar-
rollo del juego. En este ejemplo, la configuración y el juego es muy
simple, pero esas actividades (las initial conditions : condiciones
iniciales y el state change : cambio de estado) pueden determinar
gran parte el resultado del juego. Aquı́, GameEnvironment no
está diseñado para ser heredado, aunque podrı́a muy posiblemente
tener sentido hacer eso.
98
Python concretas siguen la forma de las clases obligatorias, no nece-
sitamos ninguna clase de base:
#: c05 : Games2 . py
# S i m p l i f i e d A b s t r a c t Factory .
c l a s s Kitty :
def interactWith ( s e l f , obstacle ) :
p r i n t ” K i t t y has e n c o u n t e r e d a ” ,
obstacle . action ()
c l a s s KungFuGuy :
def interactWith ( s e l f , obstacle ) :
p r i n t ”KungFuGuy now b a t t l e s a ” ,
obstacle . action ()
c l a s s Puzzle :
def action ( s e l f ) : print ” Puzzle ”
c l a s s NastyWeapon :
d e f a c t i o n ( s e l f ) : p r i n t ”NastyWeapon”
# Concrete f a c t o r i e s :
c l a s s KittiesAndPuzzles :
d e f makePlayer ( s e l f ) : r e t u r n K i t t y ( )
d e f makeObstacle ( s e l f ) : r e t u r n P u z z l e ( )
c l a s s KillAndDismember :
d e f makePlayer ( s e l f ) : r e t u r n KungFuGuy ( )
d e f makeObstacle ( s e l f ) : r e t u r n NastyWeapon ( )
c l a s s GameEnvironment :
def i n i t ( self , factory ):
s e l f . factory = factory
s e l f . p = f a c t o r y . makePlayer ( )
s e l f . ob = f a c t o r y . makeObstacle ( )
def play ( s e l f ) :
s e l f . p . i n t e r a c t W i t h ( s e l f . ob )
99
g1 = GameEnvironment ( K i t t i e s A n d P u z z l e s ( ) )
g2 = GameEnvironment ( KillAndDismember ( ) )
g1 . p l a y ( )
g2 . p l a y ( )
#:˜
Otra manera de poner esto es que toda la herencia en Python es
la herencia de implementación; ya que Python hace su la compro-
bación de tipo en tiempo de ejecución, no hay necesidad de utilizar
la herencia de interfaces para que pueda upcast al tipo base.
c l a s s Obstacle : pass
c l a s s Player : pass
c l a s s GameElementFactory : p a s s
A continuación, la herencia sólo sirve para indicar el tipo de las
clases derivadas.
10.4 Ejercicios
1. Agregar la clase Triangle a ShapeFactory1.py
2. Agregar la clase Triangle a ShapeFactory2.py
3. Agregar un nuevo tipo de GameEnvironment llamado Gnome-
sAndFairies a GameEnvironment.py
4. Modificar ShapeFactory2.py para que utilice una Abstract Fac-
tory para crear diferentes conjuntos de formas (por ejemplo, un tipo
particular de objeto de fábrica crea ”formas gruesas,” otra crea ”for-
mas delgadas,” pero cada objeto fábrica puede crear todas las for-
mas: cı́rculos, cuadrados, triángulos, etc.).
100
11 6 : Objetos de función
En Advanced C++:Programming Styles And Idioms (Addison-Wesley,
1992), Jim Coplien acuña el término functor que es un objeto cuyo
único propósito es encapsular una función (ya que ”Functor” tiene
un significado en matemáticas, Voy a utilizar el término más explı́cito
function object). El punto es desacoplar la elección de la función que
se llamará desde el sitio en que esa función se llama.
#: c06 : CommandPattern . py
c l a s s Command :
def execute ( s e l f ) : pass
c l a s s Loony (Command ) :
def execute ( s e l f ) :
p r i n t ”You ’ r e a l o o n y . ”
c l a s s NewBrain (Command ) :
def execute ( s e l f ) :
p r i n t ”You might even need a new b r a i n . ”
c l a s s A f f o r d (Command ) :
17 En el lenguaje Python, todas las funciones son ya objetos y ası́ el patrón Comando suele
ser redundante.
101
def execute ( s e l f ) :
p r i n t ” I couldn ’ t a f f o r d a whole new b r a i n . ”
# An o b j e c t t h a t h o l d s commands :
c l a s s Macro :
def init ( self ):
s e l f . commands = [ ]
d e f add ( s e l f , command ) :
s e l f . commands . append (command )
d e f run ( s e l f ) :
f o r c i n s e l f . commands :
c . execute ()
macro = Macro ( )
macro . add ( Loony ( ) )
macro . add ( NewBrain ( ) )
macro . add ( A f f o r d ( ) )
macro . run ( )
#:˜
El punto primario de Command es para que pueda entregar
una acción deseada a un método u objeto. En el ejemplo ante-
rior, esto proporciona una manera de hacer cola en un conjunto
de acciones a realizar colectivamente. En este caso, ello le permite
crear dinámicamente un nuevo comportamiento, algo que sólo puede
hacer normalmente escribiendo nuevo código, pero en el ejemplo an-
terior se podrı́a hacer mediante la interpretación de un script (ver el
patrón Interpreter : intérprete si lo que necesita hacer se pone muy
complejo).
102
Combino un grupo de patrones de diseño bajo el tı́tulo de ”devolu-
ciones de llamada.”
103
#: c06 : S t r a t e g y P a t t e r n . py
# The s t r a t e g y i n t e r f a c e :
c l a s s FindMinima :
# Line i s a s e q u e n c e o f p o i n t s :
def algorithm ( s e l f , l i n e ) : pass
# The v a r i o u s s t r a t e g i e s :
c l a s s L e a s t S q u a r e s ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 1 . 1 , 2 . 2 ] # Dummy
c l a s s NewtonsMethod ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 3 . 3 , 4 . 4 ] # Dummy
c l a s s B i s e c t i o n ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 5 . 5 , 6 . 6 ] # Dummy
c l a s s ConjugateGr adient ( FindMinima ) :
def algorithm ( s e l f , l i n e ) :
r e t u r n [ 3 . 3 , 4 . 4 ] # Dummy
# The ” Context ” c o n t r o l s t he s t r a t e g y :
c l a s s MinimaSolver :
def i n i t ( self , strategy ):
s e l f . strategy = strategy
d e f minima ( s e l f , l i n e ) :
return s e l f . strategy . algorithm ( l i n e )
d e f changeAlgorithm ( s e l f , newAlgorithm ) :
s e l f . s t r a t e g y = newAlgorithm
s o l v e r = MinimaSolver ( L e a s t S q u a r e s ( ) )
line = [
1 . 0 , 2 . 0 , 1 . 0 , 2 . 0 , −1.0 , 3 . 0 , 4 . 0 , 5 . 0 , 4 . 0
]
p r i n t s o l v e r . minima ( l i n e )
s o l v e r . changeAlgorithm ( B i s e c t i o n ( ) )
p r i n t s o l v e r . minima ( l i n e )
#:˜
104
Observe similitud con el método de plantilla – TM afirma dis-
tinción que este tiene más de un método para llamar, hace las cosas
por partes. Ahora bien, no es probable que la estrategia de ob-
jeto tendrı́a más de una llamada al método; considerar sistema de
cumplimiento de la orden de Shalloway.con información de los paı́ses
en cada estrategia.
105
11.3 Cadena de responsabilidad
Chain of Responsibility : Cadena de responsabilidad podrı́a ser pen-
sado como una generalización dinámica de recursividad utilizando
objetos Strategy. Usted hace una llamada, y cada Strategy en una
secuencia vinculada trata de satisfacer la llamada. El proceso ter-
mina cuando una de las estrategias es exitosa o termina la cadena.
En la recursividad, un método se llama a sı́ mismo una y otra vez
hasta que se alcance una condición de terminación; con Chain of
Responsibility : Cadena de responsabilidad, un método se llama a sı́
mismo, que (moviendo por la cadena de Strategies) llama una im-
plementación diferente del método, etc., hasta que se alcanza una
condición de terminación. La condición de terminación es o bien que
se alcanza la parte inferior de la cadena (en cuyo caso se devuelve
un objeto por defecto; usted puede o no puede ser capaz de pro-
porcionar un resultado por defecto ası́ que usted debe ser capaz de
determinar el éxito o el fracaso de la cadena) o una de las Strategies
: Estrategias tiene éxito.
106
la solución y puede fácilmente ser implementado utilizando una lista
estándar de Python, como se muestra más abajo. Además, usted
verá que he ido a algún esfuerzo para separar las partes de gestión de
la cadena de la implementación de las distintas Strategies, de modo
que el código puede ser más fácilmente reutilizado.
#: c06 : C h a i n O f R e s p o n s i b i l i t y . py
# Carry th e i n f o r m a t i o n i n t o t he s t r a t e g y :
c l a s s Messenger : p a s s
c l a s s Strategy :
def c a l l ( messenger ) : p a s s
def str ( self ):
r e t u r n ” Trying ” + s e l f . c l a s s . name \
+ ” algorithm ”
107
s e l f . chain = chain
s e l f . c h a i n . append ( s e l f )
d e f next ( s e l f ) :
# Where t h i s l i n k i s i n th e c h a i n :
l o c a t i o n = s e l f . chain . index ( s e l f )
i f not s e l f . end ( ) :
return s e l f . chain [ l o c a t i o n + 1]
d e f end ( s e l f ) :
r e t u r n ( s e l f . c h a i n . i n d e x ( s e l f ) + 1 >=
len ( s e l f . chain ))
def c a l l ( s e l f , messenger ) :
r = s e l f . s t r a t e g y ( messenger )
i f r . i s S u c c e s s f u l ( ) o r s e l f . end ( ) : r e t u r n r
r e t u r n s e l f . next ( ) ( messenger )
c l a s s LeastSquares ( Strategy ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 1 . 1 , 2 . 2 ] ) # Dummy data
result . setSuccessful (0)
return r e s u l t
c l a s s NewtonsMethod ( S t r a t e g y ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 3 . 3 , 4 . 4 ] ) # Dummy data
108
result . setSuccessful (0)
return r e s u l t
c l a s s Bisection ( Strategy ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 5 . 5 , 6 . 6 ] ) # Dummy data
result . setSuccessful (1)
return r e s u l t
c l a s s ConjugateGr adient ( S t r a t e g y ) :
def c a l l ( s e l f , messenger ) :
print s e l f
l i n e d a t a = messenger
# [ Actual t e s t / c a l c u l a t i o n h e r e ]
r e s u l t = LineData ( [ 7 . 7 , 8 . 8 ] ) # Dummy data
result . setSuccessful (1)
return r e s u l t
solutions = [ ]
solutions = [
ChainLink ( s o l u t i o n s , LeastSquares ( ) ) ,
ChainLink ( s o l u t i o n s , NewtonsMethod ( ) ) ,
ChainLink ( s o l u t i o n s , Bisection ()) ,
ChainLink ( s o l u t i o n s , ConjugateGradient ( ) )
]
l i n e = LineData ( [
1 . 0 , 2 . 0 , 1 . 0 , 2 . 0 , −1.0 ,
3.0 , 4.0 , 5.0 , 4.0
])
print solutions [ 0 ] ( line )
#:˜
109
11.4 Ejercicios
1. Usar Command en el capitulo 3, Ejercicio 1.
2. Implementar Chain of Responsibility: cadena de responsabilidad
para crear un ”sistema experto” que resuelva problemas, tratando
sucesivamente una única solución tras otro hasta que uno coincida.
Usted debe ser capaz de añadir dinámicamente soluciones para el
sistema experto. La prueba para la solución simplemente debe ser
un juego de string, pero cuando una solución se ajusta, el sistema
experto debe devolver el tipo apropiado de objeto ProblemSolver.
12 7: Cambiando la interfaz.
A veces el problema que usted está resolviendo es tan simple como:
”Yo no tengo la interfaz que quiero.” Dos de los patrones en Design
Patterns : Diseño de Patrones resuelven este problema: Adapter :
Adaptador toma un tipo y produce una interfaz a algún otro tipo.
Façade: Fachada crea una interfaz para un conjunto de clases, sim-
plemente para proporcionar una manera más cómoda para hacer
frente a una biblioteca o un paquete de recursos.
c l a s s WhatIHave :
def g ( s e l f ) : pass
def h( s e l f ) : pass
c l a s s WhatIWant :
def f ( s e l f ) : pass
c l a s s ProxyAdapter ( WhatIWant ) :
def i n i t ( s e l f , whatIHave ) :
110
s e l f . whatIHave = whatIHave
def f ( s e l f ) :
# Implement b e h a v i o r u s i n g
# methods i n WhatIHave :
s e l f . whatIHave . g ( )
s e l f . whatIHave . h ( )
c l a s s WhatIUse :
d e f op ( s e l f , whatIWant ) :
whatIWant . f ( )
# Approach 2 : b u i l d a d a p t e r use i n t o op ( ) :
c l a s s WhatIUse2 ( WhatIUse ) :
d e f op ( s e l f , whatIHave ) :
ProxyAdapter ( whatIHave ) . f ( )
# Approach 3 : b u i l d a d a p t e r i n t o WhatIHave :
c l a s s WhatIHave2 ( WhatIHave , WhatIWant ) :
def f ( s e l f ) :
s e l f . g ()
s e l f .h()
# Approach 4 : use an i n n e r c l a s s :
c l a s s WhatIHave3 ( WhatIHave ) :
c l a s s InnerAdapter ( WhatIWant ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
def f ( s e l f ) :
s e l f . outer . g ()
s e l f . outer . h ()
d e f whatIWant ( s e l f ) :
r e t u r n WhatIHave3 . InnerAdapter ( s e l f )
whatIUse = WhatIUse ( )
whatIHave = WhatIHave ( )
adapt= ProxyAdapter ( whatIHave )
whatIUse2 = WhatIUse2 ( )
111
whatIHave2 = WhatIHave2 ( )
whatIHave3 = WhatIHave3 ( )
whatIUse . op ( adapt )
# Approach 2 :
whatIUse2 . op ( whatIHave )
# Approach 3 :
whatIUse . op ( whatIHave2 )
# Approach 4 :
whatIUse . op ( whatIHave3 . whatIWant ( ) )
#:˜
Estoy tomando libertades con el término ”proxy” aquı́, porque
en Design Patterns afirman que un proxy debe tener una interfaz
idéntica con el objeto que es para un sustituto : surrogate. Sin em-
bargo, si tiene las dos palabras juntas: ”adaptador de proxy,” tal
vez sea más razonable.
# c07 : Facade . py
c l a s s A:
def i n i t ( s e l f , x ) : pass
c l a s s B:
def i n i t ( s e l f , x ) : pass
c l a s s C:
def i n i t ( s e l f , x ) : pass
112
# f a c a d e go h e r e . . .
c l a s s Facade :
d e f makeA( x ) : r e t u r n A( x )
makeA = s t a t i c m e t h o d (makeA)
d e f makeB ( x ) : r e t u r n B( x )
makeB = s t a t i c m e t h o d ( makeB )
d e f makeC( x ) : r e t u r n C( x )
makeC = s t a t i c m e t h o d (makeC)
# The c l i e n t programmer g e t s t h e o b j e c t s
# by c a l l i n g t he s t a t i c methods :
a = Facade . makeA ( 1 ) ;
b = Facade . makeB ( 1 ) ;
c = Facade . makeC ( 1 . 0 ) ;
# :˜
[reescribir esta sección utilizando la investigación del libro de Lar-
man]
12.3 Ejercicios
1. Crear una clase adaptador que carga automáticamente una matriz
bidimensional de objetos en un diccionario como pares clave-valor.
113
13 Código Tabla impulsada:
flexibilidad de configuración
Código de la tabla dirigido por el uso de clases internas anónimas
Véase el ejemplo ListPerformance en TIJ del Capı́tulo 9.
También GreenHouse.py
114
14 Devoluciones de Llamada
Desacoplamiento comportamiento código.
115
En realidad, hay dos ”cosas que cambian” en el patrón obser-
vador: la cantidad de objetos de observación y la forma se produce
una actualización. Es decir, el patrón observador permite modificar
ambos sin afectar el código circundante.
116
Al principio puede parecer que usted puede utilizar un objeto
ordinario Observable para administrar las actualizaciones. Pero
esto no funciona; para obtener un efecto, usted debe heredar de
Observable y en algún lugar en el código de la clase derivada lla-
mar setChanged( ). Esta es la función miembro que establece la
bandera ”cambiado”, lo que significa que cuando se llama notify-
Observers( ) todos los observadores serán, de hecho, serán notifi-
cados. Cuando usted llama setChanged( ) depende de la lógica
de su programa.
117
14.1.1 Observando Flores
import t h r e a d i n g
c l a s s ToSynch :
def init ( self ):
s e l f . mutex = t h r e a d i n g . RLock ( )
s e l f . val = 1
d e f aSynchronizedMethod ( s e l f ) :
s e l f . mutex . a c q u i r e ( )
try :
s e l f . v a l += 1
return s e l f . val
finally :
s e l f . mutex . r e l e a s e ( )
Pero esto se convierte rápidamente tedioso de escribir y de leer.
Peter Norvig me proporcionó una solución mucho más agradable:
#: u t i l : S y n c h r o n i z a t i o n . py
’ ’ ’ Simple e m u l a t i o n o f Java ’ s ’ s y n c h r o n i z e d ’
keyword , from P e t e r Norvig . ’ ’ ’
import t h r e a d i n g
d e f s y n c h r o n i z e d ( method ) :
d e f f (∗ a r g s ) :
s e l f = args [ 0 ]
s e l f . mutex . a c q u i r e ( ) ;
118
# p r i n t method . na me , ’ a c q u i r e d ’
try :
r e t u r n apply ( method , a r g s )
finally :
s e l f . mutex . r e l e a s e ( ) ;
# p r i n t method . na me , ’ r e l e a s e d ’
return f
d e f s y n c h r o n i z e ( k l a s s , names=None ) :
””” S y n c h r o n i z e methods i n th e g i v e n c l a s s .
Only s y n c h r o n i z e th e methods whose names a r e
given , o r a l l methods i f names=None . ” ” ”
i f type ( names)==type ( ’ ’ ) : names = names . s p l i t ( )
f o r ( name , v a l ) i n k l a s s . d i c t . i t e m s ( ) :
i f c a l l a b l e ( v a l ) and name != ’ i n i t ’ and \
( names == None o r name i n names ) :
# p r i n t ” s y n c h r o n i z i n g ” , name
k l a s s . d i c t [ name ] = s y n c h r o n i z e d ( v a l )
r e t u r n apply ( method , a r g s )
y como la sentencia return pasa a través de la cláusula finally,
el mutex es liberado.
myMethod = s y n c h r o n i z e d ( myMethod )
119
Para rodear su método con un mutex.
#: u t i l : T e s t S y n c h r o n i z a t i o n . py
from S y n c h r o n i z a t i o n import ∗
# To use f o r a method :
c l a s s C( S y n c h r o n i z a t i o n ) :
def init ( self ):
Synchronization . i n i t ( s e l f )
s e l f . data = 1
d e f m( s e l f ) :
s e l f . data += 1
r e t u r n s e l f . data
m = s y n c h r o n i z e d (m)
d e f f ( s e l f ) : r e t u r n 47
d e f g ( s e l f ) : r e t u r n ’ spam ’
# So m i s s y n c h r o n i z e d , f and g a r e not .
c = C( )
# On th e c l a s s l e v e l :
c l a s s D(C ) :
def init ( self ):
C. i n i t ( s e l f )
# You must o v e r r i d e an un−s y n c h r o n i z e d method
# i n o r d e r t o s y n c h r o n i z e i t ( j u s t l i k e Java ) :
120
d e f f ( s e l f ) : C. f ( s e l f )
# S y n c h r o n i z e e v e r y ( d e f i n e d ) method i n t h e c l a s s :
s y n c h r o n i z e (D)
d = D( )
d . f ( ) # Synchronized
d . g ( ) # Not s y n c h r o n i z e d
d .m( ) # S y n c h r o n i z e d ( i n t he base c l a s s )
c l a s s E(C ) :
def init ( self ):
C. i n i t ( s e l f )
d e f m( s e l f ) : C.m( s e l f )
d e f g ( s e l f ) : C. g ( s e l f )
d e f f ( s e l f ) : C. f ( s e l f )
# Only s y n c h r o n i z e s m and g . Note t h a t m ends up
# b e i n g doubly−wrapped i n s y n c h r o n i z a t i o n , which
# doesn ’ t h urt a n y t h i n g but i s i n e f f i c i e n t :
s y n c h r o n i z e (E , ’m g ’ )
e = E( )
e . f ()
e . g ()
e .m( )
#:˜
Usted debe llamar al constructor de la clase base para Synchro-
nization, pero esto es todo. En la clase C puede ver el uso de
Synchronized() para m, dejando f y g solos. Clase D tiene todos
sus métodos sincronizados en masa, y la clase E utiliza la función
de conveniencia para sincronizar m y g. Tenga en cuenta que dado
que m termina siendo sincronizado en dos ocasiones, ello se entró y
salió dos veces para cada llamada, que no es muy deseable [puede
haber una corrección para este].
#: u t i l : Observer . py
# Class support f o r ” observer ” pattern .
from S y n c h r o n i z a t i o n import ∗
c l a s s Observer :
d e f update ( o b s e r v a b l e , ar g ) :
121
’ ’ ’ C a l l e d when t h e o b s e r v e d o b j e c t i s
m o d i f i e d . You c a l l an Ob s e r v a b le o b j e c t ’ s
n o t i f y O b s e r v e r s method t o n o t i f y a l l th e
o b j e c t ’ s o b s e r v e r s o f t he change . ’ ’ ’
pass
c l a s s O b s e r va b l e ( S y n c h r o n i z a t i o n ) :
def init ( self ):
s e l f . obs = [ ]
s e l f . changed = 0
Synchronization . i n i t ( s e l f )
d e f addObserver ( s e l f , o b s e r v e r ) :
i f o b s e r v e r not i n s e l f . obs :
s e l f . obs . append ( o b s e r v e r )
d e f n o t i f y O b s e r v e r s ( s e l f , ar g = None ) :
’ ’ ’ I f ’ changed ’ i n d i c a t e s t h a t t h i s o b j e c t
has changed , n o t i f y a l l i t s o b s e r v e r s , then
c a l l clearChanged ( ) . Each o b s e r v e r has i t s
update ( ) c a l l e d with two arguments : t h i s
o b s e r v a b l e o b j e c t and t he g e n e r i c ’ arg ’ . ’ ’ ’
s e l f . mutex . a c q u i r e ( )
try :
i f not s e l f . changed : r e t u r n
# Make a l o c a l copy i n c a s e o f s ynchr onous
# additions of observers :
l o c a l A r r a y = s e l f . obs [ : ]
s e l f . clearChanged ( )
finally :
s e l f . mutex . r e l e a s e ( )
# Updating i s not r e q u i r e d t o be s y n c h r o n i z e d :
for observer in localArray :
o b s e r v e r . update ( s e l f , a r g )
122
def d e l e t e O b s e r v e r s ( s e l f ) : s e l f . obs = [ ]
def setChanged ( s e l f ) : s e l f . changed = 1
def clearChanged ( s e l f ) : s e l f . changed = 0
def hasChanged ( s e l f ) : r e t u r n s e l f . changed
def c o u n t O b s e r v e r s ( s e l f ) : r e t u r n l e n ( s e l f . obs )
s y n c h r o n i z e ( Observable ,
” addObserver d e l e t e O b s e r v e r d e l e t e O b s e r v e r s ” +
” setChanged clearChanged hasChanged ” +
” countObservers ”)
#:˜
Usando esta librerı́a, aquı́ esta un ejemplo de el patrón obser-
vador:
#: c10 : ObservedFlower . py
# Demonstration o f ” o b s e r v e r ” p a t t e r n .
import s y s
s y s . path += [ ’ . . / u t i l ’ ]
from Observer import Observer , O b s e r v ab l e
c l a s s Flower :
def init ( self ):
s e l f . isOpen = 0
s e l f . o p e n N o t i f i e r = Flower . O p e n N o t i f i e r ( s e l f )
s e l f . c l o s e N o t i f i e r= Flower . C l o s e N o t i f i e r ( s e l f )
d e f open ( s e l f ) : # Opens i t s p e t a l s
s e l f . isOpen = 1
s e l f . openNotifier . notifyObservers ()
s e l f . c l o s e N o t i f i e r . open ( )
def close ( s e l f ) : # Closes i t s petals
s e l f . isOpen = 0
s e l f . clos eNo tifi er . notifyObservers ()
s e l f . openNotifier . close ()
def closing ( s e l f ) : return s e l f . c l o s e N o t i f i e r
c l a s s O p e n N o t i f i e r ( Ob s e r v a b le ) :
def i n i t ( s e l f , outer ) :
O b s e rv a b l e . i n i t ( s e l f )
s e l f . outer = outer
s e l f . alreadyOpen = 0
123
def notifyObservers ( s e l f ) :
i f s e l f . o u t e r . isOpen and \
not s e l f . alreadyOpen :
s e l f . setChanged ( )
O b s er v a b l e . n o t i f y O b s e r v e r s ( s e l f )
s e l f . alreadyOpen = 1
def close ( s e l f ) :
s e l f . alreadyOpen = 0
c l a s s C l o s e N o t i f i e r ( O bs e r v a b le ) :
def i n i t ( s e l f , outer ) :
O b s e rv a b l e . i n i t ( s e l f )
s e l f . outer = outer
s e l f . alreadyClosed = 0
def notifyObservers ( s e l f ) :
i f not s e l f . o u t e r . isOpen and \
not s e l f . a l r e a d y C l o s e d :
s e l f . setChanged ( )
O b s er v a b l e . n o t i f y O b s e r v e r s ( s e l f )
s e l f . alreadyClosed = 1
d e f open ( s e l f ) :
alreadyClosed = 0
c l a s s Bee :
def i n i t ( s e l f , name ) :
s e l f . name = name
s e l f . openObserver = Bee . OpenObserver ( s e l f )
s e l f . c l o s e O b s e r v e r = Bee . C l o s e O b s e r v e r ( s e l f )
# An i n n e r c l a s s f o r o b s e r v i n g o p e n i n g s :
c l a s s OpenObserver ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Bee ” + s e l f . o u t e r . name + \
” ’ s b r e a k f a s t time ! ”
# Another i n n e r c l a s s f o r c l o s i n g s :
c l a s s C l o s e O b s e r v e r ( Observer ) :
def i n i t ( s e l f , outer ) :
124
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Bee ” + s e l f . o u t e r . name + \
” ’ s bed time ! ”
c l a s s Hummingbird :
def i n i t ( s e l f , name ) :
s e l f . name = name
s e l f . openObserver = \
Hummingbird . OpenObserver ( s e l f )
s e l f . closeObserver = \
Hummingbird . C l o s e O b s e r v e r ( s e l f )
c l a s s OpenObserver ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Hummingbird ” + s e l f . o u t e r . name + \
” ’ s b r e a k f a s t time ! ”
c l a s s C l o s e O b s e r v e r ( Observer ) :
def i n i t ( s e l f , outer ) :
s e l f . outer = outer
d e f update ( s e l f , o b s e r v a b l e , a rg ) :
p r i n t ”Hummingbird ” + s e l f . o u t e r . name + \
” ’ s bed time ! ”
f = Flower ( )
ba = Bee ( ” E r i c ” )
bb = Bee ( ” E r i c 0 . 5 ” )
ha = Hummingbird ( ”A” )
hb = Hummingbird ( ”B” )
f . o p e n N o t i f i e r . addObserver ( ha . openObserver )
f . o p e n N o t i f i e r . addObserver ( hb . openObserver )
f . o p e n N o t i f i e r . addObserver ( ba . openObserver )
f . o p e n N o t i f i e r . addObserver ( bb . openObserver )
f . c l o s e N o t i f i e r . addObserver ( ha . c l o s e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( hb . c l o s e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( ba . c l o s e O b s e r v e r )
f . c l o s e N o t i f i e r . addObserver ( bb . c l o s e O b s e r v e r )
125
# Hummingbird 2 d e c i d e s t o s l e e p i n :
f . o p e n N o t i f i e r . d e l e t e O b s e r v e r ( hb . openObserver )
# A change t h a t i n t e r e s t s o b s e r v e r s :
f . open ( )
f . open ( ) # I t ’ s a l r e a d y open , no change .
# Bee 1 doesn ’ t want t o go t o bed :
f . c l o s e N o t i f i e r . d e l e t e O b s e r v e r ( ba . c l o s e O b s e r v e r ) f . c l o s e ( )
f . c l o s e ( ) # I t ’ s a l r e a d y c l o s e d ; no change
f . openNotifier . deleteObservers ()
f . open ( )
f . close ()
#:˜
Los acontecimientos de interés incluyen que una Flower puede
abrir o cerrar. Debido al uso del idioma de la clase interna, estos
dos eventos pueden ser fenómenos observables por separado. Open-
Notifier y CloseNotifier ambos heredan de Observable, ası́ que
tienen acceso a setChanged( ) y pueden ser entregados a todo lo
que necesita un Observable.
126
14.2 Un ejemplo visual de Observadores
El siguiente ejemplo es similar al ejemplo ColorBoxes del Capı́tulo
14 en Thinking in Java, 2nd Edición. Las cajas se colocan en una
cuadrı́cula en la pantalla y cada uno se inicializa a un color aleatorio.
En adición, cada caja implements la interfaz Observer y es reg-
istrado con un objeto Observable.Al hacer clic en una caja, todas
las otras cajas son notificados de que un cambio se ha hecho porque
el objeto Observable llama automáticamente método update( )
de cada objeto Observer. Dentro de este método, las comproba-
ciones de caja para ver si es adyacente a la que se ha hecho clic, y
si de modo que cambia de color para que coincida con el cuadro se
hace clic.
# c10 : BoxObserver . py
# Demonstration o f Observer p a t t e r n u s i n g
# Java ’ s b u i l t −i n o b s e r v e r c l a s s e s .
c l a s s BoxObserver ( JFrame ) :
O b s er v a b l e n o t i f i e r = BoxObservable ( )
def i n i t ( self , int grid ) :
s e t T i t l e ( ” Demonstrates Observer p a t t e r n ” )
C o n t a i n e r cp = getContentPane ( )
cp . s e t L a y o u t ( GridLayout ( g r i d , g r i d ) )
f o r ( i n t x = 0 x < g r i d x++)
f o r ( i n t y = 0 y < g r i d y++)
cp . add (OCBox( x , y , n o t i f i e r ) )
127
d e f main ( s e l f , S t r i n g [ ] a r g s ) :
int grid = 8
i f ( args . length > 0)
grid = Integer . parseInt ( args [ 0 ] )
JFrame f = BoxObserver ( g r i d )
f . s e t S i z e ( 5 0 0 , 400)
f . setVisible (1)
# JDK 1 . 3 :
f . s e t D e f a u l t C l o s e O p e r a t i o n (EXIT ON CLOSE)
# Add a WindowAdapter i f you have JDK 1 . 2
s t a t i c f i n a l Color newColor ( ) :
return colors [
( i n t ) ( Math . random ( ) ∗ c o l o r s . l e n g t h )
]
def i n i t ( s e l f , i n t x , i n t y , O b s e r v ab l e
notifier ):
self .x = x
self .y = y
n o t i f i e r . addObserver ( s e l f )
self . notifier = notifier
addMouseListener (ML( ) )
d e f paintComponent ( s e l f , Graphics g ) :
s u p e r . paintComponent ( g )
g . setColor ( cColor )
Dimension s = g e t S i z e ( )
128
g . f i l l R e c t ( 0 , 0 , s . width , s . h e i g h t )
c l a s s ML( MouseAdapter ) :
d e f mousePressed ( s e l f , MouseEvent e ) :
n o t i f i e r . n o t i f y O b s e r v e r s (OCBox . s e l f )
d e f update ( s e l f , O b se r v a b l e o , Object a rg ) :
OCBox c l i c k e d = (OCBox) ar g
i f ( nextTo ( c l i c k e d ) ) :
cColor = c l i c k e d . cColor
repaint ()
p r i v a t e f i n a l b o o l e a n nextTo (OCBox b ) :
r e t u r n Math . abs ( x − b . x ) <= 1 &&
Math . abs ( y − b . y ) <= 1
# :˜
La primera vez que usted mira la documentación en lı́nea para
Observable que es un poco confuso, ya que parece que se puede
utilizar un objeto ordinario Observablepara manejar las actualiza-
ciones. Pero esto no funciona; inténtalo dentro de BoxObserver,
crea un objeto Observable en lugar de un objeto BoxObserver y
observe lo que ocurre: nada. Para conseguir un efecto, debe heredar
de Observable y en alguna parte de su código la clase derivada lla-
mada setChanged( ). Este es el método que establece la bandera
”changed : cambiado”, lo que significa que cuando se llama notify-
Observers( ) todos los observadores serán, de hecho, serán notifi-
cado. En el ejemplo anterior, setChanged( ) es simplemente lla-
mado dentro de notifyObservers( ), pero podrı́a utilizar cualquier
criterio que desee para decidir cuándo llamar setChanged( ).
129
Podrı́a parecer que la forma en que los observadores son notifi-
cados debe ser congelada en tiempo de compilación en el método
notifyObservers( ). Ahora bien, si se mira más de cerca el código
anterior usted verá que el único lugar en BoxObserver o OCBox
cuando es consciente de que usted está trabajando con una BoxOb-
servable está en el punto de la creación del objeto Observable —
de ahı́ en adelante todo lo que utiliza la interfaz básica Observable.
Esto significa que usted podrı́a heredar otras clases Observable y
intercambiarlos en tiempo de ejecución si desea cambiar el compor-
tamiento de notificación luego.
#: 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
”””
GRID = 8
130
l i n e . append ( s e l f . c r e a t e B o x ( row , column ) )
s e l f . document . append ( l i n e [ : ] )
d e f c r e a t e B o x ( s e l f , row , column ) :
c o l o r s = [ ’ black ’ , ’ blue ’ , ’ cyan ’ ,
’ darkGray ’ , ’ gray ’ , ’ green ’ ,
’ l i g h t G r a y ’ , ’ magenta ’ ,
’ orange ’ , ’ pink ’ , ’ red ’ ,
’ white ’ , ’ y e l l o w ’ ]
width , h e i g h t = s e l f . p a n e l . GetSizeTuple ( )
boxWidth = width / GRID
boxHeight = h e i g h t / GRID
l o g . i n f o ( ” width : ” + s t r ( width ) +
” height :” + str ( height ))
l o g . i n f o ( ” boxWidth : ” + s t r ( boxWidth ) +
” boxHeight : ” + s t r ( boxHeight ) )
# use an empty image , though some o t h e r
# w i d g e t s would work j u s t as w e l l
boxDesc = { ’ type ’ : ’ Image ’ ,
’ s i z e ’ : ( boxWidth , boxHeight ) , ’ f i l e ’ : ’ ’ }
name = ’ box−\%d−%d ’ % ( row , column )
# There i s p r o b a b l y a 1 o f f e r r o r i n th e
# c a l c u l a t i o n below s i n c e t he boxes s h o u l d
# p r o b a b l y have a s l i g h t l y d i f f e r e n t o f f s e t
# to prevent o v e rl a p s
boxDesc [ ’ p o s i t i o n ’ ] =
( column ∗ boxWidth , row ∗ boxHeight )
boxDesc [ ’ name ’ ] = name
boxDesc [ ’ backgroundColor ’ ] =
random . c h o i c e ( c o l o r s )
s e l f . components [ name ] = boxDesc
r e t u r n s e l f . components [ name ]
# This a l g o r i t h m w i l l r e s u l t i n changing t h e
# c o l o r o f some boxes more than once , so an
# OOP s o l u t i o n where o n l y n e i g h b o r s a r e asked
# t o change o r boxes check t o s e e i f they a r e
# n e i g h b o r s b e f o r e changing would be b e t t e r
131
# p er t he o r i g i n a l example does t h e whole g r i d
# need t o change i t s s t a t e a t once l i k e i n a
# L i f e program ? s h o u l d th e c o l o r change
# in the propogation of another n o t i f i c a t i o n
# event ?
if name == ’ m a i n ’ :
app = model . PythonCardApp ( ColorBoxesTest )
app . MainLoop ( )
#:˜
Este es el archivo de recursos para ejecutar el programa (ver
PythonCard para más detalles):
#: c10 : BoxObserver . r s r c . py
{ ’ s t a c k ’ : { ’ type ’ : ’ Stack ’ ,
’ name ’ : ’ BoxObserver ’ ,
’ backgrounds ’ : [
{ ’ type ’ : ’ Background ’ ,
’ name ’ : ’ bgBoxObserver ’ ,
’ t i t l e ’ : ’ Demonstrates Observer p a t t e r n ’ ,
’ position ’ : ( 5 , 5) ,
’ size ’ : ( 5 0 0 , 400) ,
’ components ’ : [
132
] # end components
} # end background
] # end backgrounds
} }
#:˜
14.3 Ejercicios
1. Utilizando el enfoque en Synchronization.py, crear una her-
ramienta que ajuste automáticamente todos los métodos en una
clase para proporcionar una rastreo de ejecución, de manera que
se puede ver el nombre del método y cuando entró y salió.
15 11 : Despacho Múltiple
Cuando se trata de múltiples tipos que están interactuando, un pro-
grama puede un programa puede obtener todo desordenado. Por
ejemplo, considere un sistema que analiza y ejecuta expresiones
matemáticas. Usted desea ser capaz de decir Number + Number,
Number * Number, etc., donde Number es la clase base para
una familia de objetos numéricos. Pero cuando usted dice a + b,
y no conoce el tipo exacto de uno u otro a o b, ası́ que ¿cómo se
puede conseguir que interactúen correctamente?
133
La respuesta comienza con algo que probablemente no piensas:
Python sólo realiza despacho individual. Es decir, si está realizando
una operación en más de un objeto cuyo tipo es desconocido, Python
puede invocar el mecanismo de enlace dinámico a uno sólo de esos
tipos. Esto no resuelve el problema, por lo que termina detectando
algunos tipos manualmente y produciendo con eficacia su propio
comportamiento de enlace dinámico.
#: c11 : P a p e r S c i s s o r s R o c k . py
# Demonstration o f m u l t i p l e d i s p a t c h i n g .
from future import g e n e r a t o r s
import random
# An enumeration type :
c l a s s Outcome :
def i n i t ( s e l f , value , name ) :
s e l f . value = value
134
s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
def e q ( s e l f , other ) :
r e t u r n s e l f . v a l u e == o t h e r . v a l u e
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
#: c11 : P a p e r S c i s s o r s R o c k 2 . py
136
# Multiple dispatching using a table
from future import g e n e r a t o r s
import random
c l a s s Outcome :
def i n i t ( s e l f , value , name ) :
s e l f . value = value
s e l f . name = name
def s t r ( s e l f ) : r e t u r n s e l f . name
def e q ( s e l f , other ) :
r e t u r n s e l f . v a l u e == o t h e r . v a l u e
c l a s s Item ( o b j e c t ) :
d e f compete ( s e l f , item ) :
# Use a t u p l e f o r t a b l e lookup :
r e t u r n outcome [ s e l f . c l a s s , item . class ]
def str ( self ):
return s e l f . c l a s s . name
c l a s s Paper ( Item ) : p a s s
c l a s s S c i s s o r s ( Item ) : p a s s
c l a s s Rock ( Item ) : p a s s
outcome = {
( Paper , Rock ) : Outcome .WIN,
( Paper , S c i s s o r s ) : Outcome . LOSE,
( Paper , Paper ) : Outcome .DRAW,
( S c i s s o r s , Paper ) : Outcome .WIN,
( S c i s s o r s , Rock ) : Outcome . LOSE,
( S c i s s o r s , S c i s s o r s ) : Outcome .DRAW,
( Rock , S c i s s o r s ) : Outcome .WIN,
( Rock , Paper ) : Outcome . LOSE,
( Rock , Rock ) : Outcome .DRAW,
}
d e f match ( item1 , item2 ) :
p r i n t ”\%s <−−> \%s : \%s ” % (
item1 , item2 , item1 . compete ( item2 ) )
137
# Generate t he i t e m s :
d e f itemPairGen ( n ) :
# Create a l i s t o f i n s t a n c e s o f a l l Items :
Items = Item . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d ( random . c h o i c e ( Items ) ( ) ,
random . c h o i c e ( Items ) ( ) )
f o r item1 , item2 i n itemPairGen ( 2 0 ) :
match ( item1 , item2 )
#:˜
Es un tributo a la flexibilidad de los diccionarios que una tupla se
puede utilizar como una clave tan fácilmente como un solo objeto.
138
15.1 Visitor, un tipo de despacho múltiple
La suposición es que usted tiene una jerarquı́a primaria de clases
que es fija; tal vez es de otro proveedor y no puede hacer cam-
bios en esa jerarquı́a. Sin embargo, usted tenı́a como añadir nuevos
métodos polimórficos a esa jerarquı́a, lo que significa que normal-
mente habrı́a que añadir algo a la interfaz de la clase base. Ası́ que
el dilema es que usted necesita agregar métodos a la clase base, pero
no se puede tocar la clase base. ¿Cómo se obtiene alrededor de esto?.
#: c11 : F l o w e r V i s i t o r s . py
# Demonstration o f ” v i s i t o r ” p a t t e r n .
from future import g e n e r a t o r s
import random
c l a s s G l a d i o l u s ( Flower ) : p a s s
139
c l a s s Runuculus ( Flower ) : p a s s
c l a s s Chrysanthemum ( Flower ) : p a s s
class Visitor :
def str ( self ):
return s e l f . c l a s s . name
c l a s s Bug ( V i s i t o r ) : p a s s
c l a s s P o l l i n a t o r ( Bug ) : p a s s
c l a s s P r e d a t o r ( Bug ) : p a s s
# Add th e a b i l i t y t o do ”Bee” a c t i v i t i e s :
c l a s s Bee ( P o l l i n a t o r ) :
def v i s i t ( s e l f , flower ) :
flower . pollinate ( s e l f )
# Add th e a b i l i t y t o do ” Fly ” a c t i v i t i e s :
c l a s s Fly ( P o l l i n a t o r ) :
def v i s i t ( s e l f , flower ) :
flower . pollinate ( s e l f )
# Add th e a b i l i t y t o do ”Worm” a c t i v i t i e s :
c l a s s Worm( P r e d a t o r ) :
def v i s i t ( s e l f , flower ) :
flower . eat ( s e l f )
d e f flowerGen ( n ) :
f l w r s = Flower . s u b c l a s s e s ( )
f o r i i n range ( n ) :
y i e l d random . c h o i c e ( f l w r s ) ( )
140
15.2 Ejercicios
1. Crear un entorno empresarial de modelado con tres tipos de In-
habitant : Dwarf (para Ingenieros), Elf (para los comerciantes)
y Troll (para los administradores). Ahora cree una clase llamada
Project que crea los diferentes habitantes y les lleva a interact( )
entre sı́ utilizando despacho múltiple.
141
16 12 : Patrón Refactorización
Este capı́tulo no ha tenido traducción significativa todavı́a.
142
la basura todo mezclado. El programa debe modelar la clasificación
de esa basura. Aquı́ es donde entra en juego RTTI : usted tiene un
montón de piezas anónimas de basura, y el programa se da cuenta
exactamente de qué tipo son.
# c12 : r e c y c l e a : RecycleA . py
# R e c y c l i n g with RTTI .
c l a s s Trash :
p r i v a t e double weight
def i n i t ( s e l f , double wt ) : weight = wt
a b s t r a c t double g e t V a l u e ( )
double getWeight ( ) : r e t u r n weight
# Sums t he v a l u e o f Trash i n a b i n :
s t a t i c v o i d sumValue ( I t e r a t o r i t ) :
double v a l = 0 . 0 f
w h i l e ( i t . hasNext ( ) ) :
# One kind o f RTTI :
# A dynamically −checked c a s t
Trash t = ( Trash ) i t . next ( )
# Polymorphism i n a c t i o n :
v a l += t . getWeight ( ) ∗ t . g e t V a l u e ( )
print (
” weight o f ” +
# Using RTTI t o g e t type
# i n f o r m a t i o n about t he c l a s s :
t . g e t C l a s s ( ) . getName ( ) +
” = ” + t . getWeight ( ) )
p r i n t ” Total v a l u e = ” + v a l
c l a s s Aluminum ( Trash ) :
s t a t i c double v a l = 1 . 6 7 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval
c l a s s Paper ( Trash ) :
143
s t a t i c double v a l = 0 . 1 0 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval
c l a s s G l a s s ( Trash ) :
s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
double g e t V a l u e ( ) : r e t u r n v a l
s t a t i c v o i d s e t V a l u e ( double newval ) :
v a l = newval
c l a s s RecycleA ( UnitTest ) :
Collection
b in = A r r a y L i s t ( ) ,
glassBin = ArrayList () ,
paperBin = A r r a y L i s t ( ) ,
alBin = ArrayList ()
def init ( self ):
# F i l l up th e Trash b in :
f o r ( i n t i = 0 i < 30 i ++)
s w i t c h ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :
case 0 :
b in . add ( new
Aluminum ( Math . random ( ) ∗ 1 0 0 ) )
break
case 1 :
b in . add ( new
Paper ( Math . random ( ) ∗ 1 0 0 ) )
break
case 2 :
b in . add ( new
G l a s s ( Math . random ( ) ∗ 1 0 0 ) )
def test ( s e l f ) :
I t e r a t o r s o r t e r = b in . i t e r a t o r ( )
# S o r t t he Trash :
w h i l e ( s o r t e r . hasNext ( ) ) :
144
Object t = s o r t e r . next ( )
# RTTI t o show c l a s s membership :
i f ( t i n s t a n c e o f Aluminum )
a l B i n . add ( t )
i f ( t i n s t a n c e o f Paper )
paperBin . add ( t )
i f ( t i n s t a n c e o f Glass )
g l a s s B i n . add ( t )
Trash . sumValue ( a l B i n . i t e r a t o r ( ) )
Trash . sumValue ( paperBin . i t e r a t o r ( ) )
Trash . sumValue ( g l a s s B i n . i t e r a t o r ( ) )
Trash . sumValue ( b in . i t e r a t o r ( ) )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleA ( ) . t e s t ( )
# :˜
En los listados de código fuente disponibles para este libro, este
archivo se colocará en el subdirectorio recyclea que se ramifica
desde el subdirectorio c12 (para el Capı́tulo 12). La herramienta
de desembalaje se encarga de colocarlo en el subdirectorio correcto.
La razón para hacer esto es que este capı́tulo reescribe este ejemplo
particular, un número de veces y poniendo cada versión en su pro-
pio directorio (utilizando el paquete por defecto en cada directorio
para que invocando el programa es fácil), los nombres de clase no se
enfrentarán.
145
(son upcast). Sin embargo, debido polimorfismo del comportamiento
apropiado se sigue produciendo cuando los métodos dinámicamente
enlazados son llamados a través de la Iterator sorter, una vez que
el Object resultante ha sido lanzado de nuevo a Trash. sum-
Value( ) también toma un Iterator para realizar operaciones en
cada objeto en el ArrayList.
La clave para el mal uso de RTTI aquı́ es que cada tipo se pone a
prueba. Si usted está buscando sólo un subconjunto de tipos porque
ese subconjunto necesita un tratamiento especial, eso probablemente
está muy bien. Pero si usted está buscando para cada tipo dentro
de una sentencia switch, entonces usted está probablemente perdi-
endo un punto importante, y definitivamente hacer su código menos
mantenible. En la siguiente sección vamos a ver cómo este programa
ha evolucionado a lo largo de varias etapas para llegar a ser mucho
más flexible. Esto debe resultar un ejemplo valioso en el diseño del
programa.
146
16.2 Mejorando el diseño
Las soluciones en Design Patterns se organizan en torno a la pre-
gunta ”¿Qué va a cambiar a medida que evoluciona este programa?”
Esta suele ser la pregunta más importante que usted puede pregun-
tar acerca de cualquier diseño. Si usted puede construir su sistema
en torno a la respuesta, los resultados serán de dos vertientes: no sólo
su sistema permite un fácil (y barato) mantenimiento, pero también
se pueden producir componentes que son reutilizables, de modo que
los otros sistemas se pueden construir de forma más económica. Esta
es la promesa de la programación orientada a objetos, pero esto no
sucede automáticamente; se requiere el pensamiento y la visión de
su parte. En esta sección veremos cómo este proceso puede suceder
durante el refinamiento de un sistema.
147
f o r ( i n t i = 0 i < 30 i ++)
s w i t c h ( ( i n t ) ( Math . random ( ) ∗ 3 ) ) :
case 0 :
b in . add ( new
Aluminum ( Math . random ( ) ∗ 1 0 0 ) )
break
case 1 :
b in . add ( new
Paper ( Math . random ( ) ∗ 1 0 0 ) )
break
case 2 :
b in . add ( new
G l a s s ( Math . random ( ) ∗ 1 0 0 ) )
Esto es definitivamente desordenado, y también un lugar donde
usted debe cambiar el código cada vez que se agrega un nuevo tipo.
Si comúnmente se añaden nuevos tipos, una mejor solución es un
sólo método que toma toda la información necesaria y produce una
referencia a un objeto del tipo correcto, ya upcast a un objeto de
basura. En Design Patterns esto se conoce en general como un
patrón creacional (de los cuales hay varios). El patrón especı́fico
que se aplicará aquı́ es una variante del Factory Method : Método
de fábrica. Aquı́, el método de fábrica es un miembro static de
Trash, pero más comúnmente es un método que se anula en la
clase derivada.
148
pasa si la información requerida en la clase derivada requiere argu-
mentos más o diferentes? ”La creación de más objetos” resuelve este
problema. Para implementar el método de fábrica, la clase Trash
consigue un nuevo método llamado factory. Para ocultar los datos
creacionales, hay una nueva clase llamada Messenger que lleva
toda la información necesaria para el método factory para crear
el objeto Trash apropiado (hemos empezado haciendo referencia a
Messenger como un patrón de diseño, pero es bastante simple que
no puede elegir elevarlo a ese estado). Aquı́ esta una simple imple-
mentación de Messenger:
c l a s s Messenger :
i n t type
# Must change t h i s t o add a n o t h e r type :
s t a t i c f i n a l i n t MAX NUM = 4
double data
def i n i t ( s e l f , i n t typeNum , double v a l ) :
type = typeNum % MAX NUM
data = v a l
El único trabajo de un objeto Messenger es mantener la infor-
mación para el método factory( ). Ahora, si hay una situación en
la que factory( ) necesita información más o diferente para crear
un nuevo tipo de objeto Trash, la interfaz factory( ) no necesita
ser cambiada. La clase Messenger puede ser cambiada mediante
la adición de nuevos datos y nuevos constructores, o en el la manera
orientada a objetos más tı́pica de las subclases.
s t a t i c Trash f a c t o r y ( Messenger i ) :
s w i t c h ( i . type ) :
d e f a u l t : # To q u i e t th e c o m p i l e r
case 0:
r e t u r n Aluminum ( i . data )
case 1:
r e t u r n Paper ( i . data )
case 2:
r e t u r n G l a s s ( i . data )
149
# Two l i n e s h e r e :
case 3:
r e t u r n Cardboard ( i . data )
Aquı́, la determinación del tipo exacto de objeto es simple, pero
se puede imaginar un sistema más complicado en el que factory( )
utiliza un algoritmo elaborado. El punto es que está ahora escon-
dido en un lugar, y usted sabe llegar a este lugar cuando se agregan
nuevos tipos.
f o r ( i n t i = 0 i < 30 i ++)
b in . add (
Trash . f a c t o r y (
Messenger (
( i n t ) ( Math . random ( ) ∗ Messenger .MAX NUM) ,
Math . random ( ) ∗ 1 0 0 ) ) )
Se crea un objeto Messenger para pasar los datos en factory(
), que a su vez produce una especie de objeto Trash en la pila
y devuelve la referencia que se agrega al ArrayList bin. Claro,
si cambia la cantidad y tipo de argumento, esta declaración to-
davı́a necesitará ser modificada, pero que puede ser eliminada si
la creación del objeto Messenger está automatizada. Por ejemplo,
un ArrayList de argumentos puede ser pasado en el constructor de
un objeto Messenger (o directamente en una llamada factory( ),
para el caso). Esto requiere que los argumentos sean analizados y
verificados en tiempo de ejecución, pero proporciona la mayor flex-
ibilidad.
150
16.3 Un patrón para la creación de prototipos
Un problema con el diseño anterior es que todavı́a requiere una
ubicación central donde deben ser conocidos todos los tipos de los
objetos: dentro del método factory(). Si regularmente se agregan
nuevos tipos al sistema, el método factory( ) debe cambiarse para
cada nuevo tipo. Cuando usted descubre algo como esto, es útil para
tratar de avanzar un paso más y mover toda la información sobre
el tipo — incluyendo su creación — en la clase que representa ese
tipo. De esta manera, la única cosa que necesita hacer para agregar
un nuevo tipo al sistema es heredar una sola clase.
151
prototipado.
# 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 ” )
# Loaded s u c c e s s f u l l y .
# R e c u r s i v e c a l l s h o u l d work :
return factory ( info )
p u b l i c s t a t i c c l a s s Messenger :
153
public String id
public double data
public Messenger ( S t r i n g name , double v a l ) :
id = name
data = val
# :˜
La clase básica Trash y sumValue( ) permanecen como antes.
El resto de la clase soporta el patrón de prototipado. Primero ve
dos clases internas (que se hacen static, ası́ que son las clases in-
ternas solamente para los propósitos de la organización de código)
describiendo excepciones que pueden ocurrir. Esto es seguido por
un ArrayList llamado trashTypes, que se utiliza para mantener
las referencias Class.
Una vez que haya descubierto qué tipo de Trash crear, a con-
tinuación, los métodos de reflexión entran en juego. El método
getConstructor( ) toma un argumento que es un array de refer-
encias Class. Este array representa los argumentos, en su debido
orden, para el constructor que usted está buscando. Aquı́, el array
es creado de forma dinámica usando Jvaa 1.1 la sintaxis de creación
de array:
C l a s s [ ] : double . c l a s s
Este código asume que cada tipo Trash tiene un constructor
que toma un double (y observe que double.class es distinto de
Double.class). También es posible, por una solución más flexible,
llamar getConstructors( ), que devuelve un array con los posibles
constructores.
154
Lo que viene de vuelta de getConstructor( ) es una referencia a
un objeto Constructor (parte de java.lang.reflect). Usted llama
al constructor de forma dinámica con el método newInstance( ),
lo cual toma un array de Object conteniendo los argumentos reales.
Este array se crea de nuevo utilizando la sintaxis de Java 1.1:
155
demás.
# c12 : t r a s h : Aluminum . py
# The Aluminum c l a s s with p r o t o t y p i n g .
c l a s s Aluminum ( Trash ) :
p r i v a t e s t a t i c double v a l = 1 . 6 7 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal
# :˜
# c12 : t r a s h : Paper . py
# The Paper c l a s s with p r o t o t y p i n g .
c l a s s Paper ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 1 0 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal
# :˜
# c12 : t r a s h : G l a s s . py
# The G l a s s c l a s s with p r o t o t y p i n g .
c l a s s G l a s s ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal
# :˜
156
Y aquı́ hay un nuevo tipo de Trash(basura):
# c12 : t r a s h : Cardboard . py
# The Cardboard c l a s s with p r o t o t y p i n g .
c l a s s Cardboard ( Trash ) :
p r i v a t e s t a t i c double v a l = 0 . 2 3 f
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
def getValue ( s e l f ) : return val
d e f s e t V a l u e ( s e l f , double newVal ) :
v a l = newVal
# :˜
Se puede ver que, aparte del constructor, no hay nada de especial
en cualquiera de estas clases.
157
c12 . t r a s h . Aluminum : 1 8
c12 . t r a s h . Paper : 9 1
c12 . t r a s h . G l a s s : 6 3
c12 . t r a s h . G l a s s : 5 0
c12 . t r a s h . G l a s s : 8 0
c12 . t r a s h . Aluminum : 8 1
c12 . t r a s h . Cardboard : 1 2
c12 . t r a s h . G l a s s : 1 2
c12 . t r a s h . G l a s s : 5 4
c12 . t r a s h . Aluminum : 3 6
c12 . t r a s h . Aluminum : 9 3
c12 . t r a s h . G l a s s : 9 3
c12 . t r a s h . Paper : 8 0
c12 . t r a s h . G l a s s : 3 6
c12 . t r a s h . G l a s s : 1 2
c12 . t r a s h . G l a s s : 6 0
c12 . t r a s h . Paper : 6 6
c12 . t r a s h . Aluminum : 3 6
c12 . t r a s h . Cardboard : 2 2
# :˜
Tenga en cuenta que la ruta de clase debe ser incluido al dar los
nombres de las clases, de lo contrario la clase no será encontrada.
# c12 : t r a s h : ParseTrash . py
# Parse f i l e c o n t e n t s i n t o Trash o b j e c t s ,
# p l a c i n g each i n t o a F i l l a b l e h o l d e r .
158
c l a s s ParseTrash :
def f i l l B i n ( String filename , F i l l a b l e bin ) :
f o r l i n e i n open ( f i l e n a m e ) . r e a d l i n e s ( ) :
S t r i n g type = l i n e . s u b s t r i n g ( 0 ,
l i n e . index ( ’ : ’ ) ) . s t r i p ( )
double weight = Double . valueOf (
l i n e . s u b s t r i n g ( l i n e . index ( ’ : ’ ) + 1)
. s t r i p ( ) ) . doubleValue ( )
b in . addTrash (
Trash . f a c t o r y (
Trash . Messenger ( type , weight ) ) )
# S p e c i a l c a s e t o handle C o l l e c t i o n :
d e f f i l l B i n ( S t r i n g f i l e n a m e , C o l l e c t i o n bi n ) :
f i l l B i n ( f i l e n a m e , F i l l a b l e C o l l e c t i o n ( b in ) )
# :˜
En RecycleA.py, un ArrayList se utiliza para contener los ob-
jetos Trash. Sin embargo, otros tipos de contenedores pueden ser
utilizados también. Para permitir esto, la primera versión de fill-
Bin( ) hace una referencia a un Fillable, lo cual es simplemente
una interface que soporta un método llamado addTrash( ):
# c12 : t r a s h : F i l l a b l e . py
# Any o b j e c t t h a t can be f i l l e d with Trash .
class Fillable :
d e f addTrash ( s e l f , Trash t )
# :˜
Cualquier cosa que soporta esta interfaz se puede utilizar con
fillBin. Claro, Collection no implementa Fillable, por lo que
no va a funcionar. Dado que Collection se utiliza en la mayorı́a
de los ejemplos, tiene sentido añadir un segundo método fillBin(
) sobrecargado que toma un Collection. Cualquier Collection a
continuación, se puede utilizar como un objeto Fillable usando una
clase adaptador:
# c12 : t r a s h : F i l l a b l e C o l l e c t i o n . py
159
# Adapter t h a t makes a C o l l e c t i o n F i l l a b l e .
d e f addTrash ( s e l f , Trash t ) :
c . add ( t )
# :˜
Se puede ver que el único trabajo de esta clase es conectar el
método addTrash( ) de Fillable a Collection’s add( ). Con
esta clase en la mano, el método sobrecargado fillBin( ) se puede
utilizar con un Collection en ParseTrash.py.
public s t a t i c void
f i l l B i n ( S t r i n g f i l e n a m e , C o l l e c t i o n b in ) :
f i l l B i n ( f i l e n a m e , F i l l a b l e C o l l e c t i o n ( b in ) )
Este enfoque funciona para cualquier clase de contenedor que
se utiliza con frecuencia. Alternativamente, la clase de contenedor
puede proporcionar su propio adaptador que implementa Fillable.
(Usted verá esto después, en DynaTrash.py.)
# c12 : r e c y c l e a p : RecycleAP . py
# R e c y c l i n g with RTTI and P r o t o t y p e s .
c l a s s RecycleAP ( UnitTest ) :
Collection
b in = A r r a y L i s t ( ) ,
glassBin = ArrayList () ,
paperBin = A r r a y L i s t ( ) ,
160
alBin = ArrayList ()
def init ( self ):
# F i l l up th e Trash b in :
ParseTrash . f i l l B i n (
” . . / t r a s h / Trash . dat ” , b in )
def test ( s e l f ) :
I t e r a t o r s o r t e r = b in . i t e r a t o r ( )
# S o r t t he Trash :
w h i l e ( s o r t e r . hasNext ( ) ) :
Object t = s o r t e r . next ( )
# RTTI t o show c l a s s membership :
i f ( t i n s t a n c e o f Aluminum )
a l B i n . add ( t )
i f ( t i n s t a n c e o f Paper )
paperBin . add ( t )
i f ( t i n s t a n c e o f Glass )
g l a s s B i n . add ( t )
Trash . sumValue ( a l B i n . i t e r a t o r ( ) )
Trash . sumValue ( paperBin . i t e r a t o r ( ) )
Trash . sumValue ( g l a s s B i n . i t e r a t o r ( ) )
Trash . sumValue ( b in . i t e r a t o r ( ) )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleAP ( ) . t e s t ( )
# :˜
Todos los objetos Trash, ası́ como las clases ParseTrash y de
apoyo, ahora son parte del paquete de c12.trash, por lo que sim-
plemente son importados.
161
En términos de creación de objetos, este diseño en efecto, localiza
severamente los cambios que necesita hacer para agregar un nuevo
tipo al sistema. Ahora bien, hay un problema significativo en el
uso de RTTI que se muestra claramente aquı́. El programa parece
funcionar bien, y sin embargo, nunca se detecta algún cardboard :
cartón, a pesar de que cardboard está en la lista! Esto sucede de-
bido a el uso de RTTI, que busca sólo los tipos que le indican que
debe buscar. La pista que RTTI está siendo mal utilizada es que
cada tipo en el sistema se está probando, en lugar de un solo tipo o
subconjunto de tipos. Como se verá más adelante, hay maneras de
utilizar polimorfismo en lugar de cuando se está probando para cada
tipo. Pero si usa RTTI mucho de esta manera, y añade un nuevo
tipo a su sistema, usted puede olvidar fácilmente hacer los cambios
necesarios en su programa y producir un error difı́cil de encontrar.
Ası́ que vale la pena tratar de eliminar RTTI en este caso, no sólo
por razones estéticas — produce código más mantenible.
c l a s s TrashSorter ( ArrayList ) :
162
d e f s o r t ( s e l f , Trash t ) : /∗ . . . ∗/
Es decir, TrashSorter es un ArrayList de referencias a Ar-
rayLists de referencias Trash, y con puede instalar otro, ası́:
TrashSorter ts = TrashSorter ()
t s . add ( A r r a y L i s t ( ) )
Ahora, sin embargo, sort( ) se convierte en un problema. ¿Cómo
el método estáticamentecodificado trata con el hecho de que un
nuevo tipo ha sido añadido? Para solucionar esto, la información de
tipo debe ser removido de sort( ) de manera que todo lo que que
necesita hacer es llamar a un método genérico que se ocupa de los
detalles del tipo. Esto, por supuesto, es otra manera de describir
un método dinámicamente enlazado. Ası́ sort( ) simplemente se
moverá a través de la secuencia y llamar a un método dinámicamente
enlazado para cada ArrayList. Dado que el trabajo de este método
es tomar las piezas de basura en que está interesado, este es llamado
grab(Trash). La estructura ahora queda como:
163
probablemente serı́a el enfoque más sencillo. Pero en lugar de la
codificación manual de todas las clases que tal mecanismo debe es-
tar construyendo para nosotros, mayor observación puede producir
un mejor enfoque.
# c12 : r e c y c l e b : RecycleB . py
# C o n t a i n e r s t h a t grab o b j e c t s o f i n t e r e s t .
# A c o n t a i n e r t h a t admits o n l y th e r i g h t type
# o f Trash ( e s t a b l i s h e d i n th e c o n s t r u c t o r ) :
c l a s s Tbin :
private Collection l i s t = ArrayList ()
p r i v a t e C l a s s type
def i n i t ( s e l f , C l a s s binType ) : type = binType
d e f grab ( s e l f , Trash t ) :
# Comparing c l a s s t y p e s :
i f ( t . g e t C l a s s ( ) . e q u a l s ( type ) ) :
l i s t . add ( t )
r e t u r n 1 # Object grabbed
164
r e t u r n 0 # Object not grabbed
def i t e r a t o r ( s e l f ) :
return l i s t . i t e r a t o r ()
c l a s s TbinList ( ArrayList ) :
d e f s o r t ( s e l f , Trash t ) :
I t e r a t o r e = i t e r a t o r ( ) # I t e r a t e over s e l f
w h i l e ( e . hasNext ( ) )
i f ( ( ( Tbin ) e . next ( ) ) . grab ( t ) ) r e t u r n
# Need a Tbin f o r t h i s type :
add ( Tbin ( t . g e t C l a s s ( ) ) )
sort ( t ) # Recursive c a l l
c l a s s RecycleB ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
TbinList trashBins = TbinList ()
def init ( self ):
ParseTrash . f i l l B i n ( ” . . / t r a s h / Trash . dat ” , bi n )
def test ( s e l f ) :
I t e r a t o r i t = bi n . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) )
t r a s h B i n s . s o r t ( ( Trash ) i t . next ( ) )
Iterator e = trashBins . i t e r a t o r ()
w h i l e ( e . hasNext ( ) ) :
Tbin b = ( Tbin ) e . next ( )
Trash . sumValue ( b . i t e r a t o r ( ) )
Trash . sumValue ( b in . i t e r a t o r ( ) )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
RecycleB ( ) . t e s t ( )
# :˜
Tbin contiene una referencia Class type que establece en el con-
structor qué tipo debe agarrar. El método grab() revisa este tipo
contra el objeto que se pasa. Tenga en cuenta que en este diseño,
grab() solo acepta objetos Trash de este modo usted consigue la
165
comprobación de tipos en tiempo de compilación del tipo base, pero
usted podrı́a también apenas aceptar Object y todavı́a funcionarı́a.
166
16.8 Despacho múltiple
El diseño anterior es ciertamente satisfactorio. La adición de nuevos
tipos al sistema consiste en añadir o modificar clases distintas sin
causar cambios en el código que se propagan por todo el sistema.
En adición, RTTI no está ”mal utilizada” como lo estaba en Recy-
cleA.py. Sin embargo, es posible ir un paso más allá y tomar un
punto de vista purista sobre RTTI y decir que debe ser eliminada
por completo de la operación de clasificar la basura en los contene-
dores.
167
que la basura será colocada. Esta segunda jerarquı́a no siempre es
evidente y en este caso esto necesitaba ser creado con el fin de pro-
ducir despacho múltiple (en este caso habrá sólo dos despachos, lo
cual hace referencia como double dispatching : despacho doble).
168
16.8.1 La implementación del doble despacho
169
que se tomará aquı́.
# c12 : d o u b l e d i s p a t c h : TypedBinMember . py
# An c l a s s f o r adding th e double
# d i s p a t c h i n g method t o t h e t r a s h h i e r a r c h y
# without m o d i f y i n g t he o r i g i n a l h i e r a r c h y .
c l a s s TypedBinMember :
# The method :
b o o l e a n addToBin ( TypedBin [ ] tb )
# :˜
En cada subtipo particular de Aluminum, Paper, Glass ,
and Cardboard, el método addToBin( ) en la interfaz interface
TypedBinMember es implementado, pero parece que el código es
exactamente el mismo en cada caso:
# c12 : d o u b l e d i s p a t c h : DDAluminum . py
# Aluminum f o r double d i s p a t c h i n g .
c l a s s DDAluminum( Aluminum )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0
# :˜
# c12 : d o u b l e d i s p a t c h : DDPaper . py
# Paper f o r double d i s p a t c h i n g .
c l a s s DDPaper ( Paper )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . init ( wt )
170
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0
# :˜
# c12 : d o u b l e d i s p a t c h : DDGlass . py
# G l a s s f o r double d i s p a t c h i n g .
c l a s s DDGlass ( G l a s s )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0
# :˜
# c12 : d o u b l e d i s p a t c h : DDCardboard . py
# Cardboard f o r double d i s p a t c h i n g .
c l a s s DDCardboard ( Cardboard )
implements TypedBinMember :
def i n i t ( s e l f , double wt ) : . i n i t ( wt )
d e f addToBin ( s e l f , TypedBin [ ] tb ) :
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
i f ( tb [ i ] . add ( s e l f ) )
return 1
return 0
# :˜
El código en cada addToBin( ) llama add( ) para cada objeto
TypedBin en el array. Pero note el argumento: this. El tipo de
this es diferente para cada subclase de Trash, por lo que el código
es diferente. (Aunque este código se beneficiará si un mecanismo de
tipo parametrizado es alguna vez agregado a Java.) Ası́ que esta
171
es la primera parte del doble despacho, porque una vez que está
dentro de este método usted sabe que es Aluminum, o Paper,
etc. Durante la llamada a add( ), esta información se pasa a través
del tipo de this. El compilador resuelve la llamada a la versión
correcta sobrecargada de add( ). Pero puesto que tb[i] produce
una referencia al tipo base TypedBin, esta llamada va a terminar
llamando a un método diferente dependiendo del tipo de Typed-
Bin que está actualmente seleccionado. Ese es el segundo despacho.
# c12 : d o u b l e d i s p a t c h : TypedBin . py
# A c o n t a i n e r f o r t he second d i s p a t c h .
c l a s s TypedBin :
Collection c = ArrayList ()
d e f a d d I t ( s e l f , Trash t ) :
c . add ( t )
return 1
def i t e r a t o r ( s e l f ) :
return c . i t e r a t o r ()
d e f add ( s e l f , DDAluminum a ) :
return 0
d e f add ( s e l f , DDPaper a ) :
return 0
d e f add ( s e l f , DDGlass a ) :
return 0
d e f add ( s e l f , DDCardboard a ) :
return 0
# :˜
Puede ver que todos los métodos sobrecargados add( ) retornan
false. Si el método no está sobrecargado en una clase derivada,
continuará retornando false, y el llamador (addToBin( ), en este
172
caso) asumirá que el objeto actual Trash no se ha añadido con éxito
a un contenedor, y continuar buscando el contenedor correcto.
Puesto que para este ejemplo los tipos de basura se han person-
alizado y colocado en un directorio diferente, usted necesitará un
archivo de datos de basura diferente para hacer que funcione. Aquı́
está un posible DDTrash.dat:
173
DDGlass : 8 0
DDAluminum : 8 1
DDCardboard : 1 2
DDGlass : 1 2
DDGlass : 5 4
DDAluminum : 3 6
DDAluminum : 9 3
DDGlass : 9 3
DDPaper : 8 0
DDGlass : 3 6
DDGlass : 1 2
DDGlass : 6 0
DDPaper : 6 6
DDAluminum : 3 6
DDCardboard : 2 2
# :˜
Aquı́ esta el resto del programa:
# c12 : d o u b l e d i s p a t c h : DoubleDispatch . py
# Using m u l t i p l e d i s p a t c h i n g t o handle more
# than one unknown type d u r i n g a method c a l l .
c l a s s AluminumBin ( TypedBin ) :
d e f add ( s e l f , DDAluminum a ) :
return addIt ( a )
c l a s s PaperBin ( TypedBin ) :
d e f add ( s e l f , DDPaper a ) :
return addIt ( a )
c l a s s GlassBin ( TypedBin ) :
d e f add ( s e l f , DDGlass a ) :
return addIt ( a )
c l a s s CardboardBin ( TypedBin ) :
d e f add ( s e l f , DDCardboard a ) :
return addIt ( a )
c l a s s TrashBinSet :
174
p r i v a t e TypedBin [ ] b i n S e t =:
AluminumBin ( ) ,
PaperBin ( ) ,
GlassBin ( ) ,
CardboardBin ( )
def s o r t I n t o B i n s ( s e l f , C o l l e c t i o n bin ) :
I t e r a t o r e = b in . i t e r a t o r ( )
w h i l e ( e . hasNext ( ) ) :
TypedBinMember t =
( TypedBinMember ) e . next ( )
i f ( ! t . addToBin ( b i n S e t ) )
System . e r r . p r i n t l n ( ” Couldn ’ t add ” + t )
p u b l i c TypedBin [ ] b i n S e t ( ) : r e t u r n b i n S e t
c l a s s DoubleDispatch ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
TrashBinSet b i n s = TrashBinSet ( )
def init ( self ):
# ParseTrash s t i l l works , without changes :
ParseTrash . f i l l B i n ( ” DDTrash . dat ” , bi n )
def test ( s e l f ) :
# S o r t from t he master b in i n t o
# t he i n d i v i d u a l l y −typed b i n s :
b i n s . s o r t I n t o B i n s ( b in )
TypedBin [ ] tb = b i n s . b i n S e t ( )
# Perform sumValue f o r each b i n . . .
f o r ( i n t i = 0 i < tb . l e n g t h i ++)
Trash . sumValue ( tb [ i ] . c . i t e r a t o r ( ) )
# . . . and f o r th e master bi n
Trash . sumValue ( b in . i t e r a t o r ( ) )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
DoubleDispatch ( ) . t e s t ( )
# :˜
TrashBinSet encapsula todos los diferentes tipos de Typed-
175
Bins, junto con el método sortIntoBins( ), que es donde todo el
doble despacho toma lugar. Usted puede ver que una vez que la
estructura está configurada, la clasificación en los distintos Type-
dBins es muy fácil. En adición, la eficiencia de dos llamadas al
método dinámico es probablemente mejor que cualquier otra forma
usted podrı́a ordenar.
176
El patrón de diseño que resuelve este tipo de problema es lla-
mado un “visitor : visitante” (la final en el libro Design Patterns
: Patrones de Diseño), y se basa en el esquema de despacho doble
mostrado en la última sección.
177
Ahora, si v es una referencia Visitable para un objeto Alu-
minum, el código:
P r i c e V i s i t o r pv = P r i c e V i s i t o r ( )
v . a c c e p t ( pv )
utiliza despacho doble para causar dos llamadas a métodos polimórficos:
el primero para seleccionar la versión de Aluminum de accept( ),
y el segundo dentro de accept( ) cuando la versión especifica de
178
visit( ) es llamada de forma dinamica usando la clase base Visitor
referencia v.
# c12 : t r a s h v i s i t o r : V i s i t a b l e . py
# An c l a s s t o add v i s i t o r f u n c t i o n a l i t y
# t o t he Trash h i e r a r c h y without
# m o d i f y i n g th e base c l a s s .
179
class Visitable :
# The method :
def accept ( s e l f , V i si to r v)
# :˜
Dado que no hay nada concreto en la clase base Visitor, se puede
crear como una interface:
# c12 : t r a s h v i s i t o r : V i s i t o r . py
# The base c l a s s f o r v i s i t o r s .
class Visitor :
def visit ( self , Aluminum a )
def visit ( self , Paper p )
def visit ( self , Glass g )
def visit ( self , Cardboard c )
# :˜
# c12 : t r a s h v i s i t o r : VAluminum . py
# Taking t he p r e v i o u s approach o f c r e a t i n g a
# s p e c i a l i z e d Aluminum f o r t he v i s i t o r p a t t e r n .
c l a s s VAluminum ( Aluminum )
implements V i s i t a b l e :
def i n i t ( s e l f , double wt ) : . init ( wt )
def accept ( s e l f , V i si to r v ) :
v. visit ( self )
# :˜
Sin embargo, Parece que estamos encontrando una ”explosión
de interfaces:” Trash básico, versiones especiales para el despa-
cho doble, y ahora las versiones más especiales para los visitantes.
180
Claro, esta ”explosión de interfaces” es arbitraria — uno podrı́a
simplemente poner los métodos adicionales de la clase Trash. Si
ignoramos que en lugar podemos ver la oportunidad de utilizar el
patrón Decorador : Parece como que deberı́a ser posible crear un
Decorador que puede ser envuelto alrededor de un objeto ordinario
Trash y producirá la misma interfaz que Trash y agrega el método
extra accept( ). De hecho, es un ejemplo perfecto del valor de Dec-
orador.
181
d e f getWeight ( s e l f ) :
r e t u r n d e l e g a t e . getWeight ( )
def accept ( s e l f , V i si to r v ) :
try :
d i s p a t c h . i n v o k e ( v , Object [ ] { d e l e g a t e )
c a t c h ( E xc ept ion ex ) :
ex . p r i n t S t a c k T r a c e ( )
# :˜
[[Descripción del uso de Reflexión]]
# c12 : t r a s h v i s i t o r : F i l l a b l e V i s i t o r . py
# Adapter D e c o r a t o r t h a t adds t he v i s i t a b l e
# d e c o r a t o r as t he Trash o b j e c t s a r e
# being created .
class FillableVisitor
implements F i l l a b l e :
private Fillable f
def init ( self , Fillable ff ): f = ff
d e f addTrash ( s e l f , Trash t ) :
f . addTrash ( V i s i t a b l e D e c o r a t o r ( t ) )
# :˜
Ahora usted puede envolver alrededor de cualquier tipo de Fillable
existente, o cualquier otros nuevos que aún no se han creado.
# c12 : t r a s h v i s i t o r : T r a s h V i s i t o r . py
# The ” v i s i t o r ” p a t t e r n with V i s i t a b l e D e c o r a t o r s .
# S p e c i f i c group o f a l g o r i t h m s packaged
182
# i n each i m p l e m e n t a t i o n o f V i s i t o r :
class PriceVisitor ( Visitor ):
p r i v a t e double alSum # Aluminum
p r i v a t e double pSum # Paper
p r i v a t e double gSum # G l a s s
p r i v a t e double cSum # Cardboard
d e f v i s i t ( s e l f , Aluminum a l ) :
double v = a l . getWeight ( ) ∗ a l . g e t V a l u e ( )
p r i n t ” v a l u e o f Aluminum= ” + v
alSum += v
d e f v i s i t ( s e l f , Paper p ) :
double v = p . getWeight ( ) ∗ p . g e t V a l u e ( )
p r i n t ” v a l u e o f Paper= ” + v
pSum += v
def v i s i t ( s e l f , Glass g ) :
double v = g . getWeight ( ) ∗ g . g e t V a l u e ( )
p r i n t ” v a l u e o f G l a s s= ” + v
gSum += v
d e f v i s i t ( s e l f , Cardboard c ) :
double v = c . getWeight ( ) ∗ c . g e t V a l u e ( )
p r i n t ” v a l u e o f Cardboard = ” + v
cSum += v
def total ( s e l f ) :
print (
” Total Aluminum : $” + alSum +
”\n Total Paper : $” + pSum +
”\ nTotal G l a s s : $” + gSum +
”\ nTotal Cardboard : $” + cSum +
”\ nTotal : $” +
( alSum + pSum + gSum + cSum ) )
c l a s s WeightVisitor ( V i s i t o r ) :
p r i v a t e double alSum # Aluminum
p r i v a t e double pSum # Paper
p r i v a t e double gSum # G l a s s
183
p r i v a t e double cSum # Cardboard
d e f v i s i t ( s e l f , Aluminum a l ) :
alSum += a l . getWeight ( )
p r i n t ( ” weight o f Aluminum = ”
+ a l . getWeight ( ) )
d e f v i s i t ( s e l f , Paper p ) :
pSum += p . getWeight ( )
p r i n t ( ” weight o f Paper = ”
+ p . getWeight ( ) )
def v i s i t ( s e l f , Glass g ) :
gSum += g . getWeight ( )
p r i n t ( ” weight o f G l a s s = ”
+ g . getWeight ( ) )
d e f v i s i t ( s e l f , Cardboard c ) :
cSum += c . getWeight ( )
p r i n t ( ” weight o f Cardboard = ”
+ c . getWeight ( ) )
def total ( s e l f ) :
print (
” Total weight Aluminum : ” + alSum +
”\ nTotal weight Paper : ” + pSum +
”\ nTotal weight G l a s s : ” + gSum +
”\ nTotal weight Cardboard : ” + cSum +
”\ nTotal weight : ” +
( alSum + pSum + gSum + cSum ) )
c l a s s T r a s h V i s i t o r ( UnitTest ) :
C o l l e c t i o n b in = A r r a y L i s t ( )
P r i c e V i s i t o r pv = P r i c e V i s i t o r ( )
W e i g h t V i s i t o r wv = W e i g h t V i s i t o r ( )
def init ( self ):
ParseTrash . f i l l B i n ( ” . . / t r a s h / Trash . dat ” ,
FillableVisitor (
F i l l a b l e C o l l e c t i o n ( bi n ) ) )
184
def test ( s e l f ) :
I t e r a t o r i t = bi n . i t e r a t o r ( )
w h i l e ( i t . hasNext ( ) ) :
V i s i t a b l e v = ( V i s i t a b l e ) i t . next ( )
v . a c c e p t ( pv )
v . a c c e p t (wv)
pv . t o t a l ( )
wv . t o t a l ( )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
TrashVisitor ( ) . test ()
# :˜
En Test( ), observe cómo se añade la visitabilidad simplemente
creando un tipo diferente de bin usando el decorador. Observe
también que el adaptador FillableCollection tiene la apariencia
de ser utilizado como decorador (para ArrayList) en esta situación.
Ahora bien, cambia completamente la interfaz del ArrayList, visto
que la definición de Decorador es que la interfaz de la clase decorada
aún debe estar allı́ después de la decoración.
Tenga en cuenta que la forma del código del cliente (que se mues-
tra en la clase Test) ha cambiado de nuevo, a partir de los enfoques
originales al problema. Ahora sólo hay un solo bin Trash. Los dos
objetos Visitor son aceptados en cada elemento de la secuencia, y
realizan sus operaciones. Los visitantes mantienen sus propios datos
internos para concordar los pesos y precios totales.
185
mientras que aquı́ cada uno de los métodos visit( ) sobrecargados
es anulado en cada subclase de Visitor.
186
se olvida de alguno usted no conseguirá la ayuda del compilador.
# 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 g e t ( s e l f , C l a s s type ) :
r e t u r n ( L i s t ) t . g e t ( type )
187
d e f keys ( s e l f ) :
r e t u r n t . keySet ( ) . i t e r a t o r ( )
# Adapter c l a s s t o a l l o w c a l l b a c k s
# from ParseTrash . f i l l B i n ( ) :
c l a s s TypeMapAdapter ( F i l l a b l e ) :
TypeMap map
def i n i t ( s e l f , TypeMap tm ) : map = tm
d e f addTrash ( s e l f , Trash t ) : map . add ( t )
c l a s s DynaTrash ( UnitTest ) :
TypeMap b i n = TypeMap ( )
def test ( s e l f ) :
I t e r a t o r keys = bi n . keys ( )
w h i l e ( keys . hasNext ( ) )
Trash . sumValue (
b in . g e t ( ( C l a s s ) keys . next ( ) ) . i t e r a t o r ( ) )
d e f main ( s e l f , S t r i n g a r g s [ ] ) :
DynaTrash ( ) . t e s t ( )
# :˜
Aunque potente, la definición para TypeMap es simple. Con-
tiene un HashMap, y el método add( ) hace la mayorı́a del trabajo.
Cuando usted add( ) un nuevo objeto, se extrae la referencia para
el objeto Class para ese tipo. Esto se utiliza como una clave para
determinar si un ArrayList que sostiene objetos de ese tipo ya está
presente en el HashMap. Si es ası́, ese ArrayList se extrae y el
objeto se añade al ArrayList. Si no, el objeto Class y un nuevo
ArrayList se añaden como un par clave-valor.
188
El método filler( ) es interesante porque Se aprovecha del diseño
de ParseTrash.fillBin( ), que no sólo tratar de llenar un Ar-
rayList sino cualquier cosa que implementa la interfaz Fillable con
su método addTrash( ). Todo filler( ) necesita hacer es devolver
una referencia a una interface que implementa Fillable, y luego
esta referencia puede ser utilizado como un argumento a fillBin( )
como esto:
189
16.12 Resumen
Surgir con un diseño como TrashVisitor.py que contiene una mayor
cantidad de código que los diseños anteriores puede parecer en un
principio ser contraproducente. Vale la pena notar lo que estás
tratando de lograr con varios diseños. Los patrones de diseño en
general se esfuerzan por separar las cosas que cambian de las cosas
que permanecen igual. Las ”cosas que cambian” puede referirse a
muchos tipos diferentes de cambios. Quizás el cambio ocurre porque
el programa se coloca en un nuevo entorno o porque algo en el en-
torno actual cambia: (esto podrı́a ser: ”El usuario quiere añadir
una nueva forma para el diagrama actualmente en la pantalla”). O,
como en este caso, el cambio podrı́a ser la evolución del cuerpo del
código. Mientras que las versiones anteriores del ejemplo de clasi-
ficación de basura enfatizaron la adición de nuevos tipos de Trash
al sistema, TrashVisitor.py le permite añadir fácilmente nuevas
funcionalidades sin molestar a la jerarquı́a Trash. Hay más código
en TrashVisitor.py, pero la adición de nueva funcionalidad para
Visitor es de mal gusto. Si esto es algo que sucede mucho, entonces
vale la pena el esfuerzo extra y el código para hacer que suceda con
más facilidad.
190
de emitir todos sus diseños en un molde particular.
http://st-www.cs.uiuc.edu/users/patterns
http://c2.com/cgi/wiki
http://c2.com/ppr
http://www.bell-labs.com/people/co
pe/Patterns/Process/index.html
http://www.bell-labs.com/cgi-user/OrgPatterns/OrgPatterns
http://st-www.cs.uiuc.edu/cgi-bin/wikic/wikic
http://www.cs.wustl.edu/ schmidt/patterns.html
http://www.espinc.com/patterns/overview.html
191
16.13 Ejercicios
1. Añade la clase Plastic a TrashVisitor.py
17 Proyectos
Este capı́tulo no ha tenido traducción significativa todavı́a.
192
laberinto.
# 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)
193
l i n e s = Vector ( )
setBackground ( Color . l i g h t G r a y )
public void
l o a d ( S t r i n g f i l e n a m e ) throws IOException :
String currentLine = null
B u f f e r e d R e a d e r br = B u f f e r e d R e a d e r (
FileReader ( filename ))
f o r ( c u r r e n t L i n e = br . r e a d L i n e ( )
c u r r e n t L i n e != n u l l
c u r r e n t L i n e = br . r e a d L i n e ( ) ) :
l i n e s . addElement ( c u r r e n t L i n e . g e t B y t e s ( ) )
i f ( width < 0 | |
c u r r e n t L i n e . g e t B y t e s ( ) . l e n g t h > width )
width = c u r r e n t L i n e . g e t B y t e s ( ) . l e n g t h
height = len ( l i n e s )
br . c l o s e ( )
d e f update ( s e l f , Graphics g ) : p a i n t ( g )
p u b l i c v o i d p a i n t ( Graphics g ) :
194
i n t c a n v a s H e i g h t = s e l f . getBounds ( ) . h e i g h t
i n t canvasWidth = s e l f . getBounds ( ) . width
i f ( h e i g h t < 1 | | width < 1 )
r e t u r n # n o t h i n g t o do
i n t width =
( ( byte [ ] ) ( l i n e s . elementAt ( 0 ) ) ) . l e n g t h
f o r ( i n t y = 0 y < l e n ( l i n e s ) y++):
byte [ ] b
b = ( byte [ ] ) ( l i n e s . elementAt ( y ) )
f o r ( i n t x = 0 x < width x++):
switch (b [ x ] ) :
c a s e ’ ’ : # empty p a r t o f maze
g . s e t C o l o r ( Color . l i g h t G r a y )
g. fillRect (
x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
canvasWidth / width ,
canvasHeight / height )
break
case ’∗ ’: # a wall
g . s e t C o l o r ( Color . darkGray )
g. fillRect (
x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
( canvasWidth / width ) −1 ,
( c a n v a s H e i g h t / h e i g h t ) −1)
break
default : # must be r a t
g . s e t C o l o r ( Color . r ed )
g . f i l l O v a l ( x ∗( canvasWidth / width ) ,
y ∗( c a n v a s H e i g h t / h e i g h t ) ,
canvasWidth / width ,
canvasHeight / height )
break
# :˜
# c13 : Rat . py
c l a s s Rat :
195
s t a t i c i n t ratCount = 0
p r i v a t e Maze p r i s o n
private int vertDir = 0
private int horizDir = 0
private int x , y
p r i v a t e i n t myRatNo = 0
def i n i t ( s e l f , Maze maze , i n t x S t a r t , i n t
yStart ) :
myRatNo = ratCount++
p r i n t ( ” Rat no . ” + myRatNo +
” ready t o s c u r r y . ” )
p r i s o n = maze
x = xStart
y = yStart
p r i s o n . setXY ( x , y , ( byte ) ’R’ )
Thread ( ) :
d e f run ( s e l f ){ s c u r r y ( )
. start ()
def scurry ( s e l f ) :
# Try and maintain d i r e c t i o n i f p o s s i b l e .
# H o r i z o n t a l backward
b o o l e a n ratCanMove = 1
w h i l e ( ratCanMove ) :
ratCanMove = 0
# South
i f ( p r i s o n . isEmptyXY ( x , y + 1 ) ) :
vertDir = 1 horizDir = 0
ratCanMove = 1
# North
i f ( p r i s o n . isEmptyXY ( x , y − 1 ) )
i f ( ratCanMove )
Rat ( p r i s o n , x , y−1)
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
v e r t D i r = −1 h o r i z D i r = 0
196
ratCanMove = 1
# West
i f ( p r i s o n . isEmptyXY ( x−1, y ) )
i f ( ratCanMove )
Rat ( p r i s o n , x−1, y )
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
v e r t D i r = 0 h o r i z D i r = −1
ratCanMove = 1
# East
i f ( p r i s o n . isEmptyXY ( x+1, y ) )
i f ( ratCanMove )
Rat ( p r i s o n , x+1, y )
# Rat can move a l r e a d y , so g i v e
# t h i s c h o i c e t o th e next r a t .
else :
vertDir = 0 horizDir = 1
ratCanMove = 1
i f ( ratCanMove ) : # Move o r i g i n a l r a t .
x += h o r i z D i r
y += v e r t D i r
p r i s o n . setXY ( x , y , ( byte ) ’R’ )
# I f not then t he r a t w i l l d i e .
try :
Thread . s l e e p ( 2 0 0 0 )
catch ( InterruptedException i e ) :
p r i n t ( ” Rat no . ” + myRatNo +
” can ’ t move . . dying . . a a r r g g g h . ” )
# :˜
El archivo de inicialización de laberinto:
197
17.1.1 Otros Recursos para Laberinto
http://www.mazeworks.com/mazegen/mazegen.htm
http://www.red3d.com/cwr/steer/
198