adaptadores_ejemplo

Patrones de diseño

2 Patrones de diseño.

Los patrones de diseño están íntimamente ligados a la programación orientada a objetos y quieren ir más allá a la hora de resolver problemas frecuentes en el desarrollo de software. Un patrón no es ni más ni menos que una solución a un problema típico de diseño.

Entre las características destacan su:

  • Reutilizabilidad (posibilidad de utilizarlo en distintas circunstancias).
  • Efectividad (ha resuelto problemas parecidos en otras ocasiones).
  • Utilización como vocabulario común entre ingenieros del software.
  • Estandarización.
  • Facilitan la comprensión de proyectos u estructuras más complejas.
  • No impiden que se utilice otra estrategia de diseño.
El termino Gang of Four (GoF) viene de los autores del libro “Desing Patterns” sobre patrones de diseño orientado a objetos escrito por los siguientes autores:

  • Erich Gamma
  • Richard Helm
  • Ralph Johnson
  • John Vlissides

2.1 GoF (Gang Of Four)

Estos autores dividen los patrones en tres subtipos:

  • Patrones creacionales.
  • Patrones estructurales.
  • Patrones de comportamiento.

2.1.1 Patrones creacionales.

Builder o constructor virtual. Permite abstraer el proceso de creación de un objeto y centralizarlo en un único punto.

Fábrica abstracta o Abstract Factory. Permite trabajar con objetos de distintas familias de forma transparente. En ocasiones en la creación de interfaces se necesita tratar con objetos de distintos tipos (botones, ventanas, etiquetas, etc) y una abstract factory facilita tanto el diseño como la implementación.

Prototype o prototipo. Servirá para crear nuevos objetos clonándolos de un objeto (instancia) ya existente.

Factory Method o método de fabricación. Tendremos una clase constructura la cual determinará el subtipo de objeto que se va a crear. Se utiliza cuando la casuística o diversidad de subtipos de objetos es grande.

Singleton.

En el patrón de diseño Singleton se reduce o limita el número de objetos a crear a solo uno. Igual que las variables estáticas solamente existirá un objeto por clase.

Utiliza este patrón cuando vas a realizar conexiones a bases de datos o a programar sockets. De esta forma centralizarás el acceso al recurso y controlarás el número de sesiones activas o conexiones.

Veamos un ejemplo de implementación de este patrón de diseño.

public class Singleton {

private static Singleton instance = null;
private Singleton() {
// Este método existe solamente para evitar la instanciación.
}
// Utilizamos el método getInstance para controlar la instanciación.
//solamente existirá un objeto único para la clase.
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
// Definimos los distintos métodos de nuestra clase singleton
protected static void dimeAlgo( ) {
System.out.println(“Método de la clase singleton”);
}
}

Como puedes observar, la clase tiene una referencia estática a la única instancia singleton y la devuelve invocando al método getInstance().

public class PruebaSingleton {
public static void main(String[] args) {
Singleton tmp = Singleton.getInstance( );
tmp.dimeAlgo( );
}
}
A Dimas le han encargado un proyecto en el que tiene que monitorizar el estado de un volcán. Concretamente quiere monitorizar mediciones de 4 o 5 puntos estratégicos del volcán. Esos puntos estratégicos recepcionarán con un sensor la temperatura del mismo.
Asimismo se necesita que el mecanismo de monitorización almacene una lectura de las 10 últimos valores de temperatura y que pueda mostrar el valor medio de dichas temperaturas.
Dimas cree que debería implementar la clase Monitor con el patrón de diseño Singleton. Ayúdale tú con la programación.

Object Pool o conjunto de objetos. (ojo, no pertenece a GoF) Este patrón de diseño utiliza un conjunto de objetos previamente inicializados para su uso inmediato. Permite mayor eficiencia puesto que no necesita ir creando y destruyendo objetos según necesidad. Este patrón busca la eficiencia y fluidez en la ejecución.

El funcionamiento es sencillo, un cliente le pide un objeto al pool, cuando termina de ejecutarlo lo devuelve y estará disponible para otro cliente que lo necesite.

Como puedes observar no se crean (salvo la primera vez) ni se destruyen objetos sino que se reciclan.

El patrón MVC o modelo-vista-controlador.
Explicado en profundidad en el siguiente link.

2.1.2 Patrones estructurales.

Los adaptadores o adapters

Un adaptador (adapter) o puente sirve para permitir extender la funcionalidad a clases de distintos tipos que en ocasiones no pueden utilizarlas por medio de la herencia por ejemplo.

El esquema de un adaptador es el siguiente:

adaptadoresesquema

Como se puede observar en el esquema, existe una clase cliente que utilizará un método de la clase adaptador. Este método es heredado de la clase base más genérica.

Como la clase adaptado tiene unas características específicas, la clase adaptador que hereda de la clase base creará un objeto de la clase adaptado para poder llamar al método específico de dicha clase. Obviamente la clase más específica adaptado no puede heredar de la clase adaptador.

Esto se ve más claro en un ejemplo concreto:

adaptadores_ejemplo

Tenemos un simulador que va a dibujar distintas escenas de situación. Puede dibujar escenas de ciudad o escenas de montaña. Las escenas de montaña pueden utilizar la clase Paisaje para mostrar el paisaje en la pantalla de simulador.

Las clases ya codificadas en Java funcionarían de la siguiente forma:

package adaptadorejemplo;

public abstract class escena {
abstract public void dibujar();
}

La clase escena es una clase abstracta. No se crearán objetos derivados de ella y servirá de superclase para otras clases derivadas como escenaCiudad y escenaMontaña.

La clase escenaCiudad dibujará en la pantalla del simulador una escena de ciudad. Como se puede observar esta clase hereda de la superclase abstracta escena. Es una subclase de ella.

package adaptadorejemplo;

public class escenaCiudad extends escena {
public escenaCiudad(){
super();
System.out.println(“Creando escena de ciudad”);
}

@Override
public void dibujar() {
System.out.println(“Dibujando escena de ciudad”);
}
}

La clase escenaMontaña dibujará en la pantalla del simulador una escena de montaña y necesitará utilizar la clase paisaje para dibujar el paisaje dentro de la escena de montaña. Como se puede observar esta clase hereda de la superclase abstracta escena. Es una subclase de ella.

package adaptadorejemplo;

public class escenaMontaña extends escena{
public escenaMontaña(){
super();
System.out.println(“Creando escena de Montaña”);
}

@Override
public void dibujar() {
System.out.println(“Dibujando escena de Montaña”);
paisaje p = new paisaje();
p.mostrar();
}

}

La clase paisaje (es la clase adaptada) va a ser utilizada por la clase escenaMontaña y dibujará en el simulador un paisaje determinado.

package adaptadorejemplo;

public class paisaje {
public void mostrar(){
System.out.println(“Mostrando paisaje…”);
}

}

La clase simulador va a hacer uso de dos métodos usarDibujarCiudad() y usarDibujarMontaña() pero el método público accesible a cualquier clase será el recorrido(). Este método dibujará un recorrido determinado del simulador por pantalla.

package adaptadorejemplo;

public class simulador {
private void usarDibujarCiudad(){
escena e = new escenaCiudad();
e.dibujar();
}
private void usarDibujarMontaña(){
escena e = new escenaMontaña();
e.dibujar();
}
public void recorrido(){
//primero el simulador entra en la ciudad
usarDibujarCiudad();
//ahora sale y pasa por la montaña
usarDibujarMontaña();
//regresa a la ciudad
usarDibujarCiudad();
}
}

A continuación se muestra una clase con un método main que nos va a servir para testear y depurar todo el código y las clases creadas anteriormente:

package adaptadorejemplo;

public class AdaptadorEjemplo {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
simulador s = new simulador();
s.recorrido();
}

}

La ejecución de este programa mostrará en la ventana del simulador el recorrido previamente diseñado.

Truco.
Utiliza este patrón de diseño cuando quieras añadir nuevas clases y no quieras que estas nuevas modificaciones afecten al código de las clases ya existentes.
A Dimas le han encargado un proyecto en el que tiene que monitorizar el estado de un volcán. Concretamente quiere monitorizar mediciones de 4 o 5 puntos estratégicos del volcán. Esos puntos estratégicos recepcionarán con un sensor la temperatura y temblores del mismo.
Se necesita crear la clase abstracta sensor de la cual puede haber dos tipos, sensor de temperatura y sensor sísmico. El sensor sísmico utilizará una clase diferente llamada sonda sísmica la cual devuelve un valor del movimiento sísmico en la escala de Ritcher.
El monitor deberá lanzar una alerta cada vez que la temperatura suba de 60 grados o la variación sísmica suba de 4,2 puntos básicos en la escala de Ritcher.
Dimas cree que debería implementar la clase Monitor con el patrón de diseño Adapter. Ayúdale tú con la programación.
Para implementarlo de momento haz que esos valores sean aleatorios hasta que le pasen a Dimas los drivers necesarios.

Los decoradores/decorators

Un patrón decorator permite añadir nueva funcionalidad a nuestras aplicaciones sin alterar la estructura lo que puede afectar al sistema.

Este tipo de diseño ofrece como un envoltorio o wrapper a clases ya preexistentes.

Veamos un ejemplo típico de cuando los decoradores son una solución efectiva:


decoratorexquema

Este tipo de patrones se suele utilizar en problemas en los que tenemos clases derivadas de una clase base y alguna de ellas queremos que sea adaptable o customizable. Entonces la diseñamos como clase decoradora y la programamos de tal forma que pueda adquirir la funcionalidad de las clases derivadas.

Todo esto quizás se vea de una forma más clara con un ejemplo concreto.

Tenemos un concesionario de coches en los que ofrecemos el equipamiento de los coches en una especie de packs. Nuestro concesionario ofrece de forma predeterminada unos packs adaptados prácticamente a la mayoría de los clientes como son el pack básico (para los que miran por la economía), el pack de cuero y el pack lujo (para los que no escatiman en detalles). También hay un cierto número de clientes que quieren hacerse su coche a medida y por lo tanto tenemos el pack decorador al que le podemos incluir los distintos extras como son las llantas de aleación, las lunas tintadas, los asientos de cuero y las luces de xenon.

A continuación se muestra el esquema de clases de nuestro supuesto:

decoratorejemplo

Veamos a continuación el código de cada una de las clases. Como se puede observar tenemos la clase base abstracta pack de la cual heredarán las demás clases.

public abstract class pack {
String descripcion=”";
public String getdescripcion(){ return descripcion;}
public abstract int precio();
}

Ahora vamos a crear tres subclases las cuales son los distintos pack que ofrece el concesionario como son el pack básico, el pack cuero y el pack lujo los cuales tienen una descripción diferente y un precio diferente.

public class pack_basico extends pack{
public pack_basico(){
descripcion=”Tapicería de tela, ruedas básicas, lunas básicas y pintura metalizada”;
}
@Override
public int precio(){ return 9200;}
}
public class pack_cuero extends pack {
public pack_cuero(){
descripcion=”Tapicería de cuero alcántara, ruedas básicas, lunas básicas y pintura metalizada”;
}
@Override
public int precio(){ return 10500;}
}
public class pack_lujo extends pack{
public pack_lujo(){
descripcion=”Tapicería de cuero, llantas 17 pulgadas, lunas tintadas, xenon y pintura metalizada”;
}
@Override
public int precio(){ return 12300;}

}

También necesitamos ofrecer a los clientes una configuración elegida por los mismos clientes. Nuestros clientes podrán elegir entre añadir a su coche llantas de aleación, lunas tintadas, interior de cuero o luces de xenón. Para ello necesitaremos echar mano de un decorator o decorador.

public abstract class decoratorPack extends pack{
public abstract String getdescripcion();
}

Esta clase permitirá realizar una configuración customizada la cual no interfiere en el esquema natural de la aplicación. Además tiene la ventaja de poderse modificar de una forma sencilla para poder ofrecer otro tipo de producto al cliente como navegador, cruise control, etc.

public class cuero extends decoratorPack{
pack mipack;
public cuero(pack p){ this.mipack=p; }
@Override
public String getdescripcion(){
return mipack.getdescripcion()+” + tapicería de cuero alcántara”;
}
@Override
public int precio(){
return 4300 + mipack.precio();
}
}
public class llantas extends decoratorPack{
pack mipack;
public llantas(pack p){ this.mipack=p; }
@Override
public String getdescripcion(){
return mipack.getdescripcion()+” + llantas de aleación”;
}
@Override
public int precio(){
return 3600 + mipack.precio();
}
}
public class lunastintadas extends decoratorPack{
pack mipack;
public lunastintadas(pack p){ this.mipack=p; }
@Override
public String getdescripcion(){
return mipack.getdescripcion()+” + lunas tintadas”;
}
@Override
public int precio(){
return 600 + mipack.precio();
}
}
public class xenon extends decoratorPack{
pack mipack;
public xenon(pack p){ this.mipack=p; }
@Override
public String getdescripcion(){
return mipack.getdescripcion()+” + luces de xenón iSync largo alcance”;
}
@Override
public int precio(){
return 2800 + mipack.precio();
}
}

Llegó el momento de probar la clase decorator con lo cual creamos un pequeño programa para probar todo lo creado.

Crearemos un pack de cuero, otro de lujo y otro básico. A este último le vamos a añadir algunos extras como son luces de xenón y lunas tintadas.

public class principal {
public static void main(String[] args){

pack p = new pack_cuero();
System.out.println(p.getdescripcion());
System.out.println(p.precio());

p = new pack_lujo();
System.out.println(p.getdescripcion());
System.out.println(p.precio());

p = new pack_basico();
System.out.println(p.getdescripcion());
System.out.println(p.precio());

//creamos una pack customizado con xenón y lunas
p = new xenon(p);
p = new lunastintadas(p);
System.out.println(p.getdescripcion());
System.out.println(p.precio());
}
}

Truco.
Utiliza este patrón de diseño cuando quieras añadir nueva funcionalidad a tus aplicaciones sin tener que modificar el esquema de clases.

Además de los patrones vistos existen otros como el Composite, Facade o fachada, Flyweight, Proxy, Bridge, etc.

2.1.3 Patrones de comportamiento.

Son patrones que como su nombre evidencia tratan sobre la interacción entre clases y objetos además de sus propios algoritmos.

Algunos patrones de comportamiento muy utilizados son los siguientes:

Iterador o Iterator. Permitirá al programador realizar recorridos por objetos de forma independiente al tipo de objeto que sean o cómo estén implementados.

Mediator o Mediador. Se define un objeto que realizará la mediación de la comunicación entre un conjunto de objetos.

Memento o Recuerdo. Permite trasladar un conjunto de objetos o sistema a un estado anterior.

Template Method (plantilla). Se define el esqueleto de un algoritmo de tal manera que las subclases redefinirán ciertos pasos sin modificar su estructura.

Visitor o Visitante. Permitirá al programador definir nuevas operaciones sin modificar las clases sobre una jerarquía.

Command u Órden. Permite encapsular una orden dentro de un objeto de tal manera que cuando se ejecuta no se necesita conocer el contenido de la misma.

State (Estado). Permite que un objeto modifique su comportamiento en el momento que cambie su estado interno.

Veamos un caso concreto de implementación con un ejemplo:

Se desea implementar una clase de tipo state para controlar el estado de un volcán. Cada vez que cambie de estado el monitor tiene que avisar de dicho cambio. El volcán puede estar en tres estados distintos: apagado, en reposo y activo. En reposo, el sensor detectará una temperatura entre 50 y 99 grados mientras que en estado activo, el sensor siempre marcará 100 grados o más.

El sensor deberá monitorizar cada 5 segundos si el estado del volcán está apagado, 3 si está en reposo y 1 si está activo.

Dependiendo del estado del volcán, la medición será diferente. Si el estado es activo, la medición será más compleja que si estuviese apagado. En nuestro ejemplo obviamente hemos simplificado este hecho.

estadoesquema

El esquema de clases con el que vamos a trabajar es el anterior. Como se puede observar, dependiendo del estado en el que se encuentre el volcán nos permitirá cambiar la forma de monitorizar por parte del sensor.
Aunque para implementarlo hemos recurrido a utilizar un número aleatorio, en la realidad, la medición se realizará con un dispositivo hardware.

El esquema de clases final implementadas en Java será el siguiente:

public interface estadovolcan {
public int mide();
}

Para implementar el estado utilizamos un interface. Los diferentes estados implementarán este interface con lo cual cada estado podrá implementar un método mide() diferente.

public class estado_reposo implements estadovolcan {
public int mide(){
System.out.println(“Midiendo en reposo…”);
int numeroAleatorio = (int) (Math.random()*200+1);
return numeroAleatorio;
};
}
public class estado_apagado implements estadovolcan {
public int mide(){
System.out.println(“Midiendo en estado apagado…”);
int numeroAleatorio = (int) (Math.random()*200+1);
return numeroAleatorio;
};
}
public class estado_activo implements estadovolcan {
public int mide(){
System.out.println(“Midiendo en estado activo…”);
int numeroAleatorio = (int) (Math.random()*200+1);
return numeroAleatorio;
};
}

Aunque el estado en nuestra aplicación implementa un método parecido para todas las clases, repetimos en en realidad este método será diferente (de ahí la ventaja de ese tipo de patrón).

A continuación se muestra el código de la clase sensor:

public class sensor {
private String nombresensor;
private estadovolcan estado;

sensor(String nombre){
this.nombresensor = nombre;
this.estado = new estado_apagado();
}

public String getnombresensor(){return nombresensor;}

public void esperar (int segundos) {
try {
Thread.sleep (segundos*1000);
} catch (Exception e) {
System.out.println(“Falló la espera”);
}
}

public void monitorizar(){
int grados=estado.mide();
if (grados<50){
System.out.println(grados+" -> Apagado”);
estado=new estado_reposo();
esperar(5);
}
if (grados>=50 && grados<=100){
System.out.println(grados+" -> En reposo”);
estado=new estado_reposo();
esperar(3);
}
if (grados>100){
System.out.println(grados+” -> Activo”);
estado=new estado_activo();
esperar(1);
}

}
}

Por último creamos una clase con un método principal para poder ejecutar y depurar nuestro programa:

public class Ejemplo_state {

public static void main(String[] args) {
sensor s = new sensor(“sensor1″);
System.out.println(“monitorizando sensor:”+s.getnombresensor());
s.monitorizar();
s.monitorizar();
s.monitorizar();
s.monitorizar();
}

}

Ejecutamos nuestro código y el resultado se muestra en la siguiente figura:

ejecuciónestate

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Puedes usar las siguientes etiquetas y atributos HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>