Sei sulla pagina 1di 25

Universidad del Cauca

Facultad de Ingeniería Electrónica y Telecomunicaciones

UNIVERSIDAD DEL CAUCA


FACULTAD DE INGENIERIA ELECTRÓNICA Y TELECOMUNICACIONES
PROGRAMA DE INGENIERÍA ELECTRÓNICA Y TELECOMUNICACIONES

PATRONES DE DISEÑO
OBJETIVO
• Aplicar el polimorfismo a la implementación de algunos patrones de diseño.

1. INTRODUCCIÓN
Según Grady Booch, “Una arquitectura orientada a objetos bien estructurada está llena de patrones.
La calidad de un sistema orientado a objetos se mide por la atención que los diseñadores han prestado
a las colaboraciones entre sus objetos. Los patrones conducen a arquitecturas más pequeñas, más
simples y más comprensibles”.
Diseñar software orientado a objetos que cumpla con el paradigma no es tan simple, pero diseñar
software orientado a objetos reutilizable es más difícil todavía. Diseños generales y flexibles son muy
difíciles de conseguir la primera vez. En este punto, se podría formular la pregunta, ¿Qué
conocimientos posee un programador experto que desconoce uno que se considera novato? La
respuesta ante este interrogante está en la capacidad de reutilizar soluciones que funcionaron en el
pasado, es decir, aprovechar la experiencia y la utilización de patrones. La clave para la reutilización de
software está en anticiparse a los nuevos requisitos y cambios, de modo que los sistemas evolucionen
de forma adecuada.
Los síntomas de una aplicación informática mal construida son la rigidez, la fragilidad y la
inmovilidad. La rigidez hace referencia a la tendencia del software a ser difícil de cambiar. La
fragilidad está relacionada con la rigidez, y se nota cuando un cambio en alguna parte del software,
ocasiona cambios en otros sectores. Finalmente, la inmovilidad es la inhabilidad para reutilizar el
software.

1.1. ¿Qué son los patrones de diseño?

Los patrones describen un problema recurrente y una solución en un contexto dado (Alexander, Sara,
Max, Fiksdahl-King, & Angel, 1977). En esta definición, el término contexto se refiere al conjunto de
condiciones o situaciones en las que un patrón dado es aplicable.

Los patrones de diseño proporcionan soluciones elegantes y reutilizables a los problemas más
frecuentes de desarrollo de software en un contexto particular. Esto significa que un patrón que está

1
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

destinado a proporcionar la mejor solución a un problema en un contexto particular puede no producir


una solución eficaz para el mismo problema en un contexto diferente. A veces, la solución propuesta
por el patrón de diseño puede incluso no ser aplicable en un contexto diferente (Kuchana, 2004).

Existen varios tipos de patrones. Se tiene los patrones de código, que están a nivel de lenguaje de
programación. Se tiene patrones de arquitectura, que son aquellos que expresan un esquema
organizativo estructural fundamental para sistemas de software. Finalmente, están los patrones de
diseño, que son descripciones de clases cuyas instancias colaboran entre sí, que deben ser adaptados
para resolver problemas de diseño general en un particular contexto. En este libro presta atención
particular a los patrones de diseño.

Los patrones de diseño son descripciones de clases y objetos relacionados que están adaptados para
resolver un problema de diseño general en un contexto determinado. Un patrón de diseño identifica
Clases, Roles, Colaboraciones y la distribución de responsabilidades. Cada patrón nombra, explica y
evalúa un diseño recurrente en sistemas orientados a objetos.

En general, un patrón tiene cuatro partes esenciales:


1. El nombre del patrón, es un identificador que describe en una palabra o dos el problema de
diseño, sus soluciones y las consecuencias.
2. El problema, que describe el problema y su contexto.
3. La solución que describe los elementos que componen el diseño, sus relaciones,
responsabilidades y colaboraciones.
4. Las consecuencias, son los resultados, ventajas y desventajas de aplicar el patrón.
Los patrones de diseño por lo tanto son una herramienta conceptual que facilita la reutilización del
conocimiento en un diseño. Un patrón es considerado un fragmento identificable de conocimiento
instructivo, que captura la estructura esencial e interna de una familia de soluciones con probado éxito
sobre un problema recurrente dentro de un cierto contexto y fuerzas del software. En otras palabras, un
patrón de diseño facilita reutilizar buenas soluciones.

1.2. Tipos de Patrones

Existen diferentes tipos de patrones en el desarrollo de software, dependiendo del nivel de abstracción
(y granularidad), del contexto particular en el cual se aplican o de la etapa en proceso de desarrollo.
Dentro del contexto del diseño de software, los tipos más representativos son:

 Patrones de arquitectura: son los esquemas primarios en la organización de un sistema


software. Especifican una serie de subsistemas y sus responsabilidades respectivas incluyendo
tácticas y estrategias arquitectónicas para organizar las relaciones existentes entre ellos. Por
ejemplo el estilo cliente-servidor desde un punto de vista de componentes y conectores, permite
esquematizar como ocurre la interacción entre componentes clientes y componentes servidores
a través de un conector que facilita la interacción petición-respuesta.
 Patrones de diseño: son patrones de un nivel de abstracción alto pero a un nivel de granularidad
más fino. Están por lo tanto más próximos a lo que serían las piezas concretas de la solución

2
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

(código). Su uso no necesariamente se refleja en la estructura global del sistema. Por ejemplo el
esquema observador-observado, donde se busca especificar el comportamiento de la
comunicación entre dos entidades de tal forma que queden desacopladas totalmente una de otra.
 Idioms: son patrones directamente involucrados en la codificación, por tanto son más simples y
específicos al lenguaje, normalmente vienen incluidos como plantillas en los ambientes de
desarrollo. Por ejemplo, como iniciar un hilo (Thread) en Java.

1.3. El catálogo de los patrones de diseño

A nivel de patrones de diseño existe una clasificación: creacionales, estructurales y de comportamiento.


Los patrones de diseño creacionales, abstraen el proceso de creación de objetos, ayudan a crear
sistemas independientes de cómo los objetos son creados, compuestos y representados. Dan flexibilidad
en qué se crea, quién lo crea, cómo se crea y cuándo se crea. Los patrones de diseño estructurales
determinan cómo las clases y objetos se combinan para formar estructuras más complejas. Finalmente,
los patrones de diseño de comportamiento, están relacionados con la asignación de responsabilidades
entre clases, enfatizan la colaboración entre objetos, caracterizan un flujo de control más o menos
complejo que será transparente al que utilice el patrón.
A continuación se lista el catálogo de patrones de diseño.

Patrones de Creación Patrones Estructurales Patrones de


comportamiento
 Abstract Factory  Decorador  Chain of
 Builder  Adapter Responsability
 Factory Method  Bridge  Command
 Prototype  Composite  Iterator
 Singleton  Facade  Interpreter
 Flyweight  Memento
 Proxy  Mediador
 Virtual Proxy  Observer
 Counting Proxy  State
 Aggregate Enforcer  Strategy
 Object Cache  Null Object
 Iterator  Template Method
 Flyweight  Object Authenticator
 Visitor
 Common Attribute
Registry

3
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

1.4. Patrón Método de Fábrica

Resumen
El patrón Factory Method recomienda encapsular la funcionalidad requerida, para seleccionar
e instanciar la clase apropiada de una jerarquía, dentro de un método denominado factory
method. De esta manera, un factory method puede ser definido como un método de una
clase que selecciona una clase apropiada de una jerarquía de clases basado en el contexto
de la aplicación y otros factores influyentes.

Descripción
Problema
En general, todas las subclases en una jerarquía de clases heredan los métodos
implementados de la clase padre. Una subclase puede sobrescribir la implementación de la
clase padre para ofrecer diferentes tipos de funcionalidad para el mismo método. Cuando un
objeto de una aplicación es consciente de la funcionalidad exacta que requiere, puede
directamente instanciar la clase de la jerarquía de clases que le ofrece la funcionalidad
requerida. Sin embargo, a veces, un objeto de una aplicación solo puede saber que necesita
acceder a una clase de la jerarquía de clases, pero no la conoce exactamente, es decir, no
sabe exactamente qué clase de entre el conjunto de subclases debe seleccionar. La
selección de una clase apropiada puede depender de factores tales como:
 El estado de la aplicación que se ejecuta.

 La configuración de la aplicación.

 La expansión de los requisitos o mejoras.

Solución
En tales casos, un objeto de la aplicación necesita implementar los criterios de selección de
la clase para instanciar una clase apropiada de la jerarquía para acceder a sus servicios. En
este caso, el patrón Factory Method recomienda encapsular la funcionalidad requerida, para
seleccionar e instanciar la clase apropiada, dentro de un método denominado factory
method. De esta manera, un factory method puede ser definido como un método de una
clase que:
 Selecciona una clase apropiada de una jerarquía de clases basado en el contexto de
la aplicación y otros factores influyentes.

4
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

 Instancia la clase seleccionada y la retorna como una instancia del tipo de la clase
padre.
La encapsulación de la implementación requerida para seleccionar e instanciar una clase
apropiada en un método separado tiene las siguientes ventajas:
 Los objetos de la aplicación pueden utilizar el método de fábrica para acceder a una
instancia de la clase apropiada. Se elimina la necesidad que un objeto de la
aplicación, tenga que negociar con la variación de los criterios de selección de la
clase.
 Además de los criterios de selección de la clase, el método de fábrica también
implementa un mecanismo especial requerido para instanciar la clase seleccionada.
Esto es aplicable si clases diferentes en la jerarquía necesitan ser instanciadas de
diferentes formas. El método de fábrica oculta esos detalles de los objetos de
aplicación y elimina la necesidad de tratar con esas complejidades.
 Debido a que el método de fábrica retorna la instancia de la clase seleccionada como
un objeto de tipo clase padre, un objeto de la aplicación no tiene que ser consciente
de la existencia de la clase en la jerarquía.

Ejemplo
Diseñemos la funcionalidad de registro de mensajes en una aplicación. En general, el
registro de mensajes es una de las tareas más comunes en las aplicaciones. Esto es algo útil
en la depuración y monitoreo de aplicaciones.
Debido a que el registro de mensajes puede ser requerido por diferentes clientes, podría ser
una buena idea mantener la funcionalidad en una clase común para que los objetos cliente
no tengan que repetir esos detalles.
En general, un mensaje entrante podría ser registrado en diferentes medios, en diferentes
formatos. Se puede definir una interfaz Logger (En español Registrador) utilizada por objetos
clientes que registran mensajes, y clases concretas que implementan esta interface. La clase
implementadora FileLogger almacena los mensajes entrantes en un archivo. La clase
implementadora ConsoleLogger visualiza los mensajes entrantes en pantalla. Este diseño se
puede visualizar en la Figura 1.

5
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Figura 1. Factory Method - Jerarquía de mensajes de la utilidad registro de mensajes

Aplicando el patrón Factory Method, la implementación necesaria para seleccionar e


instanciar un implementador Logger puede ser encapsulado en un método aparte getLogger
de una clase aparte LoggerFactory. El método getLogger verifica la propiedad del archivo
logger.properties para establecer si el registro a un archivo está activo y así decidir cual
implementación de Logger debe instanciar.
Con este diseño, los objetos cliente no requieren tratar con las complicaciones de la
selección e instanciación del implementador Logger adecuado. Los objetos cliente no
requieren conocer la existencia de diferentes implementadores de Logger y su funcionalidad
asociada.
Cuando un objeto cliente (LoggerTest) requiere registrar un mensaje debe:
 Invocar le método getLogger de la fábrica.

 Invocar el método log expuesto en la interface Logger sobre el objeto retornado.

El diseño completo se muestra en la Figura 2.

6
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Figura 2. Factory Method - Diseño completo

En flujo de mensajes se muestra en la Figura 3.

7
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Figura 3. Factory Method - Flujo de mensajes

Implementación en
Java
A continuación la implementación en Java.

package patrones.factorymethod.ejm1;
public interface Logger {
public void log(String msg);
}

package patrones.factorymethod.ejm1;
public class ConsoleLogger implements Logger {
public void log(String msg) {
System.out.println(msg);
}
}

package patrones.factorymethod.ejm1;
public class FileLogger implements Logger {
public void log(String msg) {
FileUtil futil = new FileUtil();
futil.writeToFile("log.txt", msg, true, true);
}
}

package patrones.factorymethod.ejm1;
import java.io.IOException;
import java.util.Properties;
public class LoggerFactory {
public boolean isFileLoggingEnabled() {
Properties p = new Properties();
try {

p.load(ClassLoader.getSystemResourceAsStream("logger.properties"));
String fileLoggingValue = p.getProperty("FileLogging");
if (fileLoggingValue.equalsIgnoreCase("ON") == true)
return true;
else
return false;
} catch (IOException e) {
return false;
}
}
public Logger getLogger() {
if (isFileLoggingEnabled()) {

8
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

return new FileLogger();


} else {
return new ConsoleLogger();
}
}
}

package patrones.factorymethod.ejm1;
public class LoggerTest {
public static void main(String[] args) {
LoggerFactory factory = new LoggerFactory();
Logger logger = factory.getLogger();
logger.log("A Message to Log");
}
}

Contenido del Archivo logger.properties (Grabarlo dentro de la carpeta src):

FileLogging=ON

La Figura 4 muestra los archivos fuente de este ejemplo en el entorno eclipse IDE:

Figura 4. Factory Method - archivos en el entorno eclipse


La salida del programa sería algo como:

El mensaje: A Message to Log, ha sido grabado con éxito en el archivo


log.txt

9
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Ejercicios
1. Adicione al proyecto anterior un nuevo logger DBLogger que registre mensajes en una
base de datos.

2. Cree una subclase de LoggerFactory y sobreescriba el método getLogger de tal


manera que tenga un criterio de selección diferente.

1.5. Patrón Adapter

Resumen
El patrón Adapter (Adaptador) se utiliza para transformar una interfaz en otra, de tal modo que una
clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda. Convierte la interfaz de
una clase en otra interfaz que el cliente espera.

Descripción
Problema
En general, los clientes de una clase acceden a los servicios ofrecidos por la clase a través de su
interfaz. A veces, una clase existente puede proporcionar la funcionalidad requerida por un cliente, pero
su interfaz no puede ser lo que el cliente espera. Esto podría suceder debido a diversas razones, por
ejemplo, la interfaz existente puede ser demasiado detallada, o puede carecer de detalle, o la
terminología utilizada por la interfaz puede ser diferente de lo que el cliente está buscando.
En tales casos, la interfaz existente tiene que ser convertida en otra interfaz, lo que el cliente espera,
preservando la reutilización de la clase existente. Sin dicha conversión, el cliente no será capaz de
utilizar la funcionalidad ofrecida por la clase. Esto se puede lograr utilizando el modelo de adaptador.
Solución
El patrón Adaptador sugiere definir una clase alrededor del objeto incompatible con la interfaz. Este
objeto contenedor se conoce como adaptador y el objeto que envuelve se refiere como un adaptado. El
adaptador proporciona la interfaz requerida esperada por el cliente. La implementación de la interfaz
adaptador convierte las solicitudes de cliente en llamadas a la interfaz de la clase adapte. En otras
palabras, cuando un cliente llama a un método adaptador, internamente la clase del adaptador llama a
un método de la clase adaptada, que el cliente no tiene conocimiento. Esto le da al cliente el acceso
indirecto a la clase adaptada. Por lo tanto, un adaptador puede ser utilizado para hacer que las clases
trabajen juntas, lo cual no podría ser de otra manera debido a las interfaces incompatibles.
En resumen, el patrón Adapter (Adaptador) se utiliza para transformar una interfaz en otra, de tal
modo que una clase que no pudiera utilizar la primera, haga uso de ella a través de la segunda.
Convierte la interfaz de una clase en otra interfaz que el cliente espera. Adapter permite a las clases

10
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

trabajar juntas, lo que de otra manera no podría hacerlo debido a sus interfaces incompatibles. También
es conocido como Wrapper (Envoltorio).
La término interfaz usado en la discusión anterior:
 No se refiere al concepto de una interfaz en lenguaje de programación Java o C#, aunque la
interfaz de una clase puede ser declarada mediante una interfaz Java o C#.
 No se refiere a la interfaz de usuario de una aplicación típica de interfaz gráfica de usuario que
consiste de los controles de las ventanas y GUI.
 Se refiere a la interfaz de programación que una clase expone, que es destinado a ser utilizado
por otras clases. Como ejemplo, cuando una clase está diseñada como una clase abstracta o una
interfaz Java, el conjunto de métodos declarado en él constituye la interfaz de la clase.
Adaptadoras de clases Vs. Adaptadores de objetos
Los adaptadores se pueden clasificar en dos categorías: adaptadores de Clases y adaptadores de objetos
(basados en la forma en que un adaptador es diseñado).
Adaptador de clase. Un adaptador de clase está diseñado por una subclase de la clase adaptada.
Además, un adaptador de clase implementa la interfaz esperada por el objeto cliente. Cuando un objeto
cliente invoca un método de clase adaptador, el adaptador llama internamente a un método de la clase
adaptada.
Adaptador de objeto. Un adaptador de objeto contiene una referencia a un objeto del adaptado.
Similar a una adaptador de clase, un adaptador de objeto también implementa la interfaz, de lo que el
cliente espera. Cuando un objeto de cliente llama a un método de objeto adaptador, el adaptador de
objeto invoca un método apropiado sobre la instancia adaptada, quien referencia su contenido. La
Tabla 1 enumera en detalles las diferencias entre adaptadores de clases y adaptadores de objetos.

Adaptador de clase Adaptador de objeto


Se basa en el concepto de herencia Utiliza composición de objetos
Solamente se puede utilizar para adaptar la Puede ser utilizado para adaptar la interface
interface del adaptado. No puede adaptar las del adaptado y todas sus subclases.
interfaces de sus subclases, debido a que el
adaptador es estáticamente linkeado con el
adaptado en el momento de su creación.
Debido a que el adapter es diseñado como No puede sobreescribir los métodos del
una subclase del adaptado, es posible adaptado (debido a que no hay herencia).
sobreescribir algunos comportamientos del Pero las funciones wrapper provistas por el
adaptado. adapter pueden cambiar el comportamiento
requerido.

11
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

El cliente tendrá algún conocimiento de la El cliente y el adaptado son completamente


interfaz del adaptado. Todas las interfaces desacoplados. Solamente el adapter es
públicas del adaptado son visibles al cliente. conciente las interfaces del adaptado.
En aplicaciones java: adaptable cuando las En aplicaciones java: adapatable cuando las
interfaces están disponibles en la forma de interfaces del objeto cliente están disponibles
interfaces java y no como clases concretas o en forma de clases abstractas o en forma de
abstractas. Esto debido a que java solo interfaces de java. O cuando hay necesidad
permite herencia simple. de adaptar la interface del adaptee así como
todas sus subclases.
Tabla 1. diferencias entre adaptadores de clases y adaptadores de objetos
Se debe usar el patrón Adapter cuando:
1. Se desea usar una clase existente, y su interfaz no concuerda con la necesitada.
2. Cuando se desea crear una clase reusable que coopera con clases no relacionadas, es decir, las
clases no tienen necesariamente interfaces compatibles.
Ejemplo
Imaginemos tenemos un sistema que maneja coches, barcos, aviones y parecidos. Generalmente los
motores que se usan son de gasolina, pero las nuevas tendencias han popularizado los motores
eléctricos.
Tenemos que el proveedor de vehículos eléctricos nos provee sus librerías para el motor eléctrico que
es prácticamente igual a nuestras implementaciones pero con otros nombres (por ejemplo, prender en
vez de encender, “mover más rápido” en vez de acelerar, etc.), y tiene una restricción extra: para poder
acelerar o detener el motor, este tiene que estar conectado. Y surge el problema ¿cómo hacemos para
que nuestras librerías puedan hacer uso del motor eléctrico? Podríamos reescribirlas todas, pero el
tiempo que eso nos tomaría sería mucho.
¿Cómo hacemos para integrar este MotorEléctrico al resto de nuestro sistema? Así es, con un adaptador
o adapter “envuelve” al objeto extraño (por eso le llaman wrapper también, ya que wrapper viene
siendo envoltorio).

Solución con adaptador de objetos


La Figura 5 muestra la solución. MotorEconomico y MotorGaston heredan de la clase abstracta Motor
y forman el sistema actual. La nueva solución crea la clase adaptadora MotorElectricoAdapter que
tiene una instancia de MotorElectrico. El adaptador se encarga no solo de corregir los nombres de los
métodos, sino también cosas como conectar y desconectar el motor, cosas que a nuestra
implementación no le importan. Pero lo más importante es que ahora podemos utilizar esta
implementación de Motor en nuestro sistema actual.

12
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Figura 5. Sistema de vehículos - Solución con Adaptador de objetos

2. Implementación en
Java
package patrones.adapter.eje1;
public abstract class Motor {
abstract public void encender();

abstract public void acelerar();

abstract public void apagar();


// ...mas metodos que hacen muchas cosas
}

package patrones.adapter.eje1;
public class MotorGaston extends Motor {
public MotorGaston() {
super();
System.out.println("Creando el motor gaston");
}

public void encender() {


System.out.println("Bum, bum....encendiendo motor gaston");
}

13
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

public void acelerar() {


System.out
.println("Buuuuuuuuuuuum, acelerando y gastando muuuucha
gas");
}

public void apagar() {


System.out.println("Apagando motor gaston");
}
}

public class MotorEconomico extends Motor {

public MotorEconomico() {
super();
System.out.println("Craendo motor economico");
}

public void encender() {


System.out.println("Encendiendo motor economico.");
}

public void acelerar() {


System.out.println("Acelerando motor economico.");
}

public void apagar() {


System.out.println("Apagando motor economico.");
}

package patrones.adapter.eje1;
/**
*
* La empresa que construye motores electricos nos manda su propia
* implementacion de su nuevo MotorElectrico:
*
*/
public class MotorElectrico {

private boolean conectado = false;

public MotorElectrico() {
System.out.println("Creando motor electrico");
this.conectado = false;
}

public void conectar() {


System.out.println("Conectando motor electrico");
this.conectado = true;
}

14
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

public void activar() {


if (!this.conectado) {
System.out
.println("No se puede activar porque no esta
conectado el motor electrico");
} else {
System.out.println("Esta conectado, activando motor
electrico....");
}
}

public void moverMasRapido() {


if (!this.conectado) {
System.out
.println("No se puede mover rapido el motor electrico
porque no esta conectado...");
} else {
System.out.println("Moviendo mas rapido...aumentando voltaje");
}
}

public void detener() {


if (!this.conectado) {
System.out
.println("No se puede detener motor electrico porque
no esta conectado");
} else {
System.out.println("Deteniendo motor electrico");
}
}

public void desconectar() {


System.out.println("Desconectando motor electrico...");
this.conectado = false;
}
}

package patrones.adapter.eje1;
/**
* El adapter de objeto, se encarga no solo de corregir los nombres de los metodos,
sino
* tambien cosas como conectar y desconectar el motor, cosas que a nuestra
* implementacien no le importan. Pero lo mas importante es que ahora podemos
* utilizar esta implemetanacion de Motor en nuestro sistema.
*
*/
public class MotorElectricoAdapter extends Motor {
private MotorElectrico motorElectrico;

public MotorElectricoAdapter() {
super();
this.motorElectrico = new MotorElectrico();

15
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

System.out.println("Creando motor Electrico adapter");


}

public void encender() {


System.out.println("Encendiendo motorElectricoAdapter");
motorElectrico.conectar();
motorElectrico.activar();
}

public void acelerar() {


System.out.println("Acelerando motor electrico...");
motorElectrico.moverMasRapido();
}

public void apagar() {


System.out.println("Apagando motor electrico");
motorElectrico.detener();
motorElectrico.desconectar();
}
}

package patrones.adapter.eje1;
public class ClienteMain {
public static void main(String [] args){
Motor motor = new MotorEconomico();
motor.encender();
motor.acelerar();
motor.apagar();

System.out.println();
motor = new MotorGaston();
motor.encender();
motor.acelerar();
motor.apagar();

System.out.println();
motor = new MotorElectricoAdapter() ;
motor.encender();
motor.acelerar();
motor.apagar();
}
}

Ejercicios

1. Durante el estudio del patrón Factory Method, se diseñó un sistema de registro de mensajes.
Se tenía la clase FileLogger con el método Log, el cual registra un mensaje de un cliente.

16
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Asumamos que un cliente LoggerClient espera una clase de registro de mensajes que
proporciona una interfaz como la siguiente:

public abstract class LoggerIntr{


public abstract bool LogMessage(string msg);
}//End of class

Diseñe un adaptador FileLoggerAdapter, para adaptar la clase FileLogger a la interface


existente.

17
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

2.1. Patrón Observer

Resumen
El patrón Observer es útil para diseñar un modelo de comunicación consistente entre un conjunto de
objetos dependientes y un objeto del que dichos objetos dependen. Esto permite que los objetos
dependientes tengan su estado, sincronizado con el objeto del cual dependen. El conjunto de objetos
dependientes se les llama observadores (observers) y el objeto del que ellos dependen se le llama sujeto
(subject).
Descripción
Problema
En ocasiones se requiere diseñar un modelo de comunicación consistente entre un conjunto de objetos
dependientes y un objeto del que dichos objetos dependen. Esto permite que los objetos dependientes
tengan su estado, sincronizado con el objeto del cual dependen. El conjunto de objetos dependientes se
les llama observadores (observers) y el objeto del que ellos dependen se le llama sujeto (subject).
Un observador típico es un objeto con interés o dependencia en el estado del sujeto. Un sujeto puede
tener más de un observador. Cada uno de los observadores necesita conocer cuándo el sujeto sufre un
cambio de estado.

Solución
El sujeto no puede mantener una lista estática de observadores. Por lo tanto, cualquier objeto con
interés en el estado del sujeto necesita explícitamente registrarse como un observador del sujeto. En
todo momento, cuando el sujeto sufre un cambio de estado, notifica a todos sus observadores
registrados. Después de recibir la notificación del sujeto, cada observador consulta el sujeto para
sincronizar su estado. De esta manera el sujeto se comporta como un publicador, publicando mensajes
con todos sus observadores suscritos.

En otras palabras, el escenario contiene uno-a-muchas relaciones entre un sujeto y el conjunto de


observadores. En todo momento cuando la instancia del sujeto sufre un cambio de estado, todos sus
observadores dependientes son notificados y se actualizan así mismos. Cada uno de los observadores
tienen que registrarse con el sujeto para poder ser notificado cuando hay un cambio de estado en el
sujeto. Un observador se puede registrar o suscribir con múltiples sujetos. En todo momento un
observador que ya no le interesa ser notificado, simplemente se puede des-registrar del sujeto.
Para que este mecanismo funcione:

El sujeto debe suministrar una interface para registrar y des-registrar los cambios y notificaciones.
Una de las dos consignas debe ser verdadera:
1. En el modelo, el sujeto debe suministrar una interface que active los observadores para
consultar el sujeto información del estado para poder actualizar su estado.
2. En el modelo, el sujeto debe enviar la información de estado que los observadores pueden estar
interesados.
Los observadores deben suministrar una interface para recibir notificaciones del sujeto.

18
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

La Figura 6, muestra la estructura de la lista de requisitos del patrón observer. De este diagrama se debe
tener en cuenta:
 Todos los sujetos esperan proveer la implementación de una interfaz similar a la interfaz
Observable.
 Todos los observadores esperan esperan tener una interfaz similar a la interfaz Observer.
 Bajo este esquema diferentes observadores pueden ser adicionados dinámicamente.

Figura 6. Diagrama genérico del patrón Observer

Ejemplo
A continuación se presenta un ejemplo muy sencillo que ilustra la aplicación del patrón observer. El
Sujeto es un contador. El contador mantiene un valor entero en el rango desde 0 hasta máximo, donde
máximo se establece en el constructor del objeto. La clase dispone de métodos para modificar el valor y
para acceder al estado (es estado lo conforman los valores de los atributos: valor y máximo).
A continuación se muestra la Tabla 2, que resume las clases que intervienen en la aplicación, su rol y la
funcionalidad, acorde a la Figura 6.

Clase Rol Funcionalidad


Contador Sujeto Mantiene un valor entero en el rango desde 0 hasta máximo

Observable Observable Clase abstracta que representa los objetos susceptibles de ser
observados. Incorpora métodos para agregar y eliminar observadores y un
método de notificación. La asociación Observable-Observadores se
implementa mediante un vector de observadores.
Observador Observador Los observadores deben implementar la interfaz Observador, es decir,
tienen un método actualizar() para reaccionar a cambios del sujeto.
Observador ConcreteOb Observador muy simple que ni siquiera consulta el estado del sujeto.

19
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

Concreto1 server
Observador ConcreteOb Un ejemplo de observador concreto de la clase contador (sujeto)
Concreto2 server
Observador ConcreteOb Un ejemplo de observador concreto de la clase contador (sujeto)
Concreto3 server
ClienteMain Cliente Un Cliente sencillo que prueba la ejecución del patrón
Tabla 2. Clases del ejemplo - Patrón Observer

La Figura 7 muestra el diagrama de clases de la aplicación.

Figura 7. Diagrama de clases de la aplicación

Implementación en
Java
A continuación el código fuente en java.
/**
* Ejemplo de sujeto observable o sujeto concreto. Es una clase que mantiene un
valor entero en
* el rango 0..maximo, donde maximo se establece en la construccion. La clase
* dispone de metodos para modificar el valor y para acceder al estado
* (valor, maximo). Extiende la clase observable heredando su
*/

public class Contador extends Observable {


// Atributos privados que mantienen el estado del contador
private int valor;
private int maximo;

/*
* El constructor inicializa el estado del objeto: el maximo y elvalor
* inicial, siempre en el rango 0..maximo. Adicionalmente,inicializa la
* parte de Observable que tiene un Contador...
*/
public Contador(int valor, int maximo) {

20
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

super();
this.maximo = maximo;
this.valor = enRango(valor);
}

// Este metodo privado asegura que un valor n esta entre 0..maximo


private int enRango(int n) {
if (n < 0) {
return 0;
} else if (n > maximo) {
return maximo;
} else {
return n;
}
}

/**
* Estos dos metodos permiten el acceso al estado del contador
* @return el valor del contador
*/
public int getValor() {
return valor;
}

public int getMaximo() {


return maximo;
}

/*
* Este metodo afecta al estado: primero se modifica de forma consistenteel
* estado y despues se notifica a los observadores del cambio
*/
public void incrementarContador(int n) {
valor = enRango(valor + n);
notificarObservadores();
}
}

/**
* Esta clase abstracta representa los objetos susceptibles de ser
* observados. Incorpora metodos para agregar y eliminar observadores
* y un metodo de notificacion. La asociacion Observable-Observadores
* se implementa mediante un vector de observadores
*/

import java.util.ArrayList;

public abstract class Observable {


// Este atributo privado mantiene el vector con los observadores
private ArrayList<Observador> observadores;

// El constructor crea el vector con la asociacion Observable-Observador


public Observable() {

21
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

observadores = new ArrayList<Observador>();


}

// Agregar y eliminar sencillamente operan sobre vector _observadores...


public void agregarObservador(Observador o) {
observadores.add(o);
}

public void eliminarObservador(Observador o) {


observadores.remove(o);
}

// Notificacion: Para cada observador se invoca el método actualizar().


public void notificarObservadores() {
for (Observador o : observadores) {
o.actualizar();
}
}
}

/**
* Los observadores deben implementar la siguiente interfaz, es decir,
* tienen un metodo actualizar() para reaccionar a cambios del sujeto
*/
public interface Observador {
public void actualizar();
}

/**
* Observador muy simple que ni siquiera consulta el estado del sujeto.
*
*/

public class ObservadorConcreto1 implements Observador {

public void actualizar() {


System.out.println("Observador concreto simple recibe actualizar!");
}
}

/**
* Un ejemplo de observador concreto de la clase contador.
*
*/

public class ObservadorConcreto2 implements Observador {


// Mantiene la asociacion con el contador
private Contador contador;

// El constructor de Medidor establece la asociacion entre Medidor-Contador


public ObservadorConcreto2(Contador contador) {
this.contador = contador;

22
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

/*
* Tras ser notificado de un cambio, un observador Medidor accedeal estado
* del Contador utilizando la asociacion
*/

public void actualizar() {


System.out.println("Valor del contador de ObservadorConcreto2: " +
contador.getValor());
}
}

/**
* Un ejemplo de observador concreto de la clase contador.
*
*/

public class ObservadorConcreto3 implements Observador {


// Mantiene asociacion con el sujeto observable
private Contador contador;

// El constructor establece la asociacion entre ValorContador-Contador


public ObservadorConcreto3(Contador contador) {
this.contador = contador;
}

/*
* Tras ser notificado de un cambio, un observador ValorContador accedeal
* estado del Contador utilizando la asociacion
*/
public void actualizar() {
System.out.println("Valor del contador de ObservadorConcreto3 es "
+ contador.getValor());
}
}

/**
* Cliente
*
*/
public class ClienteMain {
public static void main(String... argv) {

Contador c = new Contador(100, 255);

ObservadorConcreto1 oc1 = new ObservadorConcreto1();

c.agregarObservador(oc1);

ObservadorConcreto2 oc2 = new ObservadorConcreto2(c);

23
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

c.agregarObservador(oc2);

ObservadorConcreto3 oc3 = new ObservadorConcreto3(c);


c.agregarObservador(oc3);

System.out.println("Primer Incremento");
c.incrementarContador(5);

System.out.println("\nSegundo Incremento");
c.incrementarContador(5);

c.eliminarObservador(oc1);
System.out.println("\nTercer Incremento");
c.incrementarContador(5);

La ejecución del cliente debe mostrar la siguiente información por la consola:

Primer Incremento
Observador concreto simple recibe actualizar!
Valor del contador de ObservadorConcreto1: 105
Valor del contador de ObservadorConcreto3 es 105

Segundo Incremento
Observador concreto simple recibe actualizar!
Valor del contador de ObservadorConcreto1: 110
Valor del contador de ObservadorConcreto3 es 110

Tercer Incremento
Valor del contador de ObservadorConcreto1: 115
Valor del contador de ObservadorConcreto3 es 115

Ejercicios
1. Diseñe e implemente una aplicación para el monitoreo y reporte de diferentes eventos, con las
siguientes funcionalidades (utilizar el patrón observer):
 Cuando un evento ocurra, se envía el evento a un objeto EventManager el cual funciona como
un sujeto.
 Cuando EventManager recibe un evento, lo almacena en una base de datos y envía
notificaciones a tres objetos, los cuales toman una acción necesaria:
o Un objecto AlertSender envía notificaciones por email a diferentes usuarios donde se
notifica que el evento ha ocurrido.
o Dos objetos de reportes visualizan el evento en diferentes formatos.
2. Construya una aplicación de reporte de ventas para la gestión de un almacén con múltiples
departamentos. Las características de la aplicación incluye:
 Los usuarios deben poder seleccionar un departamento. Después de seleccionar el
departamento, dos tipos de reportes se visualizan (utilizar el patrón observer):

24
Universidad del Cauca
Facultad de Ingeniería Electrónica y Telecomunicaciones

o Reporte mensual: que consiste en una lista de transacciones del mes actual para el
departamento seleccionado.
o Gráfico de ventas del año (YTD - Year-to-Date sales): que consiste en un gráfico de
barras que muestra las ventas del año del departamento seleccionado por meses.
 En cualquier momento, cuando un departamento es seleccionado, los dos reportes deben ser
refrescados con los datos correspondientes a ese departamento.

25

Potrebbero piacerti anche