El documento describe la implementación de un framework para un juego de solitario. Explica que el framework define clases para cartas, pilas y reglas que permiten configurar fácilmente las reglas de cada juego sin necesidad de crear nuevas clases. El framework usa estrategias (reglas) para definir el comportamiento de cada pila respecto a permitir quitar y agregar cartas de forma independiente.
3. 3
Un solitario es un juego
en el que hay:
Ejemplo de Framework
Entendiendo el Dominio
Cartas: Unidades
básicas que se
mueven de un lado a
otro, bien sea de
forma separada o en
grupos
Bases: Lugares
donde poner cartas,
aplican reglas sobre
que cartas se
pueden poner /
quitar
Pilas: Grupos de
cartas, generalmente
sobre una base (o en
movimiento, a modo
de un grupo de
cartas). Aplican
reglas sobre que
cartas se pueden
quitar o añadir de/a
una pila
4. 4
El objetivo del juego es acomodar
las cartas de cierta forma o
eliminar todas las cartas de las
mesa, siguiendo una serie de
reglas predefinidas que dicen que
cartas se pueden mover de una pila
a otra...
Ejemplo de Framework
Entendiendo el Dominio
5. 5
Prácticamente, se pueden definir un
conjunto infinito de posibles reglas y
juegos distintos usando el mismo principio
Ejemplo de Framework
Entendiendo el Dominio
Sólo acepta una “A”
de cualquier color
NO
O
K
7. 7
Ejemplo de Framework
Entendiendo el Dominio
Una pila de la que
sólo se puede sacar
la carta del tope o
grupos de cartas que
lleguen alternando su
color con valor
descendente al tope
N
O
SI
SI
8. 8
Si vamos a programar un juego de
solitario hay dos opciones:
1) Programar un sólo juego en especifico, con
reglas especificas
2) Programar una serie de clases (framework)
que permitan luego “configurar” las reglas
fácilmente para así poder crear cualquier solitario
que se requiera
¿Qué opción seleccionaría?
¿Por qué?
¿Programar un Solitario o un Framework?
9. 9
Arquitectura de una Posible Implementación
MainFrame
representan la
IU del solitario
Es la clase encargada
de cargar las cartas
del disco, en cierto
sentido, representa el
“mazo de cartas”
Utilitarios y clases
base de Swing
Utilitarios en general
Objetos del Solitario, Cartas,
Pilas, “Dibujables”, etc
Panel en el que se dibujan
las cartas (o que “contiene”
el solitario)
El código de este ejemplo va adjunto a las
transparencias, son los proyectos
CardGames01 y CardGames02
10. 10
Arquitectura de una Posible Implementación
GamePanel se
encarga de dibujar las
pilas de cartas (que a
su vez dibujan las
cartas individuales) así
como de manejar los
eventos del ratón
Los eventos del ratón se
manejan de forma genérica
por parte de GamePanel,
es decir, las reglas de que
cartas se pueden quitar de
una pila o poner en otra no
están implementadas en
esta clase
Las reglas de las pilas
están implementadas en
cada una de las pilas. Por
ejemplo borrowCards es
invocado para ver si es
posible quitar un grupo de
cartas de una pila,
acceptCards es invocado
para ver si es posible
poner un grupo de cartas
en una pila particular. Toda
la lógica y la verificación se
implementa en estos dós
métodos de las distintas
pilas
Ver diagramas de secuencia
de las siguientes láminas
para entender el proceso completo de tomar de una pila y poner en otra
11. 11
Arquitectura de una Posible Implementación
Lo que sucede cuando el usuario
aprieta el ratón (sobre una pila)
Si el puntero no está
sobre una pila
srcStack es nulo
Si no se permite (por
reglas) mover las
cartas selecionadas,
tmpStack es nulo
12. 12
Arquitectura de una Posible Implementación
Lo que sucede cuando el usuario libera el ratón (sobre una pila)
Si el ratón no se libera
sobre una pila tgtStack
será nulo
Si acceptCards retorna
falso, quiere decir que
la pila por sus “reglas”
no aceptó las cartas, y
que deben ser
devueltas a la pila de
origen
13. 13
Arquitectura de una Posible Implementación
Es decir, desde el punto de vista de GamePanel, toda la
lógica de “si es posible sacar una o más cartas de
una pila” o “si es posible poner una o más cartas en
una pila” está implementada en la clase Stack,
específicamente en los métodos
borrowCards y acceptCards (respectivamente)
¿Cómo podríamos tener pilas que tengan distintos
comportamientos? por ejemplo, ¿Cómo podríamos
tener una pila que acepte sólo cartas del mismo color y
otra que acepte cartas de colores alternados?
14. 14
Arquitectura de una Posible Implementación
Que tal si se especializa Stack en distintos tipos de pilas,
donde cada una de ellas sobrescribe (overrides) el método
acceptCards() y define reglas particulares para cada tipo de
pila que se necesite
¿Desventajas? ¿Inconvenientes?
¿Qué tipo de framework, caja negra o caja blanca?
Acepta cartas sólo del
mismo color
Acepta cartas de
colores intercalados
Acepta cartas sólo de
valores descendentes
Acepta cartas sólo de
valores ascendentes
15. 15
Arquitectura de una Posible Implementación
El problema es que esta estrategia puede terminar en una
situación poco deseable, en la que se produzca una
explosión de clases con funcionalidad redundante, tal como
ocurre en el diagrama ...
... y eso que no se ha considerado aún la necesidad de
especializar el comportamiento de borrowCards
Acepta cartas sólo de
valores descendentes y del
mismo color
Acepta cartas sólo de
valores ascendentes y del
mismo color (¿¿¿Opps, no
esta esto repetido???)
Acepta cartas sólo de
valores ascendentes y
decolores alternados, etc,
etc, etc...
16. 16
Arquitectura de una Posible Implementación
¿Alguna solución al problema de la explosión de clases?
17. 17
Arquitectura de una Posible Implementación
En este caso, una pila está compuesta de una serie de
“Estrategias” de “prestamo” (BorrowRule) y de “aceptación”
(AcceptRule) de cartas que se pueden combinar
independientemente unas de otras
Define la interfaz de
una “pequeña” clase
que establece un
comportamiento de
“prestamo” de cartas
Define la interfaz de
una “pequeña” clase
que establece un
comportamiento de
“aceptación” de cartas
La clase pila está
compuesta por una serie
de reglas de “prestamo”
y ”aceptación” de cartas
18. 18
Arquitectura de una Posible Implementación
Cada una de las
clases de este color
definen una regla para
poder “prestar” una
carta o un grupo de
cartas de la pila
Las clases verdes definen una regla de aceptación, por ejemplo DescendantAcceptRule que sólo
acepta cartas con valores consecutivos descendientes, que se pueden encadenar con otras reglas,
como SameColorAcceptRule, para obtener una pila que solo acepta cartas descendientes
consecutivas en valor del mismo color
El método addAcceptRule
recibe una instancia de
una regla y la añade a la
lista de reglas a verificar al
momento de solicitarle a la
pila que “acepte” una carta
o un grupo de cartas.
El método acceptCards
funciona de la forma
tradicional, sólo que
ejecuta la cadena de
reglas añadidas y si todas
pasan, entonces acepta la
carta o el grupo de cartas
19. 19
Arquitectura de una Posible Implementación
En general, esta es una estrategia completamente caja
negra, porque no es necesario conocer como funciona una
clase particular del framework (Stack en este caso) para
poder heredar y sobrescribir métodos, simplemente basta con
implementar una serie de interfaces y componer la pila de
estas “reglas” que son las que hacen el trabajo
20. 20
Arquitectura de una Posible Implementación
En este ejemplo (y en los que vimos de patrones de diseño)
se ve la importancia de programar en función de interfaces
bien definidas, que pueden ser implementadas
posteriormente a gusto de los programadores y según las
necesidades que se tengan