Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Módulo 2 - Lectura 3

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 37

Patrones de diseño de comportamiento

Introducción

El patrón Observer

El patrón State

El patrón Strategy

Referencias
Lección 1 de 5

Introducción

El diseñador de un sistema orientado a objetos se enfrenta a menudo al


problema del descubrimiento de objetos. Esto puede realizarse a partir de los
dos aspectos siguientes: la estructuración de los datos y la distribución de los
procesamientos y los algoritmos.

Los patrones de estructuración aportan soluciones a los problemas de


estructuración de datos y de objetos.

El objetivo de los patrones de comportamiento consiste en proporcionar


soluciones para distribuir el procesamiento y los algoritmos entre los objetos.

Estos patrones organizan los objetos, así como sus interacciones,


especificando los flujos de control y de procesamiento en el seno de un
sistema de objetos.

En esta lectura, vamos a analizar los patrones State, Strategy y Observer.

C O NT I NU A R
Lección 2 de 5

El patrón Observer

El objetivo del patrón Observer es construir una dependencia entre un sujeto


y los observadores, de modo que cada modificación del sujeto sea notificada
a los observadores para que puedan actualizar su estado.

En nuestro sistema de venta online de vehículos, queremos actualizar la


visualización de un catálogo al instante. Cada vez que la información relativa
a un vehículo se modifica, queremos actualizar la observación de dicha
información. Además, puede haber varias visualizaciones simultáneas.

Para solucionar esto, el patrón Observer establece un enlace entre cada


vehículo y sus vistas, para que el vehículo pueda indicarles que se actualicen
cuando su estado interno haya sido modificado.

Figura 1: El patrón Observer aplicado a la visualización


de vehículos
Fuente: Debrauwer, 2013, p. 214.

En el diagrama de la figura 1, en el cual se aplica el patrón Observer a nuestro


ejemplo de venta online de vehículos, podemos observar las siguientes
cuatro clases:

1 Sujeto es la clase abstracta que incluye todo objeto que notifica a


los demás objetos de las modificaciones en su estado interno.
2 Vehículo es la subclase concreta de Sujeto que describe a los
vehículos. Gestiona dos atributos: descripción y precio.

3 Observador es la interfaz de todo objeto que necesite recibir las


notificaciones de cambio de estado provenientes de los objetos a
los que se ha inscrito previamente.

4 VistaVehículo es la subclase concreta correspondiente a la


implementación de Observador cuyas instancias muestran la
información de un vehículo. (Debrauwer, 2013, p. 214).

¿Cómo funciona este esquema?


Siguiendo lo establecido por Debrauwer (2013):

El funcionamiento es el siguiente: cada nueva vista se inscribe


como observador de su vehículo mediante el método agrega.
Cada vez que la descripción o el precio se actualizan, se invoca
el método notifica. Este solicita a todos los observadores que se
actualicen, invocando a su método actualiza. En la clase
VistaVehículo, este último método se llama redibuja.

La solución implementada mediante el patrón Observer es


genérica. En efecto, todo el mecanismo de observación está
implementado en la clase Sujeto y en la interfaz Observador, que
puede tener otras subclases distintas de Vehículo y
VistaVehículo. (p. 215).

Figura 2: Estructura del patrón Observer

Fuente: Debrauwer, 2013, p. 216.


Al aplicar el patrón Observer de forma genérica, nos encontramos con los
siguientes aspectos:

Sujeto es la clase abstracta que incluye la asociación con los


observadores, así como los métodos para agregar o suprimir
observadores.

Observador es la interfaz que es necesario implementar para


recibir las notificaciones (método actualiza).

SujetoConcreto [en nuestro ejemplo Vehículo] es una clase


correspondiente a la implementación de un sujeto. Un sujeto envía
una notificación cuando su estado se ha modificado.

ObservadorConcreto (en nuestro ejemplo VistaVehículo) es una


clase de implementación de un observador. Mantiene una
referencia hacia el sujeto e implementa el método actualiza.
Solicita a su sujeto información que forma parte de su estado
durante las actualizaciones invocando al método getEstado.
(Debrauwer, 2013, pp. 216-217).

¿En qué casos me conviene aplicar el


patrón Observer?
Es conveniente aplicar el patrón Observer cuando una modificación en el
estado de un objeto genera modificaciones en otros objetos que se
determinan dinámicamente, o cuando un objeto quiere avisar a otros objetos
sin tener que conocer su tipo, es decir, sin estar fuertemente acoplado a
ellos. También puede aplicarse este patrón cuando no se desea fusionar dos
objetos en uno solo.

Ejemplo en Java
Veamos cómo poner en funcionamiento en Java el ejemplo de la figura 1,
según el libro Patrones de diseño en Java de Laurent Debrauwer (2013):

El código fuente de la clase Sujeto aparece a continuación. Los


observadores se gestionan mediante una lista.

import java.util.*;
public abstract class Sujeto
{
protected List<Observador> observadores =
new ArrayList<Observador>();
public void agrega(Observador observador)
{
observadores.add(observador);
}
public void suprime(Observador observador)
{
observadores.remove(observador);
}
public void notifica()
{
for (Observador observador: observadores)
observador.actualiza();
}
}
El código fuente de la interfaz Observador es muy simple puesto
que solo contiene la firma del método actualiza.

public interface Observador


{
void actualiza();
}

El código fuente de la clase Vehículo aparece a continuación.


Contiene dos atributos y los accesos de lectura y escritura para
ambos atributos. Los dos accesos de escritura invocan al
método notifica.

public class Vehiculo extends Sujeto


{
protected String descripcion;
protected Double precio;
public String getDescripcion()
{
return descripcion;
}

public void setDescripcion(String descripcion)


{
this.descripcion = descripcion;
this.notifica();
}

public Double getPrecio()


{
return precio;
}

public void setPrecio(Double precio)


{
this.precio = precio;
this.notifica();
}
}

La clase VistaVehículo gestiona un texto que contiene la


descripción y el precio del vehículo asociado (el sujeto). Este
texto se actualiza tras cada notificación en el cuerpo del método
actualiza. El método redibuja imprime este texto por pantalla.

public class VistaVehiculo implements Observador


{
protected Vehiculo vehiculo;
protected String texto = "";

public VistaVehiculo(Vehiculo vehiculo)


{
this.vehiculo = vehiculo;
vehiculo.agrega(this);
actualizaTexto();
}

protected void actualizaTexto()


{
texto = "Descripcion " + vehiculo.descripcion +
" Precio: " + vehiculo.precio;
}

public void actualiza()


{
actualizaTexto();
this.redibuja();
}

public void redibuja()


{
System.out.println(texto);
}
}

Por último, la clase Usuario incluye el programa principal. Este


programa crea un vehículo y, a continuación, una vista a la que le
pide la visualización. A continuación, se modifica el precio y la
vista se refresca. A continuación, se crea una segunda vista que
se asocia al mismo vehículo. El precio se modifica de nuevo y
ambas vistas se refrescan.

public class Usuario


{
public static void main(String[] args)
{
Vehiculo vehiculo = new Vehiculo();
vehiculo.setDescripcion("Vehiculo economico");
vehiculo.setPrecio(5000.0);
VistaVehiculo vistaVehiculo = new
VistaVehiculo(vehiculo);
vistaVehiculo.redibuja();
vehiculo.setPrecio(4500.0);
VistaVehiculo vistaVehiculo2 = new
VistaVehiculo(vehiculo);
vehiculo.setPrecio(5500.0);
}
}
El resultado de la ejecución de este programa es el siguiente.

Descripcion vehiculo economico precio: 5000.0

Descripcion vehiculo economico precio: 4500.0

Descripcion vehiculo economico precio: 5500.0

Descripcion vehiculo economico precio: 5500.0 (pp. 217-218).

C O NT I NU A R
Lección 3 de 5

El patrón State

El patrón State le permite a un objeto adaptar su comportamiento en función


de su estado interno.

En nuestro sistema de venta online de vehículos, vamos a concentrarnos en


los pedidos de productos en nuestro sitio de venta online.

Es sencillo imaginar que tendremos diferentes estados o etapas al momento


de la compra. El estado EnCurso se corresponde con el estado en el que el
pedido está en curso de creación, es decir, cuando el cliente agrega los
productos. El estado Validado es el estado en el que el pedido ha sido
validado y aprobado por el cliente. Por último, el estado Entregado es el
estado en el que los productos han sido entregados.

Figura 3: Estructura del patrón State


Fuente: Debrauwer, 2012, p. 224.

Analicemos la estructura del patrón State para comprender mejor su


funcionamiento:

Los participantes del patrón son los siguientes

MáquinaEstados [en nuestro ejemplo Pedido] es una clase concreta que describe los objetos
que son máquinas de estados, es decir, que poseen un conjunto de estados que pueden ser
descritos mediante un diagrama de estados y transiciones. Esta clase mantiene una
referencia hacia una instancia de una subclase de Estado que define el estado en curso.

Estado (en nuestro ejemplo EstadoPedido) es una clase abstracta que incluye los métodos
ligados al estado y que gestionan la asociación con la máquina de estados..

EstadoConcretoA y EstadoConcretoB (en nuestro ejemplo PedidoEnCurso, PedidoValidado y


PedidoEntregado) son subclases concretas que implementan el comportamiento de los
métodos relativos a cada estado.
La máquina de estados delega las llamadas a los métodos
dependiendo del estado en curso hacia un objeto de estado,
además puede transmitir al objeto de estado una referencia
hacia sí misma si es necesario. Esta referencia puede pasarse
durante la delegación o en la propia inicialización del objeto de
estado. (Debrauwer, 2013, pp. 224-225).

¿En qué casos puedo aplicar el patrón


State?
Cuando el comportamiento de un objeto depende de su estado o cuando la
aplicación de esta dependencia del estado mediante instrucciones
condicionales se vuelve muy compleja.

Ejemplo en Java
A continuación, presentamos el ejemplo anterior escrito en Java según el
código presentado en el libro Patrones de diseño en Java de Laurent
Debrauwer (2013):

La clase Pedido se describe a continuación. Los métodos


agregaProducto, suprimeProducto y borra dependen del estado.
Por consiguiente, su implementación consiste en llamar al
método correspondiente de la instancia referenciada mediante
estadoPedido.

El constructor de la clase inicializa el atributo estadoPedido con


una instancia de la clase PedidoEnCurso. El método
estadoSiguiente pasa al estado siguiente asociando una nueva
instancia al atributo estadoPedido.

import java.util.*;
public class Pedido
{
protected List<Producto> productos = new
ArrayList<Producto>();
protected EstadoPedido estadoPedido;
public Pedido()
{
estadoPedido = new PedidoEnCurso(this);

public void agregaProducto(Producto producto)


{
estadoPedido.agregaProducto(producto);
}

public void suprimeProducto(Producto producto)


{
estadoPedido.suprimeProducto(producto);
}
public void borra()
{
estadoPedido.borra();
}
public void estadoSiguiente()
{
estadoPedido = estadoPedido.estadoSiguiente();
}

public List<Producto> getProductos()


{
return productos;
}

public void visualiza()


{
System.out.println("Contenido del pedido");
for (Producto producto: productos)
producto.visualiza();
System.out.println();
}
}

La clase abstracta EstadoPedido gestiona la relación con una


instancia de Pedido, así como la firma de los métodos de Pedido
que dependen del estado.

public abstract class EstadoPedido


{
protected Pedido pedido;

public EstadoPedido(Pedido pedido)


{
this.pedido = pedido;
}

public abstract void agregaProducto(Producto producto);


public abstract void borra();
public abstract void suprimeProducto(Producto producto);
public abstract EstadoPedido estadoSiguiente();
}
La subclase PedidoEnCurso implementa los métodos de
EstadoPedido para el estado EnCurso.

public class PedidoEnCurso extends EstadoPedido


{
public PedidoEnCurso(Pedido pedido)
{
super(pedido);
}
public void agregaProducto(Producto producto)
{
pedido.getProductos().add(producto);
}
public void borra()
{
pedido.getProductos().clear();
}

public void suprimeProducto(Producto producto)


{
pedido.getProductos().remove(producto);
}

public EstadoPedido estadoSiguiente()


{
return new PedidoValidado(pedido);
}
}

La subclase PedidoValidado implementa los métodos de


EstadoPedido para el estado Validado.
public class PedidoValidado extends EstadoPedido
{
public PedidoValidado(Pedido pedido)
{
super(pedido);
}

public void agregaProducto(Producto producto) { }

public void borra()


{
pedido.getProductos().clear();
}

public void suprimeProducto(Producto producto) { }

public EstadoPedido estadoSiguiente()


{
return new PedidoEntregado(pedido);
}
}

La subclase PedidoEntregado implementa los métodos de


EstadoPedido para el estado Entregado. En este estado, el
cuerpo de los métodos está vacío.

public class PedidoEntregado extends EstadoPedido

{
public PedidoEntregado(Pedido pedido)
{
super(pedido);
}
public void agregaProducto(Producto producto) { }

public void borra() { }

public void suprimeProducto(Producto producto) { }

public EstadoPedido estadoSiguiente()


{
return this;
}
}

La clase Producto referenciada por la clase Pedido se escribe en


Java tal y como se muestra a continuación.

public class Producto


{
protected String nombre;

public Producto(String nombre)


{
this.nombre = nombre;
}

public void visualiza()


{
System.out.println("Producto: " + nombre);
}
}

El programa principal se incluye en la clase Usuario. El programa


crea dos pedidos. Borra el primero en el estado Validado, lo que
conduce a una puesta a cero. Respecto al segundo, el programa
lo borra una vez se encuentra en el estado Entregado, lo cual no
tiene efecto alguno.

public class Usuario


{
public static void main(String[] args)
{
Pedido pedido = new Pedido();
pedido.agregaProducto(new Producto("vehiculo 1"));
pedido.agregaProducto(new Producto("accesorio 2"));
pedido.visualiza();
pedido.estadoSiguiente();
pedido.agregaProducto(new Producto("accesorio 3"));
pedido.borra();
pedido.visualiza();

Pedido pedido2 = new Pedido();


pedido2.agregaProducto(new Producto("vehiculo 11"));
pedido2.agregaProducto(new Producto("accesorio 21"));

pedido2.visualiza();
pedido2.estadoSiguiente();
pedido2.visualiza();
pedido2.estadoSiguiente();
pedido2.borra();
pedido2.visualiza();
}
}

La ejecución del programa proporciona el siguiente resultado.

Contenido del pedido


 Producto: vehiculo 1

 Producto: accesorio 2

 Contenido del pedido

 Contenido del pedido

 Producto: vehiculo 11

 Producto: accesorio 21

 Contenido del pedido

 Producto: vehiculo 11

 Producto: accesorio 21

 Contenido del pedido

 Producto: vehiculo 11

 Producto: accesorio 21
C O NT I NU A R
Lección 4 de 5

El patrón Strategy

De acuerdo con lo establecido por Debrauwer (2013):

El patrón Strategy tiene como objetivo adaptar el


comportamiento y los algoritmos de un objeto en función de una
necesidad sin cambiar las interacciones de este objeto con los
clientes.

Esta necesidad puede ponerse de relieve en base a aspectos


tales como la presentación, la eficacia en tiempo de ejecución o
en memoria, la elección de algoritmos, la representación interna,
etc. Aunque evidentemente no se trata de una necesidad
funcional de cara a los clientes del objeto, pues las interacciones
entre el objeto y sus clientes deben permanecer inmutables.

En el sistema de venta online de vehículos, la clase


VistaCatálogo dibuja la lista de vehículos destinados a la venta.
Se utiliza un algoritmo de diseño gráfico para calcular la
representación gráfica en función del navegador. Existen dos
versiones de este algoritmo:
Una primera versión que solo muestra un vehículo por línea (un
vehículo ocupa todo el ancho disponible) y que muestra toda la
información posible, así como cuatro fotografías.

Una segunda versión que muestra tres vehículos por línea pero
que muestra menos información y una única fotografía.

La interfaz de la clase VistaCatálogo no depende de la elección


del algoritmo de representación gráfica. Esta elección no tiene
impacto alguno en la relación de una vista de catálogo con sus
clientes. Solo se modifica la representación.

Una primera solución consiste en transformar la clase


VistaCatálogo en una interfaz o en una clase abstracta y en
incluir dos subclases de implementación diferentes según la
elección del algoritmo. Esto presenta el inconveniente de
complicar de manera inútil la jerarquía de las vistas del catálogo.

Otra posibilidad consiste en implementar ambos algoritmos en


la clase VistaCatálogo y en apoyarse en instrucciones
condicionales para realizar la elección. No obstante, esto
consiste en desarrollar una clase relativamente pesada donde el
código de los métodos es difícil de comprender.

El patrón Strategy proporciona otra solución incluyendo una


clase por algoritmo. El conjunto de las clases así creadas posee
una interfaz común que se utiliza para dialogar con la clase
VistaCatálogo. (p. 231).

En el diagrama de la figura 4, podemos observar las dos clases de


algoritmos: DibujaUnVehículoPorLínea y DibujaTresVehículosPorLínea, que
aplican la interfaz DibujaCatálogo.

Figura 4: Aplicación del patrón Strategy para dibujar un


catálogo de vehículos 
Fuente: Debrauwer, 2013, p. 233.

Figura 5: Estructura genérica del patrón Strategy


Fuente: Debrauwer, 2013, p. 234.

Si observamos la estructura genérica del patrón Strategy que se detalla en la


figura 5, encontraremos los siguientes componentes:

Estrategia (en nuestro ejemplo DibujaCatálogo) es la interfaz


común a todos los algoritmos. Esta interfaz se utiliza en Entidad
para invocar al algoritmo.

EstrategiaConcretaA y EstrategiaConcretaB (en nuestro ejemplo


DibujaUnVehículoPorLínea y DibujaTresVehículosPorLínea) son las
subclases concretas que implementan los distintos algoritmos.

Entidad es la clase que utiliza uno de los algoritmos de las clases


que implementan la Estrategia. Por consiguiente, posee una
referencia hacia una de estas clases. Por último, si fuera necesario,
puede exponer sus datos internos a las clases de implementación.
La entidad y las instancias de las clases de implementación de
la Estrategia interactúan para implementar los algoritmos. En el
caso más sencillo, los datos que necesita el algoritmo se pasan
como parámetro. Si fuera necesario, la clase Entidad
implementaría los métodos necesarios para dar acceso a sus
datos internos.

El cliente inicializa la entidad con una instancia de la clase de


implementación de Estrategia. Él mismo selecciona esta clase y,
por lo general, no la modifica a continuación. La entidad puede
modificar a continuación esta elección. 

La entidad redirige las peticiones provenientes de sus clientes


hacia la instancia referenciada por su atributo estrategia.
(Debrauwer, 2013, pp. 234-235).

¿Cuándo me conviene aplicar este patrón?


El patrón Strategy nos resultará de utilidad en los siguientes casos:

El comportamiento de una clase puede estar implementado


mediante distintos algoritmos siendo alguno de ellos más eficaz en
términos de ejecución o de consumo de memoria o incluso
contener mecanismos de decisión.
La implementación de la elección del algoritmo mediante
instrucciones condicionales se vuelve demasiado compleja.

Un sistema posee numerosas clases idénticas salvo una parte


correspondiente a su comportamiento.

En el último caso, el patrón Strategy permite reagrupar estas


clases en una sola, lo que simplifica la interfaz para los clientes.
(Debrauwer, 2012, p. 235).

Ejemplo en Java extraído del libro


Patrones de diseño en java de Laurent
Debrauwer (2013)
Siguiendo a Debrauwer (2013):

Nuestro ejemplo escrito en Java está basado en la visualización


del catálogo de vehículos, simulado aquí simplemente mediante
salidas por pantalla.

La interfaz DibujaCatálogo incluye el método dibuja que recibe


como parámetro unalista de instancias de VistaVehículo.
import java.util.*;
public interface DibujaCatalogo
{
void dibuja(List<VistaVehiculo> contenido);
}

La clase DibujaUnVehiculoPorLinea implementa el método


dibuja mostrando un vehículo por cada línea (imprime un salto
de línea tras mostrar un vehículo).

import java.util.*;
public class DibujaUnVehiculoPorLinea implements DibujaCatalogo
{
public void dibuja(List<VistaVehiculo> contenido)
{
System.out.println(
"Dibuja los vehiculos mostrando un vehiculo por linea");
for (VistaVehiculo vistaVehiculo: contenido)
{
vistaVehiculo.dibuja();
System.out.println();
}
System.out.println();
}
}

La clase DibujaTresVehículosPorLínea implementa el método


dibuja mostrando tres vehículos por línea (imprime un salto de
línea tras mostrar tres vehículos).

import java.util.*;
public class DibujaTresVehiculosPorLinea implements DibujaCatalogo
{
public void dibuja(List<VistaVehiculo> contenido)
{
int contador;
System.out.println(
"Dibuja los vehiculos mostrando tres vehiculos por linea");
contador = 0;
for (VistaVehiculo vistaVehiculo: contenido)
{
vistaVehiculo.dibuja();
contador++;
if (contador == 3)
{
System.out.println();
contador = 0;
}
else
System.out.println(" ");
}
if (contador != 0)
System.out.println();
System.out.println();
}
}

La clase VistaVehículo que dibuja un vehículo tiene el código que


se muestra a continuación.

Conviene observar que el método dibuja de VistaVehículo, en el


caso de esta simulación, es idéntico para la visualización en una
línea que en tres líneas, lo cual no suele ser el caso en la realidad
de una interfaz gráfica.

public class VistaVehiculo


{
protected String descripcion;
public VistaVehiculo(String descripcion)
{
this.descripcion = descripcion;
}
public void dibuja()
{
System.out.print(descripcion);
}
}

La clase VistaCatálogo posee un constructor que recibe como


parámetro una instancia de una de las clases de
implementación de DibujaCatálogo, instancia que se memoriza
en el atributo dibuja. Este constructor inicializa a su vez el
contenido que normalmente debería leerse desde una base de
datos.

El método dibuja redirige la llamada hacia la instancia


memorizada en dibujo. Pasa como parámetro una referencia
hacia el contenido del catálogo.
import java.util.*;
public class VistaCatalogo
{

protected List<VistaVehiculo> contenido =


new ArrayList<VistaVehiculo>();
protected DibujaCatalogo dibujo;
public VistaCatalogo(DibujaCatalogo dibujo)
{
contenido.add(new VistaVehiculo("vehiculo economico"));
contenido.add(new VistaVehiculo("vehiculo amplio"));
contenido.add(new VistaVehiculo("vehiculo rapido"));
contenido.add(new VistaVehiculo("vehiculo confortable"));
contenido.add(new VistaVehiculo("vehiculo deportivo"));
this.dibujo = dibujo;
}
public void dibuja()
{
dibujo.dibuja(contenido);
}
}

Por último, el programa principal está implementado mediante


la clase Usuario. Este crea dos instancias de VistaCatálogo, la
primera está configurada para realizar un dibujo en tres líneas.
La segunda instancia está configurada para realizar un dibujo en
una línea. Tras haberlas creado, el programa principal invoca al
método dibuja de sus instancias.

public class Usuario


{
public static void main(String[] args)
{
VistaCatalogo vistaCatalogo1 = new VistaCatalogo(new
DibujaTresVehiculosPorLinea());
vistaCatalogo1.dibuja();
VistaCatalogo vistaCatalogo2 = new VistaCatalogo(new
DibujaUnVehiculoPorLinea());
vistaCatalogo2.dibuja();
}
}

La ejecución de este programa produce el resultado siguiente,


que muestra claramente cómo el comportamiento del método
dibuja está configurado por la instancia que se pasa como
parámetro al constructor.

Dibuja los vehiculos mostrando tres vehiculos por linea

vehiculo economico vehiculo amplio vehiculo rapido

vehiculo confortable vehiculo deportivo

Dibuja los vehiculos mostrando un vehiculo por linea

vehiculo economico

vehiculo amplio

vehiculo rapido

vehiculo confortable

vehiculo deportivo (pp. 236-238).


C O NT I NU A R
Lección 5 de 5

Referencias

Debrauwer, L. (2013). Patrones de diseño en Java. Los 23 modelos de diseño:


descripción y soluciones ilustradas en UML 2 y Java. Barcelona, España:
Editorial Eni.

C O NT I NU A R

También podría gustarte