Este blog se realiza con el objetivo de mostrar algunos de los patrones de diseño que se estipulan en el libro de el Gof "Design Patterns"; en primera instacia se presentaran los patrones estructurales, pero la idea es ir ampliandolos. Se espera crear un espacio de discución , basado en los aportes y críticas de todo tipo que puedan aparecer. La idea es es el aprendizaje en comunidad, así, que cualquier observación es bienvenida.
Define un objeto que encapsula como interactua un conjunto de objetos. Favorece un bajo acoplamiento, liberando a los objetos de referenciarse unos a otros explícitamente, y permite variar la interacción de manera independiente. Aplicabilidad
Use el patrón mediator cuando:
La comunicación de un conjunto de objetos esta bien definida pero es compleja.
Reusar un objeto es difícil porque este se comunica con muchos otros objetos.
El comportamiento distribuido entre un numero grande de clases debe ser personalizable sin muchas subclases.
Estructura Participantes
Mediator: Define una interfaz para comunicarse con una objetos colegas.
ConcreteMediator: Implementa el comportamiento cooperativo entre los Objetos. Conoce y mantiene sus colegas.
Clases Collenguaje: Cada colega conoce su mediator y usa a este para comunicarse con otros colegas.
2. Memento Intención
Sin violar la encapsulación captura y externaliza estados internos de un objeto, de modo que el objeto puede ser externalizado más tarde.
Aplicabilidad Use el patrón memento cuando:
Una parte del estado de un objeto debe ser guardado para que pueda ser restaurado más tarde y
Una interfaz para obtener el estado de un objeto podría romper la encapsulación exponiendo detalles de
Estructura Participantes
Crea el memento y lo utiliza para recordar su estado.
Memento: Clase interna estática de Originator, que contiene su estado. El Originator determina que datos almacenar en el memento, y sólo el originator debe ser capaz de leer el memento.
CareTeaker: El objeto que desea preservar su estado. Nunca necesita saber que hay en el memento; sólo necesita saber que el objeto que restaurar el estado Originator.
3. Observer.
Intención Define una dependencia uno a muchos de objetos, de tal manera que cuando un objeto cambia el resto son notificados automáticamente. Aplicabilidad. Use el patrón observer cuando:
Una abstracción tiene dos aspectos, uno de los cuales depende del otro. Encapsular estos aspectos en objetos separados permite que los objetos varíen (y puedan ser reutilizados) de forma independiente
Un cambio en un objeto requiere que cambien otros y no sabemos cuáles ni cuántos.
Un objeto necesita notificar a otros, cambios en su estado sin hacer énfasis sobre quiénes son dichos objetos. Es decir, cuando no queremos que estén fuertemente acoplados.
Estructura Participantes
ConcreteSubject: Guarda el estado de interés para los objetos.
ConcreteObserver: Envía una notificación a sus observadores cuando cambia su estado.
ConcreteObserver: Mantiene una referencia a un objeto ConcreteSubject. Guarda el estado que debería permanecer sincronizado con el objeto observado. Implementa la interfaz.
Observer para mantener su estado consistente con el objeto observado.
4. State Intención Permite a un objeto cambiar su comportamiento cuando cambia su estado. El objeto parece cambiar de clase. Aplicabilidad Use el patrón State en cualquiera de los siguientes casos:
El comportamiento del objeto depende de su estado, y debe cambiar su comportamiento en tiempo de ejecución dependiendo de su estado.
Las operaciones tienen grandes estructuras CASE que dependen del estado del objeto, que es representado por uno o más constantes de tipo enumerado.
Estructura Participantes
Context: Define interfaz. Mantiene una instancia con el estado actual
State: Define la interfaz para el comportamiento asociado a un determinado estado del Contexto.
ConcreteState: Cada subclase implementa el comportamiento asociado con un estado del contexto.
Referencias bibliográficas [1] Erich Gamma et al, Elements of reusable object-oriented software. [2] Steve Holzner, Design patterns for dummies, Wiley Publishing Inc, Indianapolis, EEUU, 2006, 339. [3] Jesús García Molina, Análisis y diseño de software - Patrones de diseño, dis.um.es/~jmolina/astema3
Se presentan una descripción corta y general de algunos patrones de comportamiento, solo se habla de su intención, aplicabilidad, estructura y participantes, primero vamos a tratar los patrones chain of Responsibility, Command, Interprter y finalmente Iteratro.
1. Chain of Responsibility
Intención
Evita el acoplamiento del emisor de una petición y su receptor, para esto le da a más de un objeto de una cadena la responsabilidad de manejar la petición. La cadena recibe objetos y la pasa a lo largo de la cadena hasta que alguno de los elementos de la cadena se encargue de manejarlo.
Aplicabilidad
Use chain of responsibility cuando:
Más de un objeto puede manejar una petición.
Se desea enviar una solicitud a uno entre varios objetos sin especificar explícitamente el receptor.
El conjunto de objetos que puede manejar una solicitud puede ser especificado dinámicamente.
Estructura
Participantes
Handler: Define una interfaz para manejar el objeto. Opcionalmente puede definir el sucesor.
ConcreteHandler: Maneja la petición si puede. Puede acceder a su sucesor. Si la petición puede ser maneja, se concluye el proceso, sino se envía la petición al sucesor.
Cliente: Inicia la petición sobre en un objeto concreteHandler sobre la cadena.
2. Command Intención
Encapsula una petición como un objeto, así deja parametrizar a los clientes con diferentes peticiones, añadir a una cola peticiones y soportar operaciones.
Aplicabilidad
Command debe ser aplicado en los siguientes casos:
Objetos parametrizados dependen de la acción que ellos efectúan.
Especificar o agregar en una cola y ejecutar peticiones en momentos diferentes de tiempo.
Ofrecer soporte para desacoplar acciones.
Estructurar el sistema en operaciones de alto nivel que se basan en operaciones primitivas.
Desacopla el objeto que envía la acción del que la efectúa.
Estructura
Participantes
Command: declara una interfaz para ejecutar una operación.
ConcreteCommand: implementa la interfaz command. Mantiene una referencia a Receiver. Cuando se llama al método execute (), ConcreteCommand llama a uno o más métodos del Receiver.
Client: Crea un objeto ConcreteCommand le asigna su su objeto Receiver
Invoker: El invocador del método execute () de command. Pide a command que atienda peticiones ().
Receiver: conoce como se envían las operaciones.
3. Interpreter Intención
Dado un lenguaje, definir una representación para su gramática junto con un intérprete que utiliza la representación para interpretar sentencias en dicho lenguaje.
Aplicabilidad.
Este patrón se utiliza cuando hay un lenguaje que interpretar, y se puede representar las sentencias como arboles sintácticos abstractos, el patrón funciona mejor cuando:
La gramática del lenguaje es simple.
La eficiencia no es prioritaria para el sisttema.
Estructura
Participantes
AbstractExpression: declara una operación iterpret abstracta que es común a todos los nodos del árbol sintáctico abstracto.
TerminalExpression: Implementa un método interpret asociado a un símbolo de la gramática.
NonterminalExpression: Implementa AbstractExpression como TerminalExpression, pero la diferencia es que este no es un símbolo terminal en la estructura de árbol, sino un no. Mantiene a la siguiente expresión, e invoca métodos interpret en cada uno de sus hijos.
Context: Contiene información que es global al interpretador.
Client: Construye o recibe una instancia de un árbol de sintaxis abstracta.
4. Iterator
Intención
Provee una manera de acceder a un agregado secuencial de objetos, sin exponer detalles de su representación.
Aplicabilidad
Use el patrón Iterator:
Para acceder a los contenidos de objetos de un agregado, sin exponer su representación interna.
Para soportar múltiples recorridos de un agregado de objetos.
Para proveer un interfaz uniforme para recorrer diferentes agregados.
Estructura
Participantes.
Iterator: Define un interfaz para acceder y atravesar elementos.
ConcrteIterator: Implementa la interfaz iterator. Mantiene referencia a la actual posición.
Aggragate: Define una interfaz para crear un objeto Iterator.
ConcreteAggregate: Implementa la interfaz de de creación de iteradores y retorna una instancia de su propio ConcrteIterator.
Referencias bibliográficas [1] Erich Gamma et al, Elements of reusable object-oriented software. [2] Steve Holzner, Design patterns for dummies, Wiley Publishing Inc, Indianapolis, EEUU, 2006, 339. [3] Jesús García Molina, Análisis y diseño de software - Patrones de diseño, dis.um.es/~jmolina/astema3.
En paralelo con los avances en la programación orientada objetos, el moldeamiento de sistemas bajo este paradigma también ha evolucionado considerablemente, UML (Lenguaje Unificado de Modelado) es un lenguaje de moldeamiento que nos permite diseñar sistemas orientados a objetos, que entre muchas ventajas ofrece, un medio de comunicación entre desarrolladores, permite desglosar un problema complejo de forma entendible, promueve la reutilización identificando los componentes implicados en el desarrollo y como tal define la arquitectura del software (componentes, interfaces de usuario, bases de datos, lógica del negocio). En otras palabras, pensemos en UML como un lenguaje hecho para crear planos de software que análogamente a los planos en arquitectura nos indican cómo construir nuestro programa. Por estas razones es vital que en el mundo de la ingeniería de software todos conozcan por lo menos lo más importante y general sobre UML, en este artículo se presenta un breve resumen de los diagramas más importantes que hacen parte de este lenguaje, enfocándose meramente a definirlos sin entrar en detalle, a excepción de los diagramas de clase que como se dará cuenta sino es que ya lo hizo, son ampliamente utilizados en este blog para describir la estructura de los diferentes patrones que aquí se tratan.
En la versión 2.0 del lenguaje se incluyen 13 diagramas diferentes, que son clasificados en diagramas estructurales (trabajan sobre la estructura del sistema), diagramas de comportamiento (enfatizan en lo que debe hacer el sistema) y diagramas de interacción (enfatizan el flujo entre los diferentes elementos del sistema). Dentro de estos se hace una descripción general de los diagramas de clase y objetos que son de tipo estructural, los casos de uso de comportamiento y finalmente del diagrama de secuencia que es de interacción.
Diagramas de clase
Este es uno de los diagramas más importantes y populares del UML, debido a las numerosas herramientas CASE que pueden generar código a partir de estos. Los componentes que conforman los diagramas de clases son las relaciones y las clases.
En el diagrama de clases, una clase hace referencia a lo que entendemos por clase en la programación orientada objetos, así una clase, es la generalización de un objeto de la realidad, que posee métodos y atributos. La representación de una clase se hace mediante un rectángulo dividido en tres secciones. En la parte superior deberemos indicar su nombre, a continuación sus propiedades o atributos y en la tercera sección sus métodos. El nombre de la clase puede estar acompañados de etiquetas como <<>> para indicar que no es una clase sino una interfaz. Los atributos y los métodos pueden incorporarinformación adicional como por ejemplo el tipo de acceso (Público, privado, protegido), el tipo de datos de los atributos los parámetros de los métodos e incluso el tipo de retorno en el caso de las operaciones. Teniendo en cuenta lo anterior a continuación se muestra una típica clase en estos diagramas.
Hay diferentes tipos de relaciones enel diagrama de clases, a continuación se describen. • Asociación: Indica una relación genérica entre dos clases que puede estar acompañada por una multiplicidad, su anotación es simplemente una línea que une a las dos clases.
• Composición o agregación: Esta relación se da cuando una clase está contenida en otra, si una clase es parte fundamental en otra entonces hay composición, en este caso, la clase no puede existir sin su relación con la clase que asocia. En la agregación simplemente una clase incluye a la otra, pero la clase que es incluida tiene identidad en sí misma. • Dependencia: Es cuando una clase depende de otra porque la utiliza como atributo o parámetro de otra. • Generalización: es cuando hay una relación de herencia entre dos clases.
Otros diagramas
• Diagramas de objeto: Los diagramas de objetos muestran las relaciones entre objetos, se asemejan en cierta medida a los diagramas de clase, porque también se representan con rectángulos, pero en estos caso el nombre esta subrayado y está acompañado por dos punto y luego la clase de la cual es instancia el objeto. • Diagramas de casos de uso: Los casos de uso son un modelo de la funcionalidad del sistema desde el punto de vista del usuario, son muy utilizados para encontrar aciertos y errores, así como requerimientos del sistema desde el punto de vista del usuario. Nos permiten expresar gráficamente las relaciones entre los diferentes usos del mismo y sus participantes o actores. El resultado es un conjunto de diagramas muy fácilmente entendibles tanto por el cliente, como por los analistas del proyecto. Se compone de actores y casos de uso relaciones y limites del sistema.
• Diagramas de secuencia: Los diagramas de secuencia muestran la interacción entre los objetos del sistema en el tiempo. Proporcionando una buena base para identificar el comportamiento del sistema, pues muestran la lógica del flujo del sistema. Son comúnmente utilizados para describir problemas complejos, ya que muestran los elementos implicados en ellos. Se componen de objetos, actor, vida del objetos y activación y mensaje.
En este post, se presenta el código fuente de un sistema de validación sencillo hecho en java y conectado a una base de datos de MySQL. En primera instancia quiero aclarar que el sistema utiliza los patrones creacionales singleton y prototype; el primero garantiza que la conexión a la base de datos sea única, mientras el segundo ofrece flexibilidad y extensibilidad, en caso de que se quieran agregar mas tipos de usuarios al sistema (claro esta que estas no son las únicas ventajas que ofrecen). Como descripción general diremos que el sistema maneja con dos tipos de usuarios, el primero tiene todos los permisos y puede consultar listas de clientes registrados , así como eliminar registros y modificarlos (usuario tipo A), el otro únicamente puede consultar su registro y midificarlo (Usuario tipo b); la persistencia de datos se hace en mySQL . En primera instancia se presenta el diagrama de clases del sistema, luego se hacen algunas observaciones y finalmente se presenta el código fuente.
Diagrama de clases.
Como podrá observar hay una clase llamada usuario y dos subclases de ésta denominadas tipoUsarioA y tipoUsuarioB; estas clases son la estructura del prototype en el programa, el patrón de diseño singleton se encuentra en la clase conexión. A parte de estas clases existe una adicional que es la que presenta la interfaz gáfica al usuario una vez se ha validado su identidad correctamente, esta clase llamada ModuloPrincipal es la que genera la ventana que se presenta al inicio de este post. Observaciones.
Este programa funciona con una base de datos de mySQL, por lo tanto para ejecutarlo debe tener éste gestor instalado, o en tal caso instalarlo, cunado lo instale tiene que hacer dos cosas, en primer lugar tiene que crear una base de datos concorde con el código, esta debe llamarse validador, y debe tener dos tablas clientes y usuarios; la primera tiene tres campos nombre apellido y documento, todos son del tipo varchar de tamaño 10; la tabla usuarios también tiene tres campos login, clave y tipo, los dos primeros son varchar de 20 y 10 correspondientemente ,y el tercero es char (tamaño 1); las llaves primartias son nombre y login respectivamente. Además de esto tiemne que revisar en la clase conexion, del código fuente, el usuario y la clave con la que se conecta el sistema, puede crear un usuario conforme con estos datos o modificar el código para hacerlo compatible con su mySQL.
/** * metodo que recibe el nombre del tipo de Usuario * @param String * @uml.property name="tipoDeUsuario" */ public void setTipoDeUsuario(String usuario) { this.tipoDeUsuario = usuario;
}
/** * metodo que recibe el valor boolena de entrarsistema * @param boolean * @uml.property name="entarSistema" */ public void setEntrarSistema(boolean b) { this.entrarSistema = b; }
/** * metodo que recibe el valor boolena de obtenerUsuarios * @param boolean * @uml.property name="obtenerUsuarios" */ public void setObtenerUsuario(boolean b) { this.obtenerUsuarios = b; }
/** * metodo que recibe el valor boolean de EliminarUsuario * @param boolean * @uml.property name="eliminarUsuario" */ public void setEliminarUsuario(boolean b) { this.eliminarUsuario = b;
} /** * metodo que recibe el valor boolean de EliminarUsuario * @param boolean * @uml.property name="eliminarUsuario" */ public void setModifiarUsuario(boolean b) { this.modificarUsuario = b;
} /** * metodo que recibe el valor boolean de EliminarUsuario * @param boolean * @uml.property name="eliminarUsuario" */ public void setAdicionarUsuario(boolean b) { this.adicionarUsuario = b;
} public void setIdentificacion(String identificacion) { this.identificacion = identificacion; }
public void setNombre(String nombre) {
this.nombre = nombre; }
public void setapellido(String apellido) { this.apellido = apellido; } public boolean getEliminarUsuario(){ return this.eliminarUsuario; } public boolean getObtenerUsuario(){ return this.obtenerUsuarios; } public boolean getModifiarUsuario(){ return this.modificarUsuario; } public String getTipoDeUsuario() { return this.tipoDeUsuario; } public boolean getAdicionarUsuario(){ return this.adicionarUsuario; } public String getIdentificacion() { return identificacion; }
package co.edu.udistrital.validador.Logica; /** * Clase que define un tipo de usuario, hereda de la clase Usuario */
public class TipoUsuarioA extends Usuario{ /** * Metodo heredado de la clase usuario, que a su vez implementa * la interface Cloneable * * @return Object */ public Object clone(){ Object o=null; try{ o=super.clone(); }catch(CloneNotSupportedException e){ System.out.println("Error en tiempo de clonacion"); } return o; }
/** * Metodo que crea la clonacion y ejecuta la autoclonacion * * @return Usuario */ public Usuario copiarTipo(){ Usuario a=(Usuario)clone(); a.setTipoDeUsuario("Usuario de Tipo A"); a.setEntrarSistema(true); a.setObtenerUsuario(true); a.setEliminarUsuario(true); a.setAdicionarUsuario(true); a.setModifiarUsuario(true); return a; }
}
package co.edu.udistrital.validador.Logica;
/** * Clase que define un tipo de usuario, hereda de la clase Usuario */ public class TipoUsuarioB extends Usuario { /** * Metodo heredado de la clase usuario, que a su vez implementa * la interface Cloneable * * @return Object */ public Object clone() { Object o = null; try { o = super.clone(); } catch (CloneNotSupportedException e) { System.out.println("Error en tiempo de clonacion"); } return o; }
/** * Metodo que crea la clonacion y ejecuta la autoclonacion * * @return Usuario */ public Usuario copiarTipo() { Usuario a = (Usuario) clone(); a.setTipoDeUsuario("Usuario de Tipo B"); a.setEntrarSistema(true); a.setModifiarUsuario(true); return a; } }
/** * Clase que valida ingreso a los tipos de ususario. Esta clase implementa el patron Singleton ya que solo se necesita una instancia de esta. */ public class Validador {
/** * Retorna el numero de clave validada que ingreso el cliente * @return int * @uml.property name="tipoUsuario" */ public int getTipoUsuario() { return this.tipoUsuario; }
public Validador() { //Crea una instancia de la clase conexion conexion = Conexion.getConexion(); }
/** * consulta la tabla usuarios en la base de datos y valida * segun la clave ingresado por el cliente * * @param String * @return int */ public boolean validar(String cla, String usu) { this.valida = false; this.clave = cla; this.usuario=usu; conexion.setCadena("SELECT * FROM usuarios where login ='"+usuario+"'"); ResultSet tabla = conexion.Consultar(); try { while (tabla.next()) { if (this.clave.compareTo(tabla.getString("clave"))==0) { this.tipoUsuario = Integer.parseInt(tabla.getString("tipo")); this.valida = true; }
} } catch (SQLException ex) { System.out.println("Problemas con la tabla"); } return this.valida; } }
package co.edu.udistrital.validador.bd;
import java.sql.*;
/** * Clase encargada de realizar la conexion con la base de datos esta misma clase se encarga de consultar y hacer persistencia de datos. */ public class Conexion {
/** * el contructor se encargada de realizar la conexion a la base de datos * y muestra en consola mensajes si se pudo o no conectar a labase de datos. */ private Conexion() { try { System.out.println("Tratando de cargar el driver"); Class.forName("com.mysql.jdbc.Driver"); conexion = DriverManager.getConnection(url, login, password); if (conexion != null) { System.out.println("Conexion a base de datos " + bd + " OK"); } } catch (SQLException sqlEx) { System.out.println(sqlEx); } catch (ClassNotFoundException ex) { System.out.println(ex); } }
System.out.println("Totio"); } return ("Fue un exito"); }
/** * * Funcion encargada de realizar las consultas para poder usarla debe existir * una conexion a base de datos * * * @return ResultSet */ public ResultSet Consultar() { ResultSet tabla = null; try { Statement stmt = conexion.createStatement(); tabla = stmt.executeQuery(CadenaSql); } catch (SQLException ex) { System.out.println(ex); } return tabla; }
/** * Setter que asigna el valor a la cadenaSql * * @param String cadena; */ public void setCadena(String cadena) { CadenaSql = cadena; }
/** * getter que retorna * * @return Connection; */ public Connection getConectar() { return conexion; }
/** * Funcion encargada de realizar la desconexion a base de datos */ public void desconectar() { conexion = null; }
/** * Funcion encargada de retornar el objeto conexion y garantiza que la clase se instancie una sola vez. * @return Conexion * @uml.property name="conexion" */ static public Conexion getConexion() { if (conex == null) { conex = new Conexion(); } return conex; } }
import javax.swing.table.DefaultTableModel; /** * clase que se encarga de generar la interfaz gráfica para los usarios registrados. * @author Priscilos */
/** * Clase que se encarga de generar la interfaz gr�fica y de relacionarla co la l�gica */
public class Presentacion implements ActionListener {
//atributos de la GUI private JFrame frame; private JButton aceptar; private JButton clear; private JLabel log; private JLabel pass; private JPasswordField txtpassword; private JTextField txtloging; private Container pane; //atributos de validacion y usuarios private Validador val; private TipoUsuarioA TipoA; private TipoUsuarioB TipoB; private Usuario usuario = null; /** * El costructor de la clase inicializa todos los componentes de la GUI * */
public Presentacion() { frame = new JFrame("Validador"); aceptar = new JButton("Aceptar"); clear = new JButton("Cls"); log= new JLabel("Usuario: "); pass= new JLabel("Password: "); txtpassword = new JPasswordField(0); txtloging = new JTextField(0); pane = new JPanel(); val = new Validador();
frame.setBounds(450, 450, 380, 200); frame.setResizable(false); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } /** * Este m�todo se encarga de inicializar el usuario adecuaado de acuerdo con la validaci�n * @param t - tipo de usuario(dato que se optiene en la validaci�n) * @return Usuario que ingresa al sistema. */
public Usuario mostrarTipo(int t) { switch (t) { default: case 1: usuario = TipoA.copiarTipo(); break; case 2: usuario = TipoB.copiarTipo(); break; } return usuario; } /** * M�todo principal del programa * @param args */
public static void main(String[] args) { new Presentacion(); } /** * M�todo de la interface ActionListener, que se implementa, para manejar * los eventos generados por los botones que hacen parte del programa. */
public void actionPerformed(ActionEvent e) { if (e.getSource().equals(this.aceptar)) { /* * Despues de oprimir el boton aceptar se corre esta lineas de codigo. * Aqu� se llama el meto validar de validaci�n para validar el tipo de usuario. * Si el usuario existe en el sistema el m�todo validar devuelve true. * Luego con los dato otrogados por la clase validador se muestra un mensaje con el tipo de usuario. */ if (val.validar(this.txtpassword.getText(),this.txtloging.getText())) { new ModuloPrincipal(this.mostrarTipo(val.getTipoUsuario())); } else { JOptionPane.showMessageDialog(null, "USUARIO NO EXISTE", "ERROR", JOptionPane.ERROR_MESSAGE);
} } if (e.getSource().equals(this.clear)) { this.txtpassword.setText(""); } } }
Uso compartido de objetos para soportar un gran número de objetos finos eficientemente.
Motivo
Algunas aplicaciones pueden usar objetos durante todo su diseño, pero esto puede ocasionar implementaciones costosas. La mayoría de editores de documentos tienen formato de texto y facilidades de edición que son implementados por alguna extensión. Típicamente editores de documento orientados a objetos usan objetos para representar elementos embebidos como tablas o figuras, así también utilizan objetos para representar cada carácter, manejar el editor de esta ma-nera ofrece flexibilidad al sistema, pues pue-den ser dibujados y formateados uniforme-mente, con figuras y tablas, pero la desventaja es que un documento de texto puede tener miles de caracteres, por eso te-ner un objeto por carácter implica un gran costo debido a la memoria que puede consumir. Flyweight permite compartir objetos ligeros, para hacer el programa más liviano. Los objetos pueden compartir estados intrínsecos que no dependen del contexto, pero no pueden compartir los estados extrínsecos que dependen del contexto.
Aplicabilidad
La efectividad de este patrón depende de cómo y cuándo es utilizado, por eso es im-portante implementarlo siempre que todas las siguientes situaciones se cumplan: • Una aplicación usa un gran número de objetos. • El coste de almacenamiento es alto de-bido al excesivo número de objetos. • La gran mayoría de los estados de los objetos puede hacerse extrínseco. • Al separar el estado extrínseco, muchos grupos de objetos pueden reemplazarse por unos pocos objetos compartidos. • La aplicación no depende de la identi-dad de los objetos, pues el patrón se basa en el compartimento de objetos.
Estructura
Participantes
• Flyweight: Declara una interfaz por medio de la cual flyweights puede recibir e in-terpretar estados extrínsecos. • ConcreteFlyweight: implementa la interfaz Flyweight y almacena estados intrín-secos, si existen. Los ConcreteFlyweight deben ser compartibles, cualquier estado de este debe ser intrínseco. • UnsharedConcreteFlyweight: No todos los flyweight tienen que ser compartidos, la interfaz Flyweight habilita el comparti-miento pero no lo fuerza. Es común que los objetos UnsharedConcreteFlyweight tengan objetos ConcreteFlyweight como hijos en algún nivel de la estructura del flyweight. • FlyweightFactory: Crea y maneja objetos flyweight, se asegura que flyweights se-an compartidos adecuadamente. Cuando el cliente hace la petición de flyweight la FlyweightFactory proporciona una ins-tancia existente, y si no existe crea una. • Client: Mantiene una referencia a Fly-weight. Almacena o calcula los estados extrínsicos de los flyweights.
Colaboración
• Los estados que flyweights necesitan para funcionar, deben ser clasificados como intrínsecos o extrínsecos. Los estados intrínsecos se almacenan en ConcreteFlyweight, mientras los extrínsecos los conoce el cliente y los pasa como parámetros cuando llama operaciones de los flyweights. • Los clientes no deben obtener estados directamente de ConcretFlyweight, sino por medio de FlyweightFactory, quien es que se asegura que los objetos sean compartidos adecuadamente.
Consecuencias
El patrón puede tornarse pesado en tiempo de ejecución por la cantidad de transferen-cias, cálculos y búsquedas de estados extrínsecos que pueda manejar maneja. Sin embargo, esto se contrarresta con el espacio de memoria que se salva como producto de los estados que se comparten. La ganancia en memoria depende de varios factores: • La reducción del número total de instan-cias que son compartidas. • La cantidad de estados intrínsecos por objeto. • Si el estado extrínseco es calculado o almacenado. Es conveniente que los estados extrínsecos se calculen y no se almacenen, pues esto genera un ahorro extra de memoria.El patrón se suele combinar con composite, para representar la estructura jerárquica co-mo un grafo con nodos y hojas como objetos compartidos.
Implementación
Considere los siguientes aspectos cuando implemente el patrón de diseño flyweight: • Eliminar estados extrínsecos. El patrón depende ampliamente de identificar es-tados extrínsecos y removerlos de los objetos compartidos. Los estados extrín-secos, por su parte deben ser calcula-dos, en alguna parte donde los requeri-mientos de memoria sean bajos. • Debido a que los flyweight son comparti-dos, no deberían ser instanciados direc-tamente por los clientes. La clase Fly-weightFactory deja a los clientes localizar un flyweight en particular.
Código ejemplo
El código ejemplo pinta 5 pelotas en la pantalla, pero solo utiliza un objeto que sirve de referencia a las 5 pelotas.
//Interfa Flyweight
import java.awt.Image;
publicinterface Pelota {
public Image getPelota();
}
//ConcreteFlyweight implementa la inerfaz y guarda estados intrínsecos
• El primer uso que se le dio al patrón fue en un poderoso editor de texto, de inter-views, llamado Doc. • Et++ usa flyweight para soportar diferen-tes apariencias (look and feel).
Patrones relacionados
• AbstractFactory: El Flyweight factory es un Abstactfactory. • Como se menciono anteriormente, Com-posite es combinado con este patrón pa-ra representar las jerarquías de herencia como un árbol con nodos y hojas que hacen el papel de objetos compartidos. • Comúnmente es conveniente implemen-tar State y Strtegy son immolementados con Flyweight.
Referencias bibliográficas [1] Erich Gamma et al, Elements of reusable object-oriented software.
[2] Steve Holzner, Design patterns for dummies, Wiley Publishing Inc, Indianapolis, EEUU, 2006, 339.
[3] Jesús García Molina, Análisis y diseño de software - Patrones de diseño, dis.um.es/~jmolina/astema3.