Está en la página 1de 192

Tecnicas avanzadas en

Diseño de Software

Diseño de
Componentes

Dra. Marcela
Capobianco
1
Diseño orientado a objetos

Es difícil crear SW reusable


Indentificar los objetos
Factorearlos en clases con la
granularidad adecuada
Ser específicos y generales al mismo
tiempo
Es casi imposible hacerlo bien la primera
vez
El arte de programar
Los programadores experimentados no
resuelven las cosas desde cero. Reutilizan
soluciones previamente encontradas, testeadas
y aprobadas.
Lo hacen en el código con facilidad al
identificar el procedimiento a implementar.
Lo hacen en el diseño con facilidad al
identificar la aplicación particular.
Básicamente, identifican patrones y aplican
soluciones.
El arte de programar

Nos centraremos principalmente en la etapa


de diseño (tal vez la más difícil) y
analizaremos diferentes soluciones que
pueden servirnos en el futuro para desarrollar
buenas aplicaciones.

Los patrones de diseño nombran, explican y


evalúan un diseño importante y recurrente en
los sistemas orientados a objetos.
Patrones de diseño

En general, poseen estos cuatro elementos:


Nombre del patrón, para poder identificarlo
fácilmente y tratarlo de manera abstracta cuando
sea necesario.
El problema, que describe cuándo utilizar este
patrón de forma tal que sea la solución.
La solución que describe los elementos que
componen el diseño, sus relaciones, sus
responsabilidades, etc. Se describe en forma
abstracta, pues un patrón es un template que se
aplica en diferentes situaciones.
Las consecuencias, que son los resultados y el
balance final de aplicar el patrón (impacto en el
sistema, detalles de lenguajes,etc)
Patrones de diseño

Los patrones de diseño son básicamente


descripciones de objetos que se comunican y
clases que son personalizadas para resolver un
problema de diseño general en un contexto
particular [GoF]
Los patrones se describen gráficamente, lo que
facilita su comprensión, pero no es suficiente.
Algunos aspectos no pueden ser aclarados o
especificados por medio de diagramas.
Se adopta entonces una convención para la
descripción de patrones. Es un formato
generalmente aceptado que incluye todos los
items necesarios a destacar.
Patrones de diseño

Los patrones GoF se describen todos de esta


forma. Por lo general, otros patrones también
seguirán la misma estructura a la hora de
describirlos…
Descripción
Intención: ¿qué hace? ¿qué problema ataca?
Alias: algunos patrones son conocidos por
nombres diferentes.
Motivación: un escenario que describe el
problema de diseño y que muestra como el
patrón resuelve el problema.
Aplicabilidad: cuáles son las situaciones en las
cuales se aplica el patrón.
Estructura: representación gráfica de las clases
del patrón (diagramas de clases, diagramas de
interacción)
Nombre del patrón y clasificación
Descripción
Participantes: clases y objetos que participan del
patrón.
Colaboraciones: cómo los participantes colaboran
para realizar alguna tarea.
Consecuencias: beneficios, balance pros y
contras, etc.
Implementación: hints, técnicas y detalles a
tener en cuenta al implementar el patrón
Código ejemplo: fragmentos de código que
ejemplifican la implementación
Usos conocidos: ejemplos de patrones en
sistemas reales.
Patrones relacionados: qué otros patrones de
diseño están relacionados o pueden combinarse
para resolver problemas mayores.
Problemas de diseño

Los patrones de diseño procuran ayudar a resolver


problemas de diseño en el desarrollo de
aplicaciones.
Veremos algunos de estos problemas, y
mencionaremos los patrones que nos ayudarán en
este aspecto.
Luego en detalle veremos los patrones más
populares (conocidos como patrones GoF – Gang
of Four) y analizaremos las soluciones que
proponen.
Mas adelante exploraremos otros patrones
basados en tecnologías específicas.
Problemas de diseño
Problema: encontrar los objetos apropiados
Una parte difícil del diseño orientado a objetos
(DOO) es descomponer el sistema en objetos.
Muchos factores entran en juego:
encapsulamiento, granularidad, dependencia,
flexibilidad, performance, reusabilidad, etc.
Existen metodologías para esto, que aunque nos
ayudan bastante, no son completas ni infalibles.
Problemas de diseño

Algunos objetos incluso no corresponden a objetos


del mundo real (por ejemplo, arreglos) y es difícil
derivarlos de la especificación inicial.
Los patrones nos ayudan a identificar
abstracciones menos obvias. Por ejemplo, objetos
que representan procesos o algoritmos.
Esto no surge de forma natural, pero es esencial
para el diseño flexible y reutilizable.
Problemas de diseño

Problema: determinar la granularidad de los


objetos
Los objetos varían en número y tamaño.
Pueden representar aplicaciones completas o
simples datos. ¿Qué debería ser un objeto y que
no?
Algunos patrones nos ayudan en este problema…
Algunos patrones

Facade describe cómo representar subsistemas


completos como objetos.
Flyweight describe cómo soportar un gran
número de objetos a la más fina granularidad.
Abstract Factory y Builder permiten derivar
objetos cuya responsabilidad es generar otros
objetos.
Problemas de diseño
Problema: especificar las interfaces de los objetos
La interfaz es el conjunto de todos los signatures
definidos por un objeto. La interfaz de cada objeto
determina el conjunto de mensajes o pedidos
(request) que se les puede enviar.
Es un concepto fundamental en OO. Los objetos
son conocidos por sus interfaces y éstas no dicen
nada de su implementación.
El polimorfismo y la vinculación dinámica de
código es posible gracias a este concepto.
Problemas de diseño

Los patrones ayudan a definir interfaces e incluso


nos indican que no poner en la interfaz.
Por ejemplo, el patrón Memento indica cómo
encapsular y guardar el estado interno de un
objeto para ser restaurado más tarde. Este patrón
requiere dos interfaces, una semipública y la otra
restringida.
Problemas de diseño

Problema: herencia de clases vs. herencia de


interfaces.
La clase define el tipo de los objetos. Sin embargo,
es posible cierta separación entre estos dos
conceptos.
En nuestro caso, los dos conceptos están
relacionados, pero existen lenguajes que marcan
una diferencia.
Problemas de diseño

La herencia de clase define un objeto en términos


de la implementación de otros objetos.
La herencia de interfaz (subtipo) indica cuándo un
objeto puede ser utilizado en lugar de otro.
Por ejemplo, en Smalltalk la herencia es sólo
herencia de implementación. Uno puede asignar
objetos de otra clase, siempre y cuando
implementen las mismas interfaces.
Java implementa una aproximación a este
concepto…
Problemas de diseño
class Uno implements inter1 {
public interface public void f() {
inter1{ System.out.println("f() de clase Uno-
public void f(); inter1");
public void g(); }
public void g() {
} System.out.println("g() de clase Uno-
inter1"); }
class Dos implements } inter1 {
public void f() {
System.out.println("f() de clase Dos-inter1"); }
public void g() {
System.out.println("g() de clase Dos-inter1"); }
} …
Uno u = new Uno();
Dos d = new Dos();
inter1 i1;
i1 = u;
i1.f();
i1 = d;
i1.f();

Problemas de diseño

Problema: herencia de clases vs. herencia de


interfaces.
Algunos patrones dependen de estas distinciones
entre los dos tipos de herencia.
Por ejemplo, el patrón Chain of Responsability
define objetos con un tipo común, pero no
necesariamente con una implementación común.
Muchos otros patrones (Command, Observer,
Strategy) se implementan vía clases abstractas
que son simplemente interfaces.
Problemas de diseño

Lema: “Programar para la interface, no la


implementación”.
Este es una máxima a seguir en el diseño
orientado a objetos.
La herencia permite definir objetos con interfaces
idénticas. Es importante pues el polimorfismo
depende de esto.
Si la herencia se utiliza propiamente, todas las
clases derivadas de una clase abstracta
comparten la misma interfaz, por lo que todos
pueden responder a los mismos mensajes.
Problemas de diseño

Los beneficios de manipular los objetos en


términos de la interface son principalmente:
Los clientes desconocen el tipo específico de los
objetos que utilizan.
Los clientes desconocen las clases que
implementan estos objetos. Conocen las clases
abstractas que definen sus interfaces.
Problemas de diseño

Lema: “Programar para la interface, no la


implementación”.
Este lema nos sugiere que no declaremos
variables como instancias de una clase concreta
particular.
En lugar de esto, utilizamos la interfaz definida por
una clase abstracta particular.
Esta buena práctica de diseño OO es recurrente en
muchos patrones.
Los patrones creacionales (Abstract Factory,
Builder, Factory Method, Prototype, Singleton)
hacen uso de estas técnicas
Problemas de diseño

Problema: Reutilización del software.


Conocemos los elementos de la OO y las bondades
de la reutilización del software.
Usualmente la herencia es vista como el
mecanismo de reutilización en la orientación a
objetos, pero existe otra técnica alternativa
denominada composición de objetos.
En esta técnica se obtiene nueva funcionalidad
ensamblando o componiendo objetos. Para esto es
necesario que los objetos posean interfaces bien
definidas.
Las dos técnicas tienen sus ventajas y
desventajas.
Problemas de diseño
Problema: Reutilización del software.
Ventajas de la herencia: definida en tiempo de
compilación, sustentada por el lenguaje, facilidad
de modificar la implementación reutilizada.
Desventajas de la herencia:
No puede cambiarse la implementación de lo
heredado en tiempo de ejecución.
Como la clase padre implementa parte de la
estructura de la clase hija, esta expone detalles de
implementación a sus herederos. Usualmente se
dice que la herencia “quiebra el
encapsulamiento”.
La clase padre y la clase hija están íntimamente
relacionadas. Un cambio en la clase padre provoca
un cambio en la clase hija.
Problemas de diseño
Problema: Reutilización del software.
La composición de los objetos se define en tiempo de
ejecución, por medio de objetos que adquieren
referencias a otros objetos.
Cada objeto debe respetar la interfaz de cada uno.
Como los objetos se acceden por medio de las
interfaces, no se quiebra el encapsulamiento.
Los objetos pueden ser reemplazados en tiempo de
ejecución, siempre y cuando posean la misma interfaz.
Favorece el encapsulamiento, y el comportamiento del
sistema depende de la interrelación entre objetos y no
está definido en las clases.
Esto permite enunciar el segundo lema de diseño OO:
“Favorecer la composición de objetos por sobre la
herencia”
Problemas de diseño
Problema: Reutilización del software.
En realidad la herencia y la composición trabajan
juntas. Una buena combinación de las dos permite
buenos diseños.
La delegación es la técnica que le da poder a la
composición de objetos.
En delegación, dos objetos están involucrados en
atender un mensaje o pedido: un objeto que
recibe en el mensaje y lo deriva en el objeto
delegado. (análogo al “super” en herencia!)
Problemas de diseño
Problemas de diseño
Problema: Reutilización del software.
La principal ventaja de la delegación es que es
fácil determinar el comportamiento en tiempo de
ejecución y cambiar el modo en que este
comportamiento se define.
Muestra que siempre podemos reemplazar
herencia por composición de objetos como
mecanismo de reutilización de código.
La delegación es una buena opción únicamente
cuando realmente simplifica más de lo que
complica.
Varios patrones de diseño utilizan delegación.
Strategy, por ejemplo, depende totalmente de
esta técnica.
Problemas de diseño

Problema: Relacionar estructuras de tiempo de


ejecución con estructuras de tiempo de
compilación
Vimos básicamente dos tipos de relaciones entre
clases:

Asociación: informalmente, modelando la


relación “conoce a”
Agregación: informalmente, modelando la
relación “tiene un”
Problemas de diseño

Hay que tener en cuenta las posibilidades del


lenguaje utilizado a la hora de implementar estas
relaciones.
La asociación entre clases corresponde a
referencias entre objetos y la agregación a objetos
expandidos o subobjetos.
Algunos lenguajes no admiten objetos expandidos.
¿Tiene sentido hablar de la relación de
agregación?
Por supuesto!
Puede implementarse con referencias +
restricciones
Problemas de diseño
Problema: Diseñar para el cambio
La clave para maximizar la reutilización es
anticipar los nuevos requerimientos y cambios a
los actuales y diseñar el sistema para que
evolucione adecuadamente.
Los patrones de diseño nos ayudan a alcanzar este
objetivo y evitar así las consecuencias del mal
diseño.
Presentan soluciones generales a casos puntuales.
Muchos de ellos favorecen directamente el
rediseño, evitando situaciones que provocan
demasiado acoplamiento entre clases,
abstrayendo propiedades comunes, dando
flexibilidad al comportamiento de objetos, etc.
Patrones de diseño

Recordemos
Los patrones de diseño son básicamente
descripciones de objetos que se
comunican y clases que son
personalizadas para resolver un problema
de diseño general en un contexto
particular [GoF]
Patrones de diseño GoF
PROPÓSITO

CREACIONAL ESTRUCTURAL COMPORTAMIENTO

CLASE Factory Method Adapter Interpreter


Template Method

Abstract Adapter Chain of


Factory Bridge Responsibility
Builder Command
SCOPE Composite
Prototype Decorator Iterator
OBJETO Singleton Mediator
Facade
Memento
Proxy
Flyweight
Observer
State
Strategy
Visitor
Patrones Creacionales
Creational Patterns
Patrones GoF - creacionales

Los patrones creacionales son patrones que


abstraen el proceso de instanciación. Procuran
independizar el sistema de cómo sus objetos son
creados, compuestos y representados.
Cobran especial importancia al tender los sistemas
a utilizar mas composición de objetos que
herencia.
En lugar de codificar un conjunto de
comportamientos complejos, se definen varios
comportamientos simples, los cuales son
ensamblados para modelar comportamientos
complejos.
Patrones GoF - creacionales

Crear objetos con un comportamiento especial


requiere más que simplemente instanciar una
clase.
Estos patrones encapsulan el conocimiento acerca
de las clases que el sistema usa y ocultan cómo se
crean instancias de estas clases.
Seguiremos el ejemplo que Gamma et al. proponen
para estudiar estos patrones: la creación de un
laberinto para juegos.
Un laberinto (maze) es un conjunto de habitaciones
(rooms). Una habitación conoce a sus vecinos, los
cuales pueden ser otra habitación, un muro o una
puerta a otra habitación.
Patrones GoF - creacionales

*
Patrones GoF - creacionales
*

Cada habitación tiene cuatro lados. En C++ podemos


declarar un enumerado:
enum Direction {North, South, East, West};
La clase MapSite es una clase abstracta para todos los
componentes del laberinto. Posee una sola operación
Enter() para simplificar.
El significado de Enter() depende del componente. Si es
una habitación, cambiamos de locación, si es una puerta y
está abierta, pasamos a la siguiente habitación.
Patrones GoF - creacionales
*

Room es una clase concreta que define las


relaciones entre los otros elementos del laberinto. El
número identifica la habitación. Wall y Door son
clases concretas que representan las paredes o
puertas en cada lado de la habitación.
class Room : public MapSite {
public:
Room(int roomNo);
MapSite* GetSide(Direction) const; class Wall : public MapSite {
void SetSide(Direction, MapSite*); public:
virtual void Enter(); Wall();
private: virtual void Enter();
MapSite* _sides[4]; };
int _roomNumber;
};

class Door : public MapSite { class Maze {


public: public:
Door(Room* = 0, Room* = 0); Maze();
virtual void Enter(); void AddRoom(Room*);
Room* OtherSideFrom(Room*); Room* RoomNo(int)
private: const;
Room* _room1; Room* _room2; private:
bool _isOpen; // ...
}; };
Patrones GoF - creacionales
Maze* MazeGame::CreateMaze () { Esta operación crea un
Maze* aMaze = new Maze; laberinto con dos habitaciones
Room* r1 = new Room(1);
Room* r2 = new Room(2); Puede simplificarse. Por
ejemplo, las habitaciones
Door* theDoor = new Door(r1, r2);
aMaze->AddRoom(r1); podrían crear las paredes.
aMaze->AddRoom(r2);
Pero esto sólo mueve código
r1->SetSide(North, new Wall);
de un lugar a otro 
r1->SetSide(East, theDoor);
r1->SetSide(South, new Wall); Además ¿Qué pasa si
r1->SetSide(West, new Wall); queremos agregar otro
r2->SetSide(North, new Wall); elemento al laberinto o
r2->SetSide(East, new Wall); modificar uno existente?
r2->SetSide(South, new Wall);
Los patrones creacionales
r2->SetSide(West, theDoor);
procuran hacer este diseño
return aMaze;
más flexible, quitando las
}
referencias explícitas a clases
concretas.
Patrones GoF - creacionales

Si CreateMaze invoca funciones virtuales (abstractas)


en lugar de los constructores, podemos agregar con
facilidad elementos al laberinto, puesto que no se
referencia explícitamente las clases intervinientes. Esto
lo propone el patrón Factory Method
Si CreateMaze recibe un objeto como parámetro para
crear habitaciones, paredes y puertas, podemos
cambiar los elementos del laberinto pasando diferentes
parámetros. Esto lo propone el patrón Abstract Factory.
Patrones GoF - creacionales

Si CreateMaze recibe un objeto que puede crear un


nuevo laberinto completo usando operaciones para
agregar habitaciones, paredes y puertas, podemos
usar herencia para cambiar partes del laberinto o
crearlo de otra forma. Esto lo propone el patrón Builder
Si CreateMaze se parametriza con varias habitaciones,
paredes y puertas prototípicas, las cuales se copian al
laberinto, podemos cambiar la composición del
laberinto reemplazando los prototipos por otros. Esto lo
propone el patrón Prototype.
Patrón Abstract Factory
Intención: Proveer una interfaz para crear
familias de objetos dependientes o relacionados
sin especificar sus clases concretas.
Alias: Kit (como sufijo usualmente).
Aplicabilidad: Usamos este patrón cuando
Un sistema debe independizarse de cómo sus
productos son creados, compuestos y
representados.
Un sistema debe ser configurado con una de
múltiples familias de productos.
Una familia de objetos debe ser usada en
conjunto, y debe reforzarse este hecho.
Queremos proveer una librería de productos,
pero sólo publicitar las interfaces, no las
implementaciones.
Cliente

AbstractFactory AbstractProductA
* *
CreateProductA() *
CreateProductB() *
ProductA2 ProductA1

ConcretFactory1 ConcretFactory2
CreateProductA() + CreateProductA() +
CreateProductB() + CreateProductB() +

AbstractProductB
*

ProductB2 ProductB1
Participantes

AbstractFactory: declara una interfaz para


operaciones que crean objetos producto
(abstractos)
ConcreteFactory: implementa las operaciones
para crear objetos producto concretos
AbstractProduct: declara una interfaz para un
tipo de producto
ConcreteProduct: define un objeto producto
que será creado por la correspondiente clase
factory concreta. Implementa la interfaz
AbstractProduct.
Cliente: usa sólo interfaces declaradas por
AbstractFactory y AbstractProduct.
Patrón Abstract Factory -
laberintos
Apliquemos el patrón Abstract Factory a los laberintos…
class MazeFactory {
public:
MazeFactory();
virtual Maze* MakeMaze() const
{ return new Maze; }
virtual Wall* MakeWall() const
{ return new Wall; }
virtual Room* MakeRoom(int n) const
{ return new Room(n); }
virtual Door* MakeDoor(Room* r1, Room*
r2) const
{ return new Door(r1, r2); }
};

La clase MazeFactory crea componentes de laberinto.


Patrón Abstract Factory -
laberintos
Maze* MazeGame::CreateMaze (MazeFactory& factory) {
Maze* aMaze = factory.MakeMaze();
Room* r1 = factory.MakeRoom(1);
Room* r2 = factory.MakeRoom(2);
Door* aDoor = factory.MakeDoor(r1, r2);
aMaze->AddRoom(r1);
aMaze->AddRoom(r2);
r1->SetSide(North, factory.MakeWall());
r1->SetSide(East, aDoor);
r1->SetSide(South, factory.MakeWall());
r1->SetSide(West, factory.MakeWall());
r2->SetSide(North, factory.MakeWall());
r2->SetSide(East, factory.MakeWall());
r2->SetSide(South, factory.MakeWall());
r2->SetSide(West, aDoor);
return aMaze;
}
Patrón Abstract Factory -
laberintos
Podemos crear otros tipos de laberintos
simplemente utilizando herencia e invocando la
operación anterior con el factory correspondiente.
class EnchantedMazeFactory : public MazeFactory {
public:
EnchantedMazeFactory();
virtual Room* MakeRoom(int n) const
{ return new EnchantedRoom(n, CastSpell()); }
virtual Door* MakeDoor(Room* r1, Room* r2) const
{ return new DoorNeedingSpell(r1, r2); }
protected:
Spell* CastSpell() const;
};
Patrón Builder

Intención: Separar la construcción de un objeto


complejo de su representación de forma tal que
el mismo proceso de construcción pueda crear
diferentes representaciones.
Aplicabilidad: Usamos este patrón cuando
El algoritmo para crear un objeto complejo debe
ser independiente de las partes que crean los
objetos y de cómo son ensamblados
El proceso de construcción debe permitir
diferentes representaciones para el objeto que es
construído.
Patrón Builder - Estructura
Patrón Builder Participantes

Builder especifica una interfaz abstracta para crear


partes del objeto producto.
ConcreteBuilder
Construye y ensambla partes del producto
implementando la interface Builder.
Define y lleva un registro de las representaciones que
crea.
Provee una interfaz para recuperar el producto.
Director construye un objeto usando la interfaz Builder
Product Representa el objeto complejo en construcción.
ConcreteBuilder construye la representación interna del
producto y define el proceso por el cual es ensamblado.
Incluye clases que definen las partes constituyentes,
incluyendo interfaces para ensamblar las partes en el
producto final
Patrón Builder

El cliente crea el objeto Director y lo configura


con el objeto Builder deseado.
Director notifica al Builder cuando una parte del
objecto producto deba ser creada
Builder administra pedidos del director y agrega
partes al producto
El cliente recupera el producto del Builder.
Patrón Builder -
Colaboraciones
Patrón Builder (en los
laberintos)
class MazeBuilder {
public:
virtual void BuildMaze() { }
virtual void BuildRoom(int room) { }
virtual void BuildDoor(int roomFrom, int roomTo)
{}
virtual Maze* GetMaze() { return 0; }
protected:
MazeBuilder();
};

Maze* MazeGame::CreateMaze (MazeBuilder&


builder) {
builder.BuildMaze();
builder.BuildRoom(1);
builder.BuildRoom(2);
builder.BuildDoor(1, 2);
return builder.GetMaze();
}
Patrón Builder - laberintos
class StandardMazeBuilder : public MazeBuilder {
public:
StandardMazeBuilder();
virtual void BuildMaze();
virtual void BuildRoom(int);
virtual void BuildDoor(int, int);
virtual Maze* GetMaze();
private:
Direction CommonWall(Room*, Room*);
Maze* _currentMaze;
};

StandardMazeBuilder::StandardMazeBuilder () { _currentMaze = 0
void StandardMazeBuilder::BuildMaze () { _currentMaze =
new Maze; }
Maze* StandardMazeBuilder::GetMaze () { return
_currentMaze; }
Patrón Builder - laberintos
void StandardMazeBuilder::BuildRoom (int
n) {
if (!_currentMaze->RoomNo(n)) {
Room* room = new Room(n);
_currentMaze->AddRoom(room); Maze* maze;
room->SetSide(North, new Wall); MazeGame game;
room->SetSide(South, new Wall); StandardMazeBuilder bld
room->SetSide(East, new Wall); …
room->SetSide(West, new Wall); game.CreateMaze(bldr);
} maze = bldr.GetMaze();
}
void StandardMazeBuilder::BuildDoor (int n1,
int n2) {
Room* r1 = _currentMaze->RoomNo(n1);
Room* r2 = _currentMaze->RoomNo(n2);
Door* d = new Door(r1, r2);
r1->SetSide(CommonWall(r1,r2), d);
r2->SetSide(CommonWall(r2,r1), d);
}
Patrón Factory Method

Intención: Define una interfaz para crear un


objeto, pero deja que las subclases decidan qué
clase instanciar. Permite a una clase relegar la
instanciación a sus subclases.
Alias: Virtual Constructor
Aplicabilidad: Usamos este patrón cuando
Una clase no puede anticipar qué objetos va a
crear.
Una clase quiere que sus subclases indiquen qué
objetos crea.
Patrón Factory Method -
Estructura
Patrón Factory Method

Product define la interfaz de los objetos que el método


factoryt va a crear.
ConcreteProduct implementa la interfaz Product.
Creator
Declara el método factory, el cual retorna un objeto del
tipo Product. Creator puede también definir una
implementación default del método factory y retornar un
objeto por defecto.
Puede invocar al método factory para crear un objeto de
tipo Product.
ConcreteCreator Redefine el método factory que
retorna una instancia de ConcreteProduct.
Patrón Factory Method
class MazeGame {
public:
Maze* CreateMaze();

// factory methods:
virtual Maze* MakeMaze() const
{ return new Maze; }
virtual Room* MakeRoom(int n) const
{ return new Room(n); }
virtual Wall* MakeWall() const
{ return new Wall; }
virtual Door* MakeDoor(Room* r1, Room* r2) const
{ return new Door(r1, r2); }
};
Podemos implementar CreateMaze en función de estos factory
methods. Diferentes juegos pueden heredar de MazeGame y
redefinir partes específicas del laberinto.
Patrón Prototype

Intención: especifica los tipos de objetos a crear


utilizando una instancia prototipo, y crea nuevos
objetos copiando esta instancia.
Aplicabilidad: Usamos este patrón cuando
Cuando las clases a instanciar son especificadas
en tiempo de ejecución, por ejemplo, por carga
dinámica.
Evitar construir una jerarquía de factories
paralela a una jerarquía de clases de products
Cuando instancias de una clase pueden tener
sólo uno de muchas combinaciones de estados,
lo que puede obtenerse clonando prototipos en
lugar de hacerlo manualmente.
Patrón Prototype - Estructura
Patrón Prototype -
Participantes

Prototype declara una interface para la


autoclonación.

ConcretePrototype implementa una operación


para clonarse a sí mismo.

Client crea un nuevo objeto solicitándole al


prototipo que se clone a sí mismo.
Patrón Prototype - laberintos
class MazePrototypeFactory : public MazeFactory {
public:
MazePrototypeFactory(Maze*, Wall*, Room*, Door*);

virtual Maze* MakeMaze() const;


virtual Room* MakeRoom(int) const;
virtual Wall* MakeWall() const;
virtual Door* MakeDoor(Room*, Room*) const;

private:
Maze* _prototypeMaze;
Room* _prototypeRoom;
Wall* _prototypeWall;
Door* _prototypeDoor;
};
El constructor inicializa los prototipos con los
parámetros.
Patrón Prototype
Las funciones miembro para crear habitaciones, puertas
paredes simplemente solicitan clones a los prototipos.

Wall* MazePrototypeFactory::MakeWall () const {


}
Door* MazePrototypeFactory::MakeDoor (Room* r1, Room *r2)
const {
Door* door = _prototypeDoor->Clone();
door->Initialize(r1, r2);
return door;
}
Wall* MazePrototypeFactory::MakeWall () const {
return _prototypeWall->Clone();
}

Door* MazePrototypeFactory::MakeDoor (Room* r1, Room


*r2) const {
Door* door = _prototypeDoor->Clone();
door->Initialize(r1, r2);
return door;
}
Patrón Prototype

Para crear laberintos utilizamos la operación


MakeMaze()
Para crear laberintos con otras características (por
ejemplo, habitaciones con ciertas propiedades
adicionales), simplemente inicializamos
MazePrototypeFactory con otros prototipos.
Patrón Singleton

Intención: Asegura que una clase posea sólo una


instancia y provee un punto global de acceso a
ella.
Aplicabilidad: Usamos este patrón cuando
Debe existir exactamente una instancia de
una clase y debe ser accedida por los clientes
a través de un punto de acceso conocido.
Cuando la única instancia debe ser
extensible por herencia y los clientes pueden
usar esta nueva versión sin modificar sus
códigos.
•Estructura:

•Participantes:
Singleton
• Define una operación Instance que permite a los
clientes acceder su instancia única.
• Es una operación de la clase (static). Es
responsable por crear su propia instancia única.
Patrón Singleton
Si necesitamos que exista una única instancia de
la clase MazeFactory que produce el laberinto,
debemos contemplar la siguiente estructura:
class MazeFactory {
public:
static MazeFactory*
Instance();

// la interfaz completa va acá

protected:
MazeFactory();
private:
static MazeFactory* _instance;
};
Bibliografía

Design Patterns: elements of reusable OO


SW. Gamma, Helm, Johnson, Vlissides.
Patrones de diseño

Recordemos
Los patrones de diseño son básicamente
descripciones de objetos que se
comunican y clases que son
personalizadas para resolver un problema
de diseño general en un contexto
particular [GoF]
Patrones de diseño GoF
PROPÓSITO

CREACIONAL ESTRUCTURAL COMPORTAMIENTO

Factory Method Adapter Interpreter


CLASE Template Method

Abstract Factory Adapter Chain of


Builder Bridge Responsibility
Command
Prototype Composite
Iterator
Singleton Decorator
SCOPE Mediator
Facade
Memento
OBJETO Proxy
Flyweight
Observer
State
Strategy
Visitor
Patrones
Estructurales
Structural Patterns
Patrones GoF - estructurales
Los patrones estructurales se refieren a cómo las
clases y los objetos son organizados para conformar
estructuras más grandes.
La mayor variedad se encuentra en los patrones
estructurales de objetos, en donde los objetos se
componen para obtener nuevas funcionalidades.
Tener en cuenta la importancia del concepto de
interfaz. Los objetos interactúan entre ellos por
medio de sus interfaces.
Pueden ser patrones de clases, basados en la
utilización de herencia, o patrones de objetos,
basados en la técnica de composición.
Patrón Adapter
•Intención: Convertir la interfaz de una clase en otra
interfaz que el cliente espera.
•Alias: Wrapper.
•Aplicabilidad: Usaremos este patrón cuando:
• Deseamos usar una clase existente, pero su interfaz
no es la que necesitamos.
• Queremos crear una clase reutilizable que coopera
con otras clases no relacionadas, probablemente con
interfaces incompatibles.
• Necesitamos usar varias subclases, pero no es
práctico adaptar sus interfaces heredando de ellas, por
lo que apelamos a un objeto adaptador. En este caso
se utiliza la versión Object Adapter
Patrón Adapter

Se realiza una encapsulación de los métodos y


una traducción directa.
Lo usaremos cuando querramos ver clases de
una forma determinada, y la apariencia de esas
clases en origen no correspondan con nuestras
necesidades.
Patrón Adapter
•Motivación: Aplicación para gráficos.
Patrón Adapter

•Estructura: (versión Class-Adapter)


Patrón Adapter
•Estructura: (versión Object-Adapter)
Patrón Adapter

•Participantes:
• Target: define la interfase específica del
dominio que usa el cliente
• Client: Colabora con los objetos que se ajustan
a la interfaz de target
• Adaptee: Define una interfaz que se necesita
adaptar
• Adapter: Adapta la interfaz de adaptee a target
class Shape {
public: Interfaz
Shape(); esperada en
virtual void BoundingBox(Point& bttmLft, Point& nuestra
tpRight) const;
virtual Manipulator* CreateManipulator() const; aplicación
};

class TextView {
public: Clase que
TextView(); queremos
void GetOrigin(Coord& x, Coord& y) const; usar, pero la
void GetExtent(Coord& width, Coord& height)
interfaz es
const;
virtual bool IsEmpty() const; incompatible
}; .
class TextShape : public Shape, private TextView {
public: Clase que
TextShape(); adapta
virtual void BoundingBox(Point& bttmLft, Point&
tpRght) const; TextView
virtual bool IsEmpty() const; con la
virtual Manipulator* CreateManipulator() const; interfaz
}; Shape
Patrón Adapter
El object-adapter utiliza composición de objetos en lugar
de herencia múltiple.
class TextShape : public Shape {
public:
TextShape(TextView*);
virtual void BoundingBox(Point& bttmLft, Point&
topRight) const;
virtual bool IsEmpty() const;
virtual Manipulator* CreateManipulator() const;
private:
TextView* _text;
};

Cada operación del adapter debe derivar (“adaptar”) las


consultas de la interfaz Shape en función de la interfaz de
TextView (interfaz adaptada).
Consecuencias

Un adapter a nivel clase:


Adapta comprmetiendose a una clase
adaptee concreta (no sirve si queremos
adaptar una clase y sus subclases)
Permite override parte del comportamiento
de adaptee
Un adapter a nivel objeto
Permite que un solo adapter trabaje con
muchos adaptee (todas sus subclases!)
Hace más difícil override el comportamiento
del adaptee (subclassing y referirse a la
subclase en vez de al adaptee)
Bridge: motivación

De esta forma comienzan a mezclarse abstracciones


de windows con clases concretas de
implementaciones para diversas aplicaciones.
Lo mejor es separar las jerarquías de abstracciones de
las de implementaciones. Esto permite extender
ambos conceptos.
Bridge: motivación
Patrón Bridge
•Intención: Desacopla una abstracción de su
implementación de forma tal que las dos puedan
variar o evolucionar en forma independiente.
•Alias: Handle/Body.
•Aplicabilidad: Usaremos este patrón cuando:
• Queremos evitar un ligamiento constante entre
la abstracción y la implementación
• Tanto la implementación como la abstracción
deben ser extensibles por herencia.
• Los cambios en la implementación de una
abstracción no deben tener impacto en los
clientes.
• Queremos ocultar completamente la
implementación a los clientes.
Patrón Bridge
•Estructura:

Bridge
Patrón Bridge
•Participantes:
• Abstraction: define la interfaz de la abstracción y
mantiene una referencia a un objeto de tipo
Implementor.
• RefinedAbstraction: extiende la interfaz definida
por Abstraction.
• Implementor: define la interfaz para clases de
implementación. No necesariamente debe
corresponder a la interfaz Abstraction.
• ConcreteImplementor: implementa la interfaz
Implementor y define su implementación
concreta.
Leer ejemplo de implementación (abstracción
Window) del libro de Gamma et al.
Patrón Bridge
Leer ejemplo de implementación (abstracción
Window) del libro de Gamma et al.

Un abstract factory resulta adecuado para crear y


configurar un tipo particular de bridge
Estructuras compuestas de
objetos
Algunas aplicaciones necesitan manipular objetos
básicos y agrupaciones de estos objetos de manera
indistinta.
Por ejemplo, una aplicación gráfica necesita manipular
líneas, círculos y rectángulos tanto como figuras
compuestas por estos elementos, o por otras figuras
compuestas. Para la aplicación gráfica, el tratamiento
es distinto, aún cuando para el usuario es el mismo.
La aplicación necesita distinguir estos objetos, lo cual
complica un poco el escenario general.
La clave en este caso es abstraerse de los dos
elementos, y trabajar con una clase abstracta que
represente tanto los objetos primitivos como los
objetos compuestos.
Estructuras compuestas de
objetos
En este caso la organización puede ser:

Esta composición recursiva de objetos es la


propuesta del patrón de diseño Composite.
Patrón Composite

•Intención: Compone objetos en estructuras de árbol


para representar jerarquías de relación “es-parte-
de”.
•Aplicabilidad: Usaremos este patrón cuando:
• Queremos representar jerarquías de objetos
modelando la relación “es-parte-de” (part-whole
hierarchies)
• Queremos que el cliente ignore la distinción
entre objetos compuestos y objetos individuales. El
cliente tratará todos los objetos de la estructura
compuesta de manera uniforme.
Patrón Composite
Patrón Composite

• Component: declara la interfaz para los


objetos en la composición.
• Implementa el comportamiento por defecto
para todos los objetos.
• Declara la interfaz para acceder y manipular
los componentes hijos.
• Opcionalmente implementa una interfaz para
acceder al padre.
Patrón Composite
• Leaf: representa los objetos simples en la
composición. Define el comportamiento de
tipos primitivos.
• Composite: define el comportamiento para
componentes compuestos. Almacena estos
componentes e implementa operaciones
relacionadas a su administración.
• Client: Manipula los objetos en la composición
por medio de la interfaz Component.
Este patrón simplifica la implementación del
cliente
Patrón Composite
Usualmente las clases que conforman este patrón
incluyen, de ser posible, cada una su propio
destructor.
Es factible agregar referencias a los padres, lo cual
facilita el recorrido de la estructura. Sin embargo, es
importante no perder la consistencia (el padre debe
apuntar al hijo y viceversa)
Se pueden utilizar varias estructuras de datos para
que Composite matenga referencias a sus hijos (listas
enlazadas, tablas hash, etc).
En algunos casos es importante el orden de los hijos
(por ejemplo, un parse-tree de un proceso de
compilación). En este caso es factible combinar este
patrón con el patrón de diseño Iterator, que veremos
Objetos, clases y
responsabilidades

A veces es deseable agregar responsabilidades a


objetos individuales, y no a toda la clase a la que
pertenece.
Una conjunto de herramientas para desarrollar
interfaces gráficas debería permitir agregar bordes o
comportamientos como el scrolling a cualquier
componente gráfica.
Objetos, clases y
responsabilidades

Una forma de agregar responsabilidades es por


medio de la herencia. Heredar un borde significa
agregarlo a todas las subclases.
Pero la elección del borde se resuelve en forma
estática. El cliente no controla cómo y cuándo decorar
el componente con un borde.
Una aproximación más flexible es “encerrar” el
componente a decorar en otro objeto que agrega el
borde. Este último se denomina el objeto decorador.
Decorando objetos
Patrón Decorator
•Intención: Agrega responsabilidades a un objeto de
manera dinámica. Provee una alternativa a la
herencia cuando deseamos agregar funcionalidad.
•Alias: Wrapper
•Aplicabilidad: Usaremos este patrón cuando:
• Queremos agregar responsabilidades a objetos
individuales de manera dinámica y transparente, sin
afectar otros objetos.
• Deseamos implementar responsabilidades que
pueden removerse.
• Cuando la extensión utilizando herencia es
impracticable, por ejemplo, pues provocaría
demasiadas variaciones y un gran número de clases.
Patrón Decorator
Patrón Decorator
•Participantes:
• Component: define la interfaz para los objetos
que pueden tener responsabilidades agregadas
dinámicamente.
• ConcreteComponent: define un objeto al cual se
le pueden agregar responsabilidades
dinámicamente.
• Decorator: mantiene una referencia a un objeto
Component y define la interfaz que conforma la
interfaz de Component.
• ConcreteDecorator: agrega responsabilidades al
componente.
Leer las secciones Consecuencias e Implementación del libro
de Gamma et. al.
Patrón Decorator - ejemplo
class Decorator : public VisualComponent {
class VisualComponent { public:
public: Decorator(VisualComponent*);
VisualComponent(); virtual void Draw();
virtual void Draw(); virtual void Resize();
virtual void Resize(); // ...
// ... private:
}; VisualComponent* _component;
};

Decorador Concreto
class BorderDecorator : public Decorator {
public:
BorderDecorator(VisualComponent*, int
borderWidth);
virtual void Draw(); {
private:
void DrawBorder(int); Decorator::Draw
private: ();
int _width;
}; DrawBorder(_wid
th);
}
Patrón Decorator - ejemplo
Supongamos que tenemos esta operación en Window, para
agregar componentes visuales
void Window::SetContents (VisualComponent* contents) {
// ...
}
Creamos un objeto TextView y una ventana en la cual ubicarlo.
Window* window = new Window;
TextView* textView = new TextView;
window->SetContents(textView);
Pero como queremos un TextView con borde y con barra de
desplazamiento (scroll bar), lo decoramos acordemente antes de
ubicarlo en la ventana.
window->SetContents(
new BorderDecorator( new ScrollDecorator(textView), 1)
);

Como Window accede a sus componentes por medio de la


interfaz VisualComponent, no tiene conciencia de la decoración
de TextView.
Objetos y subsistemas
Estructurar un sistema en subsistemas reduce la
complejidad.
Una de las premisas de diseño es la minimización de
las comunicaciones entre subsistemas, para evitar la
excesiva dependencia.
Una forma de lograr esto es centralizar los accesos al
subsistema por medio de puntos de acceso
generalizados, lo que se suele denominar facade
(fachada).
Patrón Facade
El patrón de diseño Facade provee una interfaz
unificada para acceder a un conjunto de interfaces en
un subsistema.
Define una interfaz de alto nivel que facilita la
utilización del subsistema como un todo.

Dado que existe usualmente un solo objeto Facade,


puede combinarse con el patrón Singleton.
Patrón Facade

El patrón oculta a los clientes los componentes del


subsistema, reduciendo el número de objetos que los
clientes deben manipular.
Disminuye el acoplamiento entre un subsistema y sus
clientes, al proveer puntos de acceso específicos, que
permiten manejar un cierto número de objetos.
Reduce las dependencias de compilación.

Es un patrón muy simple. Leer el ejemplo de la


implementación de un subsistema de un compilador.
Administrando muchos
objetos
Las aplicaciones se benefician por el uso de objetos,
aunque ciertas implementaciones se tornan
demasiado costosas.
La identificación de los objetos con la granularidad
adecuada forma parte de los desafíos del DOO
En un editor de texto no será eficiente crear un
objeto por cada letra del documento, con toda la
información pertinente a las letras.
El problema es el costo. Tener tantos objetos requiere
memoria y puede incurrir en un incremento del
tiempo de ejecución de la aplicación.
Administrando muchos
objetos
Un objeto flyweight puede ser utilizado en diferentes
contextos en forma simultánea.
El objeto flyweight es compartido por diferentes
contextos, pero en cada uno de ellos este hecho es
imperceptible.
Esto es posible distinguiendo dos tipos de estados
para el objeto modelado: estado intrínseco y estado
extrínseco.
El estado intrínseco se mantiene en el objeto
flyweight propiamente dicho, y consiste de la
información independiente del contexto en el cual el
flyweight es utilizado.
Administrando muchos
objetos

El estado extrínseco depende y varía del


contexto en el cual es utilizado. Los
objetos clientes son responsables por
otorgar el estado extrínseco al flyweight
si lo necesita.
Por ejemplo, en el caso de los
caracteres, la posición en el documento
forma parte del estado extrínseco de ese
objeto, y el código UNICODE o ASCII es
parte del estado intrínseco.
Patrón Flyweight
•Intención: Permitir manipular un gran número de
objetos con granularidad fina, compartiendo objetos.
•Alias: Wrapper
•Aplicabilidad: Usaremos este patrón cuando:
• Una aplicación usa una gran cantidad de objetos.
• Los costos de almacenamiento son altos
precisamente por esa cantidad de objetos.
• La mayor parte del estado de un objeto es extrínsico.
• Muchos grupos de objetos se reducen a pocos al
eliminar el estado extrínsico.
• La aplicación no depende de la identidad de los
objetos (la igualdad de dos objetos flyweight es
verdadera!)
Patrón Flyweight
Patrón Flyweight
•Estructura dinámica:
Patrón Flyweight

•Participantes:
• Flyweight: declara una interfaz por medio de la
cual los objetos flyweight pueden actuar sobre el
estado extrínseco.
• ConcreteFlyweight: implementa la interfaz
Flyweight y agrega almacenamiento para el
estado intrínseco.
• UnsharedConcreteFlyweight: objeto flyweight no
compartible.
Patrón Flyweight

FlyweightFactory: crea y administra objetos


flyweight. Se asegura que los objetos sean
compartidos correctamente. Cuando el
cliente solicita un objeto flyweight, retorna
una instancia existente o crea una nueva en
caso contrario.
Client: mantiene una referencia a los objetos
flyweight que solicitó. Es el responsable de
computar y/o almacenar el estado extrínseco
de cada objeto.

Leer sección Implementación del patrón


en el libro de Gamma et. al.
Rapidez y eficiencia
En algunos casos es deseable que la creación de los
objetos sea controlada en pos de la eficiencia en el
escenario general. Pensemos en un documento con
imágenes complejas. Algunas de estas imágenes pueden
ser costosas de crear.
Sin embargo, la apertura y manipulación del documento
debe ser rápida, por lo que deberíamos postergar la
creación de las imágenes hasta tanto realmente se
necesiten
La solución es crear objetos en demanda, y en su lugar,
mientras tanto, ubicamos objetos proxy que representan
los originales
Patrón Proxy
•Intención: Proveer un objeto subrogante o reemplazante
de otro objeto, para controlar el acceso a éste.
•Aplicabilidad: Este patrón es aplicable siempre que
necesitemos una referencia más detallada a un objeto que
el simple puntero:
• Un proxy remoto que provee representación para un
objeto en un espacio de direcciones diferente.
• Un proxy virtual que crea objetos costosos en
demanda (como el de las imágenes del documento).
• Un proxy de protección, que controla el acceso al
objeto original.
• Una referencia inteligente (smart reference) como
reemplazo de un simple puntero, que realiza acciones
adicionales cuando el objeto es accedido (eg. conteo de
ref., locks para escritura)
Patrón Proxy
Patrón Proxy

•Estructura dinámica:
Patrón Proxy
• Participantes:
• Proxy: mantiene una referencia que le permite al
proxy acceder al objeto real.
Provee una interfaz idéntica a Subject de forma tal
que el proxy puede sustituir al objeto real.
Controla el acceso al objeto real.
• Subject: define la interfaz común para RealSubject y
Proxy de forma tal que Proxy pueda ser usada en
cualquier lugar en el que se espere usar RealSubject.
• RealSubject: define el objeto real que el proxy
representa.
class Graphic { Interfaz para los
public: objetos gráficos.
virtual ~Graphic();
virtual void Draw(const Point& at) = 0;
virtual void HandleMouse(Event& event)
= 0;
virtual const Point& GetExtent() = 0;
virtual void Load(istream& from) = 0;
virtual void Save(ostream& to) = 0;
protected:
Graphic();
};
class Image : public Graphic { Clase para
public:
Image(const char* file); // carga imagen de
mostrar
archivo archivos de
virtual ~Image(); imágenes.
virtual void Draw(const Point& at);
virtual void HandleMouse(Event& event);
virtual const Point& GetExtent();
virtual void Load(istream& from);
virtual void Save(ostream& to);
... };
class ImageProxy : public Graphic { Proxy para las
public: imágenes.
ImageProxy(const char* imageFile);
virtual ~ImageProxy(); Tiene la misma
virtual void Draw(const Point& at); interfaz que
virtual void HandleMouse(Event& Image
event);
virtual const Point& GetExtent();
virtual void Load(istream& from);
virtual void Save(ostream& to);ImageProxy::ImageProxy (const char*
protected: fileName) {
Image* GetImage(); _fileName = strdup(fileName);
private: _extent = Point::Zero;
Image* _image; _image = 0;
Point _extent; }
char* _fileName;
};
Image* ImageProxy::GetImage() {
if (_image == 0) {
_image = new Image(_fileName);
}
return _image;
}
Patrón Proxy
GetExtent del proxy retorna el tamaño almacenado, si
no es posible se lo consulta a la imagen real.
const Point& ImageProxy::GetExtent () {
if (_extent == Point::Zero) {
_extent = GetImage()->GetExtent();
}
return _extent;
}

void ImageProxy::Draw (const Point& at) {


GetImage()->Draw(at);
}

void ImageProxy::HandleMouse (Event& event) {


GetImage()->HandleMouse(event);
}
Patrón Proxy
Podemos agregar una imagen al documento de la
siguiente manera:

TextDocument* text = new TextDocument;
...
text->Insert(new
ImageProxy(“Nombre_de_Archivo_de_Imagen"));
Patrones estructurales
Adapter y Bridge parecen similares pero se
diferencian en su intención:
• Adapter resuelve incompatibilidades entre
interfaces
• Bridge separa una abstracción de su
implementación
Composite y Decorator tienen diagramas similares
• Decorator agrega responsabilidades sin
subclassing
• Composite estructura las clases de forma que
objetos relacionados puedan ser tratados
uniformemente
Algo similar sucede con Proxy y Decorator
Patrones de diseño

Recordemos
Los patrones de diseño son básicamente
descripciones de objetos que se
comunican y clases que son
personalizadas para resolver un problema
de diseño general en un contexto
particular [GoF]
Patrones de diseño GoF
PROPÓSITO

CREACIONAL ESTRUCTURAL COMPORTAMIENTO

Factory Method Adapter Interpreter


CLASE Template Method

Abstract Factory Adapter Chain of


Builder Bridge Responsibility
Command
Prototype Composite
Iterator
Singleton Decorator
SCOPE Mediator
Facade
Memento
OBJETO Proxy
Flyweight
Observer
State
Strategy
Visitor
Patrones de
Comportamiento
Behavioral Patterns
Patrones GoF -
comportamiento
Los patrones de comportamiento se centran en los
algoritmos y la asignación de responsabilidades
entre los objetos.
Son patrones tanto de clases y objetos (similares a
los anteriores) como de comunicación entre ellos.
Caracterizan flujo de control complejo.
Estos patrones también se clasifican en patrones de
clases y patrones de objetos.
Patrones GoF -
comportamiento
Los patrones de comportamiento de clases
(behavioral class patterns) utilizan herencia para
distribuir el comportamiento entre las clases.
Los patrones de comportamiento de objetos
(behavioral object patterns) utilizan composición de
objetos en lugar de herencia. Algunos describen
cómo los objetos cooperan entre sí para realizar una
tarea compleja, imposible para sólo uno de ellos.
Atención de pedidos

En algunos casos es factible que se necesario


realizar una consulta sobre algún objeto
específico, aún sabiendo que tal vez no sea ese el
objeto que resolverá realmente el pedido.
Ejemplo: ayuda contextual de ventanas

El objeto sobre el cual se hace click no


necesariamente es el objeto que provee la ayuda
(que puede no existir)
En ese caso se provee una ayuda más general
Atención de pedidos

La idea es organizar los objetos de forma tal que


pueda desacoplarse el objeto que origina la ayuda
del que la provee.
El requerimiento atraviesa una cadena de objetos
hasta que alguno de ellos pueda atender
propiamente este pedido.
Atención de pedidos

El primer objeto en la cadena recibe el pedido


(request) y lo atiende o lo deriva a otros objetos.
El objeto que hizo el pedido no tiene conocimiento de
quién lo atiende. Decimos en este caso que el pedido
tiene un receptor implícito.
Atención de pedidos

Tal es la propuesta del patrón Chain of Responsability


Patrón Chain of
Responsability

•Intención: Evitar acoplar el emisor de un pedido


con el receptor, permitiendo que más de un objeto
pueda manejar el pedido. Encadena los objetos
receptores, los cuales se pasan el pedido hasta
que alguno pueda atenderlo.
•Aplicabilidad: Usaremos este patrón cuando:
• Más de un objeto puede atender un pedido, y
el receptor no es conocido a priori.
• Queremos realizar un pedido a uno de varios
objetos especificando el receptor explícitamente.
• El conjunto de objetos que atiende un pedido
es determinado dinámicamente.
Patrón Chain of
Responsability - Estructura
Patrón Chain of
Responsability

• Participantes:
• Handler: define una interfaz para atender
pedidos. Implementa opcionalmente el link
sucesor.
• ConcreteHandler: atiende los pedidos por los
cuales es responsable. Puede acceder a sus
sucesor.
• Client: inicia el pedido a un objeto
ConcreteHandler de la cadena.

Ver los ejemplos de GoF y de Applied Java Patterns


Requerimientos sobre objetos
desconocidos
A veces es necesario solicitar servicios que
desconocemos sobre objetos que ignoramos.
Por ejemplo, por medio de herramientas (toolkits)
para implementar menús de usuario, con botones,
listas, etc.
Obviamente, una opción del menú no implementa
explícitamente la tarea. Sólo la aplicación que usa el
toolkit sabe qué debe hacerse en qué objeto.
Requerimientos sobre objetos
desconocidos
La idea es que el pedido mismo sea un objeto.
Podemos declarar una clase Command que declara
una interfaz para ejecutar operaciones.
Las clases Command concretas (descendientes)
especifican el receptor de la operación, vinculando
el requerimiento con el objeto en cuestión.
Patrón Command

•Intención: Encapsular el pedido (mensaje,


solicitud) como un objeto, permitiendo
parametrizar los clientes con diferentes pedidos,
encolar o registrar los pedidos y proveer
operaciones para deshacer pedidos previos.
•Alias: Action, Transaction.
Patrón Command
•Aplicabilidad: Usaremos este patrón cuando:
• Parametrizar objetos para una acción a
realizar.
• Especificar, encolar y ejecutar pedidos en
momentos diferentes.
• Proveer la posibilidad de deshacer acciones.
• Proveer registros de auditoría y salvaguarda.
• Estructurar un sistema en función de
operaciones de alto nivel construidas en base a
operaciones primitivas.
Patrón Command - Estructura
Patrón Command
•Diagrama de interacción
Patrón Command –
Participantes
• Command: declara una interfaz para ejecutar
operaciones.
• ConcreteCommand: define el vínculo entre el
objeto Receiver y una acción. Implementa
Execute() invocando las operaciones
correspondientes sobre el Receiver.
• Client: crea un objeto ConcreteCommand y
setea el receptor.
• Invoker: solicita al comando realizar su tarea.
• Receiver: conoce cómo realizar operaciones
asociadas al llevar a cabo un pedido. Cualquier
clase puede actuar como Receiver.
Interpretación de lenguajes
Es una tarea frecuente en la computación.
Nuestras aplicaciones pueden requerir lenguajes
simples, con gramáticas simples, que permitirán
construir sentencias representando información
específica.
La representación de las gramáticas del lenguaje
puede hacerse de forma tal de favorecer el
procesamiento de las sentencias.
El patrón Interpreter procura solucionar este
problema de diseño.
En él podremos representar símbolos terminales y no
terminales de la gramática, junto con las reglas de
producción.
Interpretación de lenguajes

Esta estructura representa la expresión regular raining


{dogs,cats}*
Patrón Interpreter
•Intención: Dado un lenguaje, define una
representación para su gramática junto con un
intérprete que usa la representación para
interpretar las sentencias del lenguaje.
•Aplicabilidad: Usaremos este patrón cuando existe
un lenguaje que es necesario interpretar, y podemos
representar sentencias en el lenguaje como árboles
sintácticos abstractos.
• La gramática debe ser simple. Para gramáticas
complejas los árboles se vuelven intratables.
• La eficiencia no es un factor crítico en el
desarrollo.
Patrón Interpreter
Patrón Interpreter -
Participantes
• AbstractExpression: declara una operación
abstracta Interpret que es común a todos los
nodos en el árbol sintáctico.
• TerminalExpression: implementa Interpret
asociada a símbolos terminales de la gramática.
Se requiere una instancia por cada símbolo
terminal.
• NonterminalExpression: requerida para cada
regla de la gramática. Mantiene instancias de
tipo AbstractExpression para cada símbolo no
terminal. Implementa Interpret para estos
símbolos.
Patrón Interpreter -
Participantes
• Context: contiene información global al
intérprete.
• Client: Construye el árbol sintáctico de la
sentencia particular de la gramática. Invoca la
operación Interpret.
Conscuencias

Es fácil cambiar la gramática


Es fácil implementar la gramática
No sirve para gramáticas complejas
Recorriendo estructuras de
objetos
Un objeto que contenga otro objeto agregado
(como listas) debería permitir el acceso a sus
elementos, pero revelando lo menos posible la
estructura interna del objeto agregado.
La idea es proveer una forma de recorrer una
estructura de objetos y de obtener sus
componentes independientemente de la
estructura específica.
Recorriendo estructuras de
objetos
Cada estructura puede definir sus propia forma de
recorrerse, pero la metodología es la misma.
Los lenguajes modernos incorporan esta técnica
para todos sus tipos estructurados predefinidos.
Se denominan iteradores.
El patrón Iterator nos indica cómo construir esto
para nuestras propias estructuras complejas.
Esto nos permite definir iteradores para diferentes
políticas de recorridos sin enumerarlas en la
interfase de lista
Patrón Iterator

•Intención: Provee un modo de acceder a los


elementos de un objeto agregado en forma
secuencial, sin exponer su representación interna.
•Alias: Cursor
•Aplicabilidad: Usaremos este patrón cuando:
• Queremos acceder al contenido de los objetos sin
exponer su representación interna.
• Queremos proveer múltiples recorridos de
objetos agregados.
• Queremos proveer una interfaz uniforme para
acceder diferentes estructuras agregadas
(iteración polimórfica)
Patrón Iterator - Estructura

Observemos que se usa Factory Method


Patrón Iterator
•Participantes:
• Iterator: define una interfaz para acceder y
recorrer elementos.
• ConcreteIterator: implementa la interfaz Iterator.
Mantiene registro de la posición actual del
recorrido.
• Aggregate: define una interfaz para crear un
objeto Iterator.
• ConcreteAggregate: implementa la interfaz de
creación Iterator y devuelve una instancia de
ConcreteIterator.
Ordenando la marea de
objetos
La construcción de un sistema orientado a objetos
puede derivar en la definición de un gran número de
objetos que cooperativamente resuelven una tarea.
En muchos casos se determina una estructura de
objetos con muchas conexiones entre ellos.
Una alternativa es incluir un objeto que controle y
coordine la interacción de un grupo de objetos.
Este objeto evita que los otros se referencien
explícitamente unos a otros para realizar ciertas
operaciones.
Este objeto se denomina mediador. Los objetos solo
conocen al mediador, y por medio (!) de él se
interrelacionan con los otros objetos del grupo.
Patrón Mediator
•Intención: Define un objeto que encapsula cómo un
conjunto de objetos interactúa. Promueve el bajo
acoplamiento y es posible variar la interacción
independientemente.
•Aplicabilidad: Usaremos este patrón cuando:
• Un conjunto de objetos se comunica en una
forma bien definida, pero compleja y difícil de
entender.
• La reutilización de un objeto es dificultosa pues
se comunica con varios objetos en el sistema.
• Un comportamiento distribuido en varias clases
debe ser personalizado sin agregar muchas
subclases.
Patrón Mediator
Patrón Mediator
•Participantes:
• Mediator: define una interfaz para que se
comuniquen los objetos Colleague.
• ConcreteMediator: implementa el
comportamiento cooperativo coordinando objetos
Colleague. Conoce y mantiene a sus colegas.
• Clases Colleague: cada clase conoce a su objeto
Mediator. Siempre se comunica con otros objetos
por medio del objeto Mediator.
Estados del objeto
En muchos casos es necesario registrar el estado
interno de un objeto, por ejemplo, al permitir
deshacer ciertas acciones en nuestra aplicación.
El objetivo es recordar el estado interno del objeto,
para poder eventualmente restaurarlo más tarde. No
debería exponerse el estado interno del objeto, pues
viola el encapsulado.
Estados del objeto
Una forma de realizar esta tarea es crear un objeto
que almacene un recuerdo (snapshot) del estado
interno del objeto.
El primero se denomina memento, y el segundo el
objeto originador del memento.
El mecanismo para deshacer acciones solicitará un
memento del originador para restaurarlo al estado
recordado.
Estos detalles se especifican en el patrón de diseño
Memento.
Patrón Memento

•Intención: Sin violar el encapsulamiento, captura y


externaliza el estado interno de un objeto de forma
tal que el objeto puede ser restaurado a ese mismo
estado más tarde.
•Aplicabilidad: Usaremos este patrón cuando:
• Un registro (snapshot) del estado de un objeto
debe ser guardado para restaurarlo más tarde, y
• Una interfaz directa para obtener el estado
expondría detalles de implementación y quiebraría
el encapsulado del objeto.
Patrón Memento
Patrón Memento –
Estructura de colaboración

aCaretaker solicita un memento del originador, lo retiene


durante un tiempo, y luego se lo devuelve al originador para
restauración.
Patrón Memento -
Participantes
• Memento: almacena el estado interno del objeto
Originator. Almacena tanto como sea necesario o
requerido. Protege contra accesos que no provean
de su originador.
• Originator: crea un memento registrando su estado
actual. Usa el memento para restaurar el estado
interno.
• Caretaker: responsable por la salvaguarda del
memento. Nunca opera o examina el contenido del
memento.
Leer la implementación ejemplo de Memento en
“Applied Java Patterns” (pag.63)
Objetos y consistencia
Un problema recurrente en la programación orientada
a objetos es el mantenimiento de la consistencia entre
objetos relacionados y cooperativos, sin alto
acoplamiento.
Este problema cobra también importancia en la
representación visual de objetos complejos. Cuando el
objeto cambia, su representación visual (que también
es un objeto) debe cambiar acordemente (ejemplo:
gráficos en Excel)
Una forma de lograr esto es organizar los objetos en
observadores de un aspecto particular (observers-
subject)
La propuesta de organización se refleja en el patrón
Observer.
Patrón Observer
•Intención: Define una dependencia entre objetos de
uno-a-muchos de forma tal que cuando un objeto
cambia de estado, todos sus dependientes son
notificados y actualizados acordemente.
•Alias: Dependents, Publish-Suscribe
•Aplicabilidad: Usaremos este patrón cuando:
• Cuando una abstracción tiene dos aspectos, uno
independiente del otro.
• Cuando un cambio a un objeto requiere cambios en
otros, y no sabemos cuántos objetos necesitan ser
cambiados.
• Cuando un objeto debería notificar a otros objetos sin
realizar suposiciones de quiénes son esos objetos (evitar
el acoplamiento)
Patrón Observer
Patrón Observer
•Estructura de colaboración
Patrón Observer -
Participantes
• Subject: conoce a sus observadores. Cualquier
número de objetos Observer puede observar un
subject. Provee una interfaz para vincular y
desvincular objetos Observer.
• Observer: define una interfaz de actualización para
objetos, que debe ser notificada de los cambios en
el subject.
• ConcreteSubject: Almacena el estado de interés de
objetos ConcreteObserver. Notifica a sus
observadores cuando el estado cambia.
• ConcreteObserver: mantiene una referencia a un
objeto ConcreteSubject. Almacena estado que debe
mantenerse consistente con el subject. Implementa
la interfaz Observer.
Actuar en función del estado
Muchas veces diferentes alternativas del flujo
procesamiento del objeto se deciden en función de su
estado interno.
Esto genera a veces muchos condicionales en donde
se examina el estado y se determina la acción a
realizar. Ante los cambios las modificaciones son
bastante tediosas.
Podemos encapsular el estado del objeto en otro
objeto que determine la secuencia de acciones a
realizar en ese estado en particular.
El objeto delega todos los pedidos relacionados con el
estado al objeto mismo que representa el estado
actual, el cual resuelve el pedido finalmente.
Patrón State

•Intención: Permite a un objeto alterar su


comportamiento cuando cambia su estado interno.
•Alias: Objects for States
•Aplicabilidad: Usaremos este patrón cuando:
• El comportamiento de un objeto depende de su
estado, y debe cambiar en tiempo de ejecución
según el estado.
• Las operaciones tienen muchas sentencias
condicionales dependientes del estado del objeto.
Patrón State

La clase Context delega pedidos dependientes del estado


al objeto State asociado (concreto)
Patrón State
•Participantes:
• Context: define una interfaz de interés para los
clientes. Mantiene una instancia de
ConcreteState que define el estado actual
• State: define una interfaz para encapsular el
comportamiento asociado con un estado
particular del Contexto.
• Subclases ConcreteState: cada subclase
implementa un comportamiento específico
asociado con un estado del objeto Context.

Leer el ejemplo correspondiente en el libro Applied Java


Patterns
Algoritmos, comportamiento y
variaciones
Así como es posible encapsular el estado de un
objeto en pos de la flexibilidad, también es posible
encapsular operaciones, de forma tal que estas
también puedan variar libremente.
Algoritmos diferentes pueden utilizarse en
situaciones diferentes y podemos perfeccionar el
algoritmo sin modificar las clases que lo utilizan.
La idea básica es alojar un algoritmo en un objeto con
una interfaz definida, de forma tal que al cambiar el
objeto cambia la implementación del algoritmo.
Esta es la situación que captura el patrón Strategy.
Patrón Strategy
•Intención: Define una familia de algoritmos,
encapsula cada uno convirtiéndolos en
intercambiables. Permite variar un algoritmo
independientemente de los clientes que lo utilizan.
•Alias: Policy
•Aplicabilidad: Usaremos este patrón cuando:
• Muchas clases relacionadas difieren únicamente en el
comportamiento.
• Necesitamos diferentes variantes de un algoritmo.
• Un algoritmo utiliza datos que el cliente no debe
conocer.
• Una clase define múltiples comportamientos, y éstos
figuran como sentencias condicionales múltiples en sus
operaciones.
Patrón Strategy - Estructura
Patrón Strategy

•Participantes:
• Strategy: declara una interfaz común a todos los
algoritmos soportados.
• ConcreteStrategy: implementa un algoritmo
utilizando la interfaz Strategy.
• Context: está configurado con un objeto de tipo
ConcreteStrategy. Mantiene una referencia a un
objeto Strategy. Puede definir, si es necesario,
una interfaz para que Strategy acceda a sus
datos.
Algoritmos en términos
abstractos
Es posible que se conozca la estructura general de un
algoritmo, pero que ciertos detalles deban resolverse
en situaciones específicas.
Podemos implementar el algoritmo en función de
ciertas primitivas abstractas, las cuales serán
redefinidas por los herederos correspondientes. Esta
operación se denomina template method.
Las subclases proveen de esta forma el
comportamiento concreto de la operación antes
especificada, implementando los pasos que son
abstractos en el template method.
Patrón Template Method
•Intención: Define el esqueleto de un algoritmo en
una operación, postergando la implementación de
algunos pasos a las subclases. Las subclases
redefinen ciertos pasos del algoritmo sin cambiar su
estructura general.
•Aplicabilidad: Usaremos este patrón cuando:
• Es conveniente implementar la parte invariante
de un algoritmo sólo una vez y dejar que las
subclases implementen el comportamiento
variante.
• El comportamiento común de varias subclases
puede ser factorizado y ubicado en una clase
común para evitar la duplicación de código.
• Para controlar las extensiones por subclases. La
extensión se permite en ciertos puntos de un
algoritmo.
Patrón Template Method

Participantes
AbstractClass: define
operaciones primitivas
abstractas.
ConcreteClass: implementa
las operaciones primitivas
específicas.
Repartiendo tareas..
Supongamos que tenemos una estructura de objetos
medianamente compleja, por ejemplo un árbol, en la
cual debemos realizar una operación que requiere
operar con cada objeto de la estructura.
Repartiendo tareas..
Replicar estas operaciones por cada tipo de nodo
que podemos encontrar puede ser tedioso, y el
resultado global difícil de entender, mantener y
modificar.
En lugar de incluir en cada clase operaciones que
permiten realizar cálculos en cada instancia de esas
clases, podemos encapsular una única operación en
un objeto que será pasado a cada objeto de la
estructura.
Este objeto se denomina visitador (visitor) pues
“visita” todos los objetos, realizando la operación
indicada. Cada objeto visitado acepta esta visita y se
ofrece como participante del cálculo general.
Patrón Visitor
•Intención: Representa una operación a realizarse
sobre los elementos de una estructura. Permite
definir una nueva operación sin cambiar la clases de
elementos sobre los cuales opera.
•Aplicabilidad: Usaremos este patrón cuando:
• Un objeto estructura contiene muchas clases de
objetos con diferentes interfaces y deseamos realizar
operaciones sobre esos objetos.
• Muchas operaciones distintas y no relacionadas
necesitan realizarse sobre objetos en una estructura, y
no deseamos “contaminar” las clases con estas
operaciones.
• Las clases definiendo una estructura de objetos
raramente cambia, pero usualmente deseamos definir
nuevas operaciones sobre la estructura.
Patron Visitor -
Colaboraciones

El cliente debe crear un concreteVisitor y


entonces recorrer la estructura visitando
cada elemento
Cuando visita un elemento llama a la
operación visitor que corresponde a su
clase.
El elemento es un parámetro para esta
operación para permitir acceder a su
estado
Patrón Visitor

Colaboración entre una estructura, un visitador y dos


elementos
Patrón Visitor
• Visitor: declara una operación Visit para cada clase
de ConcreteElement en la estructura.
• ConcreteVisitor: implementa cada operación
declarada por Visitor.
• Element: define una operación Accept que toma un
Visitor como argumento.
• ConcreteElement: implementa una operación
Accept que toma un Visitor como argumento.
• ObjectStructure: puede enumerar sus elementos,
puede proveer una interfaz que permita a los
Visitors visitar sus elementos.

También podría gustarte