Requerimientos y Patrones

November 10, 2023 | Author: Anonymous | Category: N/A
Share Embed Donate


Short Description

Download Requerimientos y Patrones...

Description

UNIVERSIDAD NACIONAL DEL SANTA

FACULTAD DE INGENIERÍA

E.A.P. SISTEMAS E INFORMATICA

TEMA: REQUERIMIENTOS Y PATRONES

CURSO: ING. DE SOFTWARE

PROFESOR: CAMILO SUAREZ

INTEGRANTES: 

CAMPOS AMAYA LEO



MORENO SAAVEDRA MARCO



CHONG QUISPE ERNESTO



VARGAS LOPEZ JEAN



VELARDE ZIMIC ANDREE

AÑO: 2016

Requerimientos y Patrones

1. Requerimientos

1.1.

Introducción

Entender los requerimientos de un problema es una de las tareas más difíciles que enfrenta el ingeniero de software.

Cuando se piensa por primera vez, no parece tan difícil desarrollar un entendimiento claro de los requerimientos. Después de todo, ¿acaso no sabe el cliente lo que se necesita? ¿No deberían tener los usuarios finales una buena comprensión de las características y funciones que le darán un beneficio?

Sobre las prácticas eficaces respecto de los requerimientos, alguien escribió lo siguiente:

Es la peor de las pesadillas. Un cliente entra a la oficina, toma asiento, lo mira a uno fijamente a los ojos y dice: “Sé que cree que entiende lo que digo, pero lo que usted no entiende es que lo que digo no es lo que quiero decir.”.

1.2.

Conceptos de obtención de requerimientos

1.2.1. Requerimientos funcionales Los requerimientos funcionales son declaraciones de los servicios que proveerá el sistema, de la manera en que éste reaccionará a entradas particulares. En algunos casos, los requerimientos funcionales de los sistemas también declaran explícitamente lo que el sistema no debe hacer. Muchos de los problemas de la ingeniería de software provienen de la imprecisión en la especificación de requerimientos. Para un desarrollador de sistemas es natural dar interpretaciones de un requerimiento ambiguo con el fin de simplificar su implementación. Sin embargo, a menudo no es lo que el cliente desea. Se tienen que estipular nuevos requerimientos y se deben hacer cambios al sistema, retrasando la entrega de éste e incrementando el costo. En principio, la especificación de requerimientos funcionales de un sistema debe estar completa y ser consistente. La compleción significa que todos los servicios solicitados por el usuario están definidos. La consistencia significa que los requerimientos no tienen definiciones contradictorias. En la práctica, para sistemas grandes y complejos, es imposible cumplir los requerimientos de consistencia y compleción. La razón de esto se debe parcialmente a la complejidad inherente del sistema y parcialmente a que los diferentes puntos de vista tienen necesidades inconsistentes. Estas inconsistencias son obvias cuando los requerimientos se especifican por primera vez. Los problemas emergen después de un análisis profundo. Una vez que éstos se hayan descubierto en las diferentes revisiones o en las fases posteriores del ciclo de vida, se deben corregir en el documento de requerimientos.

1.2.2. Requerimientos no funcionales y seudo-requerimientos

o REQUERIMIENTOS NO FUNCIONALES Son aquellos requerimientos que no se refieren directamente a las funciones específicas que entrega el sistema, sino a las propiedades emergentes de éste como la fiabilidad, la respuesta en el tiempo y la capacidad de almacenamiento. De forma alternativa, definen las restricciones del sistema como la capacidad de los dispositivos de entrada/salida y la representación de datos que se utiliza en la interface del sistema. Los requerimientos no funcionales surgen de la necesidad del usuario, debido a las restricciones en el presupuesto, a las políticas de la organización, a la necesidad de interoperabilidad con otros sistemas de software o hardware o a factores externos como los reglamentos de seguridad, las políticas de privacidad, entre otros.

o SEUDO-REQUERIMIENTOS

Son requerimientos impuestos por el cliente que restrieguen la implementación del sistema.

Ejemplos

Lenguaje de implementación. Plataforma en que el sistema debe ser implementado. Requerimientos del proceso y documentación.

1.2.3. Niveles de descripción

o División del trabajo.

Este conjunto de casos de uso describe tos procesos de trabajo de tos usuarios que son relevantes para el sistema. También se describe la parte del proceso soportada por el sistema, pero el meollo está en la definición de las fronteras entre los usuarios y el sistema. o Funciones del sistema específicas de la aplicación.

Este conjunto de casos de uso describe las funciones proporcionadas por el sistema que están relacionadas con el dominio de aplicación. o Funciones del sistema específicas del trabajo.

Este conjunto de casos de uso describe las funciones de apoyo del sistema que no están relacionadas con el dominio de aplicación. Éstas incluyen funciones para administración de archivos, funciones de agolpamiento, funciones para deshacer, etc. Estos casos de uso se extenderán durante el diseño del sistema cuando estemos discutiendo condiciones de frontera conocidas, como la inicialización del sistema, el apagado y las políticas para el manejo de excepciones. o Diálogo.

Este conjunto de casos de uso describe las interacciones entre los usuarios y la interfaz de usuario del sistema. El enfoque está en el diseño de la resolución del flujo de control y asuntos de disposición.

1.2.4. Corrección, suficiencia, consistencia, claridad y realismo

o Los requerimientos se validan en forma continua con el cliente y el usuario. La validación es un paso crítico en el proceso de desarrollo, tomando en cuenta que tanto el cliente como el desarrollador dependen de la especificación del sistema. La validación de requerimientos involucra la revisión para ver si la especificación es correcta, completa, consistente, realista y no es ambigua. Una especificación es correcta si representa la visión del cliente del sistema (es decir, todo lo que hay en el modelo de

requerimientos representa con precisión un aspecto del sistema). Es completa si se describen todos los escenarios posibles que

hay en el sistema, incluyendo el comportamiento excepcional (es decir, en el modelo de requerimientos están representados todos los aspectos del sistema). La especificación del sistema es consistente si no se contradice a sí misma. La especificación del sistema no es ambigua si está definido exactamente un sistema (es decir, no es posible interpretar la especificación en dos o más formas diferentes). Por último, es realista si el sistema puede implementarse dentro de esas restricciones.

1.2.5. Verificabilidad y rastreabilidad

o Dos propiedades deseables de una especificación del sistema son que sea verificable y ras- treable. La especificación es verificable si, una vez que se construye el sistema, se puede diseñar una prueba repetible para demostrar que el sistema satisface los requerimientos. Por ejemplo, una falla de tiempo medio durante cien años para el RelojSat sería difícil de lograr (suponiendo, en primer lugar, que fuera realista). Los siguientes requerimientos son ejemplos adicionales de requerimientos no verificables: 

El producto debe tener una buena interfaz de usuario (no se define buena).



El producto debe estar libre de errores (se requieren muchos recursos para determinarlo).



El producto debe responder al usuario en menos de un segundo en la mayoría de los casos (no se define “en la mayoría de los casos”).

o

Una especificación del sistema es rastreable si cada función del sistema puede rastrearse hasta su conjunto de requerimientos

correspondiente. La rastreabilidad no es una restricción en el contenido de la especificación sino, más bien, en su organización. La rastreabilidad facilita el desarrollo de pruebas y la validación sistemática del diseño contra los requerimientos. 1.2.6. Ingeniería a partir de cero (greenfield), reingeniería e ingeniería de interfaz

o Greenfield

En la ingeniería a partir de cero (greenfield) el desarrollo comienza sin nada, no existe sistema anterior y los requerimientos se extraen de los usuarios y del cliente. Un proyecto de ingeniería a partir de cero se activa por una necesidad del usuario o por la creación de un nuevo mercado. El RelojSat es un proyecto de ingeniería a partir de cero. o Reingeniería

Un proyecto de reingeniería es el rediseño y reimplementación de un sistema existente activado por los coordinadores de tecnología o por nuevo flujo de información [Hammer y Champy, 1993]. A veces se amplía la funcionalidad del nuevo sistema, pero el propósito inicial del sistema sigue siendo el mismo. Los requerimientos del nuevo sistema se extraen de un sistema existente.

o Ingeniería de Interfaz

Este tipo de proyecto es un proyecto de reingeniería en el cual el sistema heredado no puede descartarse sin entrañar altos costos. En esta sección examinamos la manera en que se realiza la obtención de requerimientos en estas tres situaciones

1.3.

Actividades para la obtención de Requerimientos

1.3.1. Identificación de los actores

Los actores representan entidades externas que interactúan con el sistema. Un actor puede ser un sistema humano o uno externo. En el ejemplo RelojSat, el propietario del reloj, los satélites GPS y el dispositivo serial WebificarReloj, son actores (vea la figura 4-3). Todos ellos interactúan e intercambian información con el RelojSat. Sin embargo, observe que todos ellos tienen interacciones específicas con el RelojSat: el propietario del reloj lo porta y observa, el reloj rastrea la señal de tos satélites GPS y WebificarReloj transfiere nuevos datos hacia el reloj. Los actores definen clases de funcionalidad.

Esto sirve para definir las fronteras del sistema y para encontrar todas las perspectivas desde las cuales los desarrolladores necesitan considerarlo. Cuando el sistema se despliega en una organización existente (como una compañía), por lo general ya existen la mayoría de los actores antes de que se desarrolle el sistema y corresponden a los papeles dentro de la organización.

1.3.2. Identificación de escenarios

Un escenario es “una descripción narrativa de lo que la gente hace y experimenta cuando trata de utilizar sistemas y aplicaciones de computadora” [Carroll, 1995]. Un escenario es

una descripción concreta, enfocada e informal de una sola característica del sistema desde el punto de vista de un solo actor. El uso de escenarios para la obtención de requerimientos es una desviación conceptual con respecto a las representaciones tradicionales que son genéricas y abstractas. Las representaciones tradicionales se centran en el sistema y no en el trabajo al cual da apoyo el sistema. Por último, su enfoque es la suficiencia, consistencia y precisión, mientras que los escenarios son abiertos e informales. Un enfoque basado en escenarios no puede reemplazar por completo (y no se pretende que lo haga) a los enfoques tradicionales. Sin embargo, mejora la obtención de requerimientos proporcionando una herramienta que es comprensible con facilidad para usuarios y clientes.

1.3.3. Identificación de casos de uso

Un escenario es una instancia de una cado de uso, esto es, un caso de uso especifica todos los escenarios posibles para una parte de funcionalidad dad, Un caso de uso es indicado por un actor.

Después de haber sido iniciado, un caso de uso también puede interactuar con otros actores. Un caso de uso representa un flujo de eventos completo a través del sistema, en el sentido de que describe una serie de interacciones relacionadas que resultan de la iniciación del caso de uso.

1.3.4. Refinamiento de los casos de uso

El uso de escenarios y casos de uso para definir la funcionalidad del sistema ayuda en la creación de requerimientos que son

validados por el usuario al inicio del desarrollo. Conforme empiezan el diseño y la implementación del sistema se incrementa el costo de los cambios a la especificación del sistema y la adición de nuevas funcionalidades no previstas. Aunque los requerimientos cambian hasta cerca del final del desarrollo, los desarrolladores y usuarios deben esforzarse para manejar desde el principio la mayoría de los requerimientos. Esto implica muchos cambios y experimentación durante la obtención de requerimientos. Observe que muchos casos de uso se escriben varias veces, otros se refinan mucho y otros, incluso, se eliminan por completo. Para ahorrar tiempo se puede realizar mucho trabajo de exploración usando escenarios y maquetas de interfaz. Se puede usar la siguiente heurística para la escritura de escenarios y casos de uso.

1.3.5. Identificación de las relaciones entre actores y casos de uso

o Relaciones de comunicación entre actores y casos de uso

Las relaciones de comunicación entre actores y casos de uso representan el flujo de información durante el caso de uso. Se debe distinguir entre el actor que inicia el caso de uso y los demás actores con los que se comunica el caso de uso. Por lo tanto, el control de acceso (es decir, cuál actor tiene acceso a cuál funcionalidad de clase) puede representarse a este nivel. Las relaciones entre actores y casos de uso se identifican cuando se identifican los casos de uso.

o Relaciones extendidas entre casos de uso

Un caso de uso extiende otro caso de uso si el caso de uso extendido puede incluir el comportamiento de la extensión bajo determinadas condiciones.

o Relaciones de inclusión entre casos de uso

Las redundancias entre casos de uso pueden factorizarse usando relaciones de inclusión. Supongamos, por ejemplo, que un D espachador necesita consultar el plano de la ciudad cuando abre un incidente (por ejemplo, para verificar cuáles áreas tienen riesgo durante un incendio) y cuando asigna recursos (por ejemplo, para saber cuáles recursos están más cercanos al incidente). o Relaciones extendidas frente a inclusión

Las construcciones de inclusión y extendidas son similares, y al principio puede ser que no le quede claro al desarrollador cuándo hay que usar cada una de ellas [Jacobson et al., 1992]. La principal distinción entre estas construcciones es la dirección de la relación. En el caso de una relación de inclusión, las condiciones bajo las cuales se inicia el caso de uso están descritas en el caso de uso iniciador como un evento en el flujo de eventos.

1.3.6. Identificación inicial de los objetos de análisis

Uno de los primeros obstáculos que encuentran los desarrolladores y los usuarios cuando colaboran es la terminología diferente. Se produce una falta de comprensión por usar los mismos términos en contextos diferentes y con diferente

significado. Aunque los desarrolladores con el tiempo aprenden la terminología de los usuarios, es probable que vuelvan a encontrar este problema cuando añadan nuevos desarrolladores al proyecto. Una vez que se han consolidado los casos de uso, los desarrolladores identifican los objetos participantes en cada caso de uso. Los objetos participantes corresponden a los conceptos principales del dominio de aplicación. Los desarrolladores los identifican, nombran y describen sin ambigüedad, y los reúnen en un glosario. Este glosario se incluye en la identificación del sistema y más adelante en los manuales de usuario. Los desarrolladores mantienen actualizado este glosario conforme evoluciona la especificación del sistema. Son muchos los beneficios del glosario: los nuevos desarrolladores quedan expuestos a un conjunto de definiciones consistentes, un solo término se usa para cada contexto (en vez de un término del desarrollador y un término del usuario) y cada término tiene un significado oficial preciso y claro.

1.3.7. Identificación de requerimientos no funcionales

Los requerimientos no funcionales describen aspectos del sistema visibles para el usuario que no están relacionados en forma directa con el comportamiento funcional del sistema. Los requerimientos no funcionales abarcan varios asuntos, desde la apariencia de la interfaz de usuario hasta los requerimientos de tiempo de respuesta y las cuestiones de seguridad. Los requerimientos no funcionales se definen al mismo tiempo que los funcionales, debido a que tienen mucho impacto en el desarrollo y costo del sistema. Se pueden obtener investigando los siguientes temas: 

Interfaz de usuario y factores humanos



Documentación



Consideraciones de hardware

1.4.



Características de desempeño



Manejo de errores y condiciones externas



Asuntos de calidad



Modificaciones al sistema



Ambiente Físico



Ambiente Físico



Cuestiones de Seguridad



Cuestiones de recursos.

Administración de la obtención de Requerimientos

En la sección anterior describimos las cuestiones técnicas del modelado de un sistema desde el punto de vista de tos casos de uso. Sin embargo, el modelado de casos de uso no constituye, por sí mismo, la obtención de requerimientos. Aun después de que se convierten en expertos modeladores de casos de uso, los desarrolladores todavía necesitan obtener requerimientos de los usuarios y llegar a un acuerdo con el cliente. En esta sección describimos métodos para la obtención de información de los usuarios y la negociación de un acuerdo con un cliente.

1.4.1. Obtención de información de los usuarios El análisis de tareas se basa en la suposición de que es poco eficiente pedir a tos usuarios que describan lo que hacen y la manera en que lo hacen. Los usuarios, por lo general, no piensan en forma explícita en la secuencia de tareas que se requieren para realizar su trabajo, ya que con frecuencia repiten estas tareas muchas veces. Cuando se pregunta a los usuarios la manera en que realizan su trabajo, describen, en el mejor de los casos, la manera en que se supone que lo realizan, y esto puede estar muy lejano de la realidad. En consecuencia, el análisis de tareas usa la observación como una alternativa para construir un modelo de tarea inicial. Este modelo de tarea inicial se refina luego preguntándole a los usuarios por qué realizan una tarea de determinada forma. Se puede resumir al KAT en 5 pasos. puede resumir al KAT con los cinco pasos siguientes:



Identificación de objetos y acciones. Se identifican los objetos y acciones asociadas con los objetos usando técnicas similares a las de la identificación de objetos en el análisis orientado a objetos, como el análisis de libros de texto, manuales, reglamentos, reportes, entrevistas con quien realiza la tarea y la observación de quien realiza la tarea.



Identificación de procedimientos

Un procedimiento es un conjunto de acciones, una condición previa necesaria para activar el procedimiento y una condición posterior. Las acciones pueden estar ordenadas en forma parcial. Los procedimientos se identifican mediante la redacción de escenarios, la observación de quien realiza la tarea y pidiéndole a quien realiza la tarea que seleccione y ordene tarjetas en las que se escriben acciones individuales. 

Identificación de objetivos y subjetivos

Un objetivo es un estado a lograr para que la tarea sea satisfactoria. Los objetivos se identifican mediante entrevistas durante la realización de una tarea o después de ella. Los subjetivos se identifican descomponiendo los objetivos. 

Identificación del carácter típico y la importancia.

Cada elemento identificado se califica de acuerdo con la frecuencia con que se le encuentra y si es necesario para la realización de un objetivo. 

Construcción de un modelo de la tarea.

La información recopilada antes se generaliza para tomar en cuenta las características comunes que hay entre las tareas. Se relacionan los objetivos, procedimientos y objetos correspondientes usando una notación textual o una gráfica. Por último se valida el modelo con quien realiza la tarea.

1.4.2. Negociación de especificaciones con los clientes

El diseño conjunto de aplicaciones (JAD, por sus siglas en inglés) es un método de requerimientos desarrollado por IBM a finales de los setenta. Su efectividad radica en que el trabajo de obtención de requerimientos se realiza en una sola sesión de trabajo en la que participan todos los involucrados. Usuarios, clientes, desarrolladores y un líder de sesión entrenado se sientan juntos en un salón para presentar sus puntos de vista, escuchar los puntos de vista de los demás, negociar y ponerse de acuerdo en una solución mutuamente aceptable. El resultado de la sesión de trabajo, el documento JAD final, es un documento de especificación de sistema completo que incluye definiciones de elementos de datos, flujos de trabajo y pantallas de interfez. Debido a que el documento final es desarrollado en forma conjunta por todos los interesados (es decir, los participantes que no sólo tienen un interés en el éxito del proyecto sino que también pueden tomar decisiones sustanciales), el documento JAD final representa un acuerdo entre usuarios, clientes y desarrolladores y, por lo tanto, minimiza los cambios de requerimientos posteriores en el proceso de desarrollo.



Definición del proyecto.

Durante esta actividad el coordinador JAD entrevista a gerentes y clientes para determinar los objetivos y el alcance del proyecto. Lo que encuentra en las entrevistas se recopila en la guía de definición administrativa. Durante esta actividad, el coordinador JAD forma un equipo compuesto por usuarios, clientes y desarrolladores. Están representados todos los interesados, y los participantes tienen la capacidad de tomar decisiones conjuntas. 

Investigación.

Durante esta actividad el coordinador JAD entrevista a los usuarios presentes y futuros, recopila información del dominio y describe los flujos de trabajo. El coordinador JAD también inicia una lista de asuntos que necesitarán tratarse durante la reunión. Los resultados principales de la actividad de investigación son una agenda de sesión y una especificación preliminar que lista el flujo de trabajo y la información del sistema. 

Preparación.

Durante esta actividad el coordinador JAD prepara la sesión. El coordinador JAD crea un documento de trabajo, primer borrador del documento final, una agenda para la sesión y cualquier cantidad de filminas o gráficas que representan la información recopilada durante la actividad de investigación. 

Sesión. Durante esta actividad el coordinador JAD guía al equipo para la creación de la especificación del sistema. Una

sesión JAD dura de tres a cinco días. El equipo define y se pone de acuerdo en el flujo de trabajo, los elementos de datos, las pantallas y los reportes del sistema. Todas las decisiones se documentan mediante un escribano que llena formularios JAD. 

Documento final.

El coordinador JAD prepara el documento final revisando el documento de trabajo para que incluya todas las decisiones tomadas durante la sesión. El documento final representa una especificación completa del sistema acordada durante la sesión. El documento final se distribuye a los participantes en la sesión para que lo revisen. Luego los participantes se reúnen durante una a dos horas para discutir las revisiones y finalizar el documento.

1.4.3. Validación de requerimientos: prueba de utilidad

Las pruebas de utilidad examinan la comprensión que tiene el usuario del modelo de los casos de uso. Las pruebas de utilidad encuentran problemas en la especificación del sistema permitiendo que los usuarios exploren el sistema o sólo parte de él (por ejemplo, la interfaz de usuario). Las pruebas de utilidad también se interesan en los detalles de la interfaz de usuario, como la apariencia de la interfaz de usuario, la disposición geométrica de las pantallas y el hardware. Por ejemplo, en el caso de una computadora que se lleva en el cuerpo, una prueba de utilidad podría comprobar la habilidad del usuario para darle comandos al sistema mientras se encuentra en una posición

difícil, como el caso de un mecánico viendo una pantalla debajo de un automóvil mientras revisa el silenciador del escape. Hay tres tipos de pruebas de utilidad: 

Prueba de escenario. Durante esta prueba se presenta un escenario visionario del sistema ante uno o más usuarios. Los desarrolladores identifican qué tan rápido pueden comprender tos usuarios el escenario, con cuánta precisión representa su modelo de trabajo y qué tan positivamente reaccionan a la descripción del nuevo sistema. Los escenarios seleccionados deberán ser lo más realistas y detallados posible. Una prueba de escenario permite una retroalimentación rápida y frecuente del usuario. Las pruebas de escenario pueden realizarse como maquetas en papel o con un ambiente prototipo simple, el cual con frecuencia es más fácil de aprender que el ambiente de programación que se usa para el desarrollo. La ventaja de las pruebas de escenario es que es barato realizarlas y repetirlas. La desventaja es que el usuario no puede interactuar en forma directa con el sistema y que los datos son fijos.



Prueba de prototipo.

Durante este tipo de prueba se presenta ante uno o más usuarios un fragmento de software que prácticamente implementa al sistema. Un prototipo vertical implementa un caso de uso completo a lo largo del sistema y un prototipo horizontal presenta una interfaz para la mayoría de los casos de uso (propacionando poca o ninguna funcionalidad). Las ventajas de las pruebas de prototipo consisten en que proporcionan una vista realista del sistema ante el usuario y que se pueden implementar tos prototipos para recolectar datos detallados. Las

desventajas de tos prototipos son los altos costos de producirlos y modificarlos. 

Prueba de producto

Esta prueba es similar a la de prototipo, a excepción de que se usa una versión funcional del sistema en vez del prototipo. Una prueba de producto sólo puede

1.4.4. Documentación de la obtención de requerimientos

Los resultados de la actividad de obtención de requerimientos y la actividad de análisis se documentan en el documento de análisis de requerimientos (RAD, por sus siglas en inglés). Este documento describe por completo al sistema desde el punto de vista de los requerimientos funcionales y no funcionales, y sirve como una base contractual entre el cliente y los desarrolladores. La audiencia del RAD incluye al cliente, los usuarios, los administradores del proyecto, los analistas del sistema (es decir, los desarrolladores que participan en los requerimientos) y los diseñadores del sistema (es decir, los desarrolladores que participan en el diseño del sistema). La primera parte del documento, incluyendo los casos de uso y los requerimientos no funcionales, se escribe durante la obtención de requerimientos. La formalización de la especificación en términos de modelos de objetos se escribe durante el análisis. La siguiente es una plantilla de ejemplo para un RAD:

2. Patrones

2.1.

Patrón Abstract Factory

2.1.1. Definición 

El patrón Abstract Factory nos permite crear, mediante una interfaz, conjuntos o familias de objetos (denominados productos) que dependen mutuamente y todo esto sin especificar cuál es el objeto concreto. Este patrón se puede aplicar cuando: Un sistema debe ser independiente de cómo sus objetos son creados. Un sistema debe ser 'configurado' con una cierta familia de productos. Se necesita reforzar la noción de dependencia mutua entre ciertos objetos.

2.1.2. Esquema



Cliente La clase que llamará a la factoría adecuada ya que necesita crear uno de los objetos que provee la factoría, es decir, Cliente lo que quiere es obtener una instancia de alguno de los productos (ProductoA, ProductoB).



AbstractFactory Es la definición de la interfaces de las factorías. Debe de proveer un método para la obtención de cada objeto que pueda crear. ("crearProductoA()" y "crearProductoB()")



Factorías Concretas Estas son las diferentes familias de productos. Provee de la instancia concreta de la que se encarga de crear. De esta forma podemos tener una factoría que cree los elementos gráficos para Windows y otra que los cree para Linux, pudiendo poner fácilmente (creando una nueva) otra que los cree para MacOS, por ejemplo.



Producto abstracto Definición de las interfaces para la familia de productos genéricos. En el diagrama son "ProductoA" y "ProductoB". En un ejemplo de interfaces gráficas podrían ser todos los elementos: Botón, Ventana, Cuadro de Texto, Combo... El cliente trabajará directamente sobre esta interfaz, que será implementada por los diferentes productos concretos.



Producto concreto Implementación de los diferentes productos. Podría ser por ejemplo "BotónWindows" y "BotónLinux". Como ambos implementan "Botón" el cliente no sabrá si está en Windows o Linux, puesto que trabajará directamente sobre la superclase o interfaz.

2.1.3. Ejemplo General Supongamos que disponemos de una cadena de pizzerías. Para crear pizzas disponemos de un método abstracto en la clase Pizzería que será implementada por cada subclase de Pizzería.

abstract Pizza crearPizza()

Concretamente se creará una clase Pizzería Zona por cada zona, por ejemplo la Pizzería de New York sería PizzeriaNewYork y la de California PizzeríaCalifornia que implementarán el método con los ingredientes de sus zonas.

Las pizzas son diferentes según las zonas. No es igual la pizza de New York que la pizza de California. Igualmente, aunque usarán los mismos ingredientes (tomate, mozzarella...) no los obtendrán del mismo lugar, cada zona los comprará donde lo tenga más cerca. Así pues podemos crear un método creador de Pizza que sea Pizza(FactoriaIngredientes fi); Como vemos utilizamos la factoría abstracta (no las concretas de cada zona, como podría ser IngredientesNewYork o IngredientesCalifornia). Pizza podrá obtener los ingredientes de la factoría independientemente de donde sea. Sería fácil crear nuevas factorías y añadirlas al sistema para crear pizzas con estos nuevos ingredientes. Efectivamente, en este ejemplo cliente es Pizza y es independiente de la Factoría usada.

El creador de la Pizza será el encargado de instanciar la factoría concreta, así pues los encargados de instanciar las factorías concretas serán las pizzerías locales. En PizzeríaNewYork

podemos tener el método crearPizza() que realice el siguiente trabajo: Pizza crearPizza() { FactoríaIngredientes fi = new IngredientesNewYork(); Pizza pizza = new Pizza(fi); // Uso de la factoría pizza.cortar(); pizza.empaquetar(); return pizza;} Como conclusión podemos observar que gracias a la factoría de ingredientes crear una nueva zona, por ejemplo una pizzería en Barcelona, no nos implicaría estar modificando el código existente, solo deberemos extenderlo (uno de los pilares de la Ingeniería del software) ya crearíamos la subclase de Pizzería: PizzeríaBarcelona que al instanciar la factoría solo debería escoger la factoría de Barcelona. Obviamente se debería crear la factoría de Barcelona que se encargaría de crear los productos obtenidos de Barcelona. Así que en ningún momento modificamos las pizzerías existentes, la superclase pizzería o las otras factorías o productos, solo creamos nuevas clases.

2.1.4. Ejemplo con acceso a BD

Basada en los patrones “Abstract Factory” y “Factory Method” Pasos: Definir la interface DAO de nuestros objetos de datos  public interface ClienteDAO {  public Cliente create (String nombre, String nif, String correo, String usuario, String clave)  throws DAOException;  public Cliente findClienteByUsuario (String usuario) throws DAOException;  public java.util.Collection findAll () throws DAOException;  public void update (Cliente c) throws DAOException; }

Definir la factoría abstracta de objetos DAO:           

public abstract class DAOFactoria { public abstract ClienteDAO getClienteDAO() throws DAOException; public abstract ProductoDAO getProductoDAO() throws DAOException; public final static int ACCESS = 1; ... public static DAOFactoria getDAOFactoria (int tipo) { switch (tipo) { case ACCESS: return new AccessDAOFactoria(); case XML: …… } Implementar la factoría concreta:

    

public class AccessDAOFactoria extends DAOFactoria{ ... public ClienteDAO getClienteDAO() { return (ClienteDAO) new AccessClienteDAO(ds); } } Simplemente instancia objetos DAO concretos. Implementar las clases DAO concretas:

            

import java.sql.*; public class AccessClienteDAO implements ClienteDAO { ... public Cliente create (String nombre, String nif, String correo, String usuario, String clave) throws DAOException { Connection con = null; try { con = ds.getConnection(); Statement stmt = con.createStatement(); stmt.executeUpdate(...); stmt.close(); con.close(); Cliente c = new Cliente(); c.setNombre(nombre); ... return c; }

2.2.

Patrón Estrategy

2.2.1. Definición 

El patrón Estrategia (Strategy) es un patrón de diseño para el desarrollo de software. Se clasifica como patrón de comportamiento porque determina cómo se debe realizar el intercambio de mensajes entre diferentes objetos para resolver una tarea. El patrón estrategia permite mantener un conjunto de algoritmos de entre los cuales el objeto cliente puede elegir aquel que le conviene e intercambiarlo dinámicamente según sus necesidades.

2.2.2. Esquema



Las clases y objetos que participan en este modelo son: Strategy o Declara una interfaz común para todos los algoritmos compatibles. El Contexutiliza esta interfaz para llamar al algoritmo definido por un ConcreteStrategy ConcreteStrategy o Implementa el algoritmo utilizando la interfaz de Strategy Context o Está configurado con un objeto ConcreteStrategy

o Mantiene una referencia a un objeto de Strategy o Puede definir una interfaz que le permite acceder a sus datos Strategy.

2.2.3. Ejemplo General public class Main { public static void main(String args[]) { //Usamos la estrategia A Strategy estrategia_inicial = new StrategyA(); Context context = new Context(estrategia_inicial); context.some_method();

//Decidimos usar la estrategia B Strategy estrategia2 = new StrategyB(); context.setStrategy(estrategia2); context.some_method();

//Finalmente,usamos de nuevo la estrategia A context.setStrategy(estrategia_inicial); context.some_method();

/** Salida: * Estrategia A * Estrategia B * Estrategia A

**/ } }

public class Context { Strategy c;

public Context( Strategy c ) { this.c = c; }

public void setStrategy(Strategy c) { this.c = c; }

//Método de estrategia 'c' public void some_method() { c.behaviour(); } }

public Interface Strategy{ public void behaviour(); }

public class StrategyA implements Strategy{ @Override public void behaviour() { System.out.println("Estrategia A"); } }

public class StrategyB implements Strategy{ @Override public void behaviour() { System.out.println("Estrategia B"); } }

2.2.4. Ejemplo con acceso a BD

Aplicado a un ejemplo en PHP, imaginemos que queremos conectarnos a una base de datos, pero no sabemos si va a ser Oracle o MySQL (podría haber tantas como quisiéramos). El usuario elegirá en un formulario las opciones de bases de datos que se especifiquen en un desplegable, pasando por POST el parámetro “dbms”. /* Fichero Conexion.php */ // Estrategia: Interfaz interface Conexion { public function conectar($usuario, $clave) { } }

/* Fichero ConexionOracle.php */ // Estrategia concreta: Oracle class ConexionOracle implements Conexion { public function conectar($usuario, $clave) { return oci_connect($usuario, $clave); } }

/* Fichero ConexionMysql.php */ // Estrategia concreta: MySQL class ConexionMysql implements Conexion { public function conectar($usuario, $clave) { return mysql_connect("localhost", $usuario, $clave); } }

/* Fichero index.php */ // Contexto /* * $_POST['dbms'] proviene de un cuadro desplegable (option select) en que se da a escoger entre oracle y mysql. * ucfirst() pone en mayúscula la primera letra */ $conector = "Conexion" . ucfirst($_POST['dbms']); $instancia = new $conector;

try { $recurso = $instancia->conectar($_POST['usuario'], $_POST['clave']); } catch (Exception $e) { echo "Lo sentimos, pero por algún motivo la conexión ha fallado."; }

2.3.

Patrón Factory Method

2.3.1. Definición

En diseño de software, el patrón de diseño Factory Method consiste en utilizar una clase constructora (al estilo del Abstract Factory) abstracta con unos cuantos métodos definidos y otro(s) abstracto(s): el dedicado a la construcción de objetos de un subtipo de un tipo determinado. Es una simplificación del Abstract Factory, en la que la clase abstracta tiene métodos concretos que usan algunos de los abstractos; según usemos una u otra hija de esta clase abstracta, tendremos uno u otro comportamiento.

2.3.2. Esquema

Las clases principales en este patrón son el creador y el producto. El creador necesita crear instancias de productos, pero el tipo concreto de producto no debe ser forzado en las subclases del creador, porque las posibles subclases del creador deben poder especificar subclases del producto para utilizar.

2.3.3. Ejemplo General

abstract class Creator{ // Definimos método abstracto public abstract Product factoryMethod(); } Ahora definimos el creador concreto:

public class ConcreteCreator extends Creator{ public Product factoryMethod() { return new ConcreteProduct(); } } Definimos el producto y su implementación concreta:

public interface Product{

public void operacion(); }

public class ConcreteProduct implements Product{ public void operacion(){ System.out.println("Una operación de este producto"); } } Ejemplo de uso:

public static void main(String args[]){ Creator aCreator; aCreator = new ConcreteCreator(); Product producto = aCreator.factoryMethod(); producto.operacion(); } 2.3.4. Ejemplo con acceso a BD Basada en los patrones “Abstract Factory” y “Factory Method” Pasos: Definir la interface DAO de nuestros objetos de datos  public interface ClienteDAO {  public Cliente create (String nombre, String nif, String correo, String usuario, String clave)  throws DAOException;  public Cliente findClienteByUsuario (String usuario) throws DAOException;  public java.util.Collection findAll () throws DAOException;  public void update (Cliente c) throws DAOException; }

Definir la factoría abstracta de objetos DAO:

          

public abstract class DAOFactoria { public abstract ClienteDAO getClienteDAO() throws DAOException; public abstract ProductoDAO getProductoDAO() throws DAOException; public final static int ACCESS = 1; ... public static DAOFactoria getDAOFactoria (int tipo) { switch (tipo) { case ACCESS: return new AccessDAOFactoria(); case XML: …… } Implementar la factoría concreta:

    

public class AccessDAOFactoria extends DAOFactoria{ ... public ClienteDAO getClienteDAO() { return (ClienteDAO) new AccessClienteDAO(ds); } } Simplemente instancia objetos DAO concretos. Implementar las clases DAO concretas:

            

import java.sql.*; public class AccessClienteDAO implements ClienteDAO { ... public Cliente create (String nombre, String nif, String correo, String usuario, String clave) throws DAOException { Connection con = null; try { con = ds.getConnection(); Statement stmt = con.createStatement(); stmt.executeUpdate(...); stmt.close(); con.close(); Cliente c = new Cliente(); c.setNombre(nombre); ... return c; }

2.4.

Patrón Prototype

2.4.1. Definición El patrón de diseño Prototype (Prototipo), tiene como finalidad crear nuevos objetos duplicándolos, clonando una instancia creada previamente. Este patrón especifica la clase de objetos a crear mediante la clonación de un prototipo que es una instancia ya creada. La clase de los objetos que servirán de prototipo deberá incluir en su interfaz la manera de solicitar una copia, que será desarrollada luego por las clases concretas de prototipos.

2.4.2. Esquema 

Cliente: Es el encargado de solicitar la creación de los nuevos objetos a partir de los prototipos.



Prototipo Concreto: Posee características concretas que serán reproducidas para nuevos objetos e implementa una operación para clonarse.



Prototipo: Declara una interfaz para clonarse, a la que accede el cliente.

2.4.3. Ejemplo General // Los productos deben implementar esta interface public interface Producto implements Cloneable { Object clone(); // Aquí van todas las operaciones comunes a los productos que genera la factoría }

// Un ejemplo básico de producto public class UnProducto implements Producto { private int atributo;

public UnProducto(int atributo) { this.atributo = atributo; }

public Object clone() { return new UnProducto(this.atributo); }

public String toString() { return ((Integer)atributo).toString(); } }

// La clase encargada de generar objetos a partir de los prototipos public class FactoriaPrototipo {

private HashMap mapaObjetos; private String nombrePorDefecto;

public FactoriaPrototipo() { mapaObjetos = new HashMap(); // Se incluyen en el mapa todos los productos prototipo mapaObjetos.put("producto 1", new UnProducto(1)); }

public Object create() { return create(nombrePorDefecto); }

public Object create(String nombre) { nombrePorDefecto = nombre; Producto objeto = (Producto)mapaObjetos.get(nombre); return objeto != null ? objeto.clone() : null; } }

public class PruebaFactoria { static public void main(String[] args) { FactoriaPrototipo factoria = new FactoriaPrototipo(); Producto producto = (Producto) factoria.create("producto 1"); System.out.println ("Este es el objeto creado: " + producto); } }

2.4.4. Ejemplo con acceso a BD

public abstract class Shape implements Cloneable private String id; protected String type; abstract void draw(); public String getType(){ return type; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Object clone() { Object clone = null;

try { clone = super.clone();

} catch (CloneNotSupportedException e) { e.printStackTrace(); }

return clone; } }

A continuación extendemos dicho objeto con nuestras clases que se diferencian solo en el tipo.

public class Rectangle extends Shape {

public Rectangle(){ type = "Rectangle"; }

@Override public void draw() { System.out.println("Insideangle::draw() method."); } } public class Square extends Shape {

public Square(){ type = "Square"; }

@Override public void draw() { System.out.println("Insidere::draw() method."); } } public class Circle extends Shape {

public Circle(){ type = "Circle"; }

@Override public void draw() { System.out.println("Insidele::draw() method."); } } A continuación creamos la clase que nos va a proporcionar su “cacheo” y evitar crearlos leyendo de la base de datos.

import java.util.Hashtable;

public class ShapeCache {

private static Hashtable shapeMap = new Hashtable();

public static Shape getShape(String shapeId) { Shape cachedShape = shapeMap.get(shapeId); return (Shape) cachedShape.clone(); }

// for each shape run database query and create shape // shapeMap.put(shapeKey, shape); // for example, we are adding three shapes public static void loadCache() { Circle circle = new Circle(); circle.setId("1

shapeMap.put(circle.getId(),circle);

Square square = new Square(); square.setId("2

shapeMap.put(square.getId(),square);

Rectangle rectangle = new Rectangle(); rectangle.setId("3

shapeMap.put(rectangle.getId(), rectangle);

} } Tan solo nos queda usarlo

public class PrototypePatternDemo { public static void main(String[] args) { ShapeCache.loadCache();

Shape clonedShape = (Shape) ShapeCache.getShape("1"); System.out.println("Shape+ clonedShape.getType());

Shape clonedShape2 = (Shape) ShapeCache.getShape("2"); System.out.println("Shape+ clonedShape2.getType());

Shape clonedShape3 = (Shape) ShapeCache.getShape("3"); System.out.println("Shape+ clonedShape3.getType()); } } y comprobar la salida en consola

Shape : Circle Shape : Square Shape : Rectangle

2.5.

Patrón Singleton

2.5.1. Definición 

Su intención consiste en garantizar que una clase sólo tenga una instancia y proporcionar un punto de acceso global a ella.



El patrón singleton se implementa creando en nuestra clase un método que crea una instancia del objeto sólo si todavía no existe alguna. Para asegurar que la clase no puede ser instanciada nuevamente se regula el alcance del constructor (con modificadores de acceso como protegido o privado).



La instrumentación del patrón puede ser delicada en programas con múltiples hilos de ejecución. Si dos hilos de ejecución intentan crear la instancia al mismo tiempo y esta no existe todavía, sólo uno de ellos debe lograr crear el objeto. La solución clásica para este problema es utilizar exclusión mutua en el método de creación de la clase que implementa el patrón.

2.5.2. Esquema

2.5.3. Ejemplo General

public class Singleton { private static Singleton INSTANCE = null;

// Private constructor suppresses private Singleton(){}

// creador sincronizado para protegerse de posibles problemas multi-hilo // otra prueba para evitar instanciación múltiple private synchronized static void createInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } }

public static Singleton getInstance() { if (INSTANCE == null) createInstance(); return INSTANCE; } }

2.5.4. Ejemplo con acceso a BD

 Crearemos una clase llamada Conexion, y para empezar declararemos una instancia de ésta clase y un constructor de manera privada: public class Conexion{ private static Conexion instancia = null; private Conexion(){

} }

Ahora, crearemos el punto de acceso a nuestra instancia mediante un método getInstancia: public static Conexion getInstancia(){ if(instancia == null){ instancia = new Conexion(); } return instancia; }

Para finalizar añadimos una clase que se encargue de tener almacenada la conexión con la base de datos Mysql y un método de acceso a dicha clase. El código final quedaría de la siguiente manera:

2.6.

Patrón Adapter

2.6.1. Definición

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.

2.6.2. Esquema 

Target define la interfaz específica del dominio que Client usa.



Client colabora con la conformación de objetos para la interfaz Target.



Adaptee define una interfaz existente que necesita adaptarse



Adapter adapta la interfaz de Adaptee a la interfaz Target

2.6.3. Ejemplo General

package Structural_patterns; public class AdapterWrapperPattern { public static void main(String args[]){ Guitar eGuitar = new ElectricGuitar(); eGuitar.onGuitar();

eGuitar.offGuitar(); Guitar eAGuitar = new ElectricAcousticGuitar(); eAGuitar.onGuitar(); eAGuitar.offGuitar(); } public abstract class Guitar{ abstract public void onGuitar(); abstract public void offGuitar(); } public class ElectricGuitar extends Guitar{ public void onGuitar() { System.out.println("Playing Guitar"); } public void offGuitar() { System.out.println("I'm tired to play the guitar"); } } /** * Class to Adapter/Wrapper */ public class AcousticGuitar{

public void play(){ System.out.println("Playing Guitar"); } public void leaveGuitar(){ System.out.println("I'm tired to play the guitar"); } }

/** * we Adapter/Wrapper AcousticGuitar into * ElectricAcousticGuitar to adapt into the GuitarModel */ public class ElectricAcousticGuitar extends Guitar{ AcousticGuitar acoustic = new AcousticGuitar();

public void onGuitar() { acoustic.play(); }

public void offGuitar() { acoustic.leaveGuitar(); } } }

2.6.4. Ejemplo con acceso a BD

El proyecto es una aplicación que maneja contactos personales (nombre, teléfonos, correos, etc.), y fue desarrollada por un grupo reducido

de

desarrolladores.

Para

empezar

tenemos

una

interfaz Contacto que implementarán todos los tipos diferentes de contactos que existan y que consta de lo siguiente:

Las clases que implementan ésta interfaz son Amigo y Familiar, les recuerdo que las clases serán de lo mas simple, con el fin de enfocarnos únicamente en la implementación del patrón:

El problema surgió cuando se le encargó a un nuevo integrante del equipo

desarrollar

una

nueva

clase

para

los

contactos

que

fuera CompañerosTrabajo, dicha clase quedó de la siguiente manera:

Como se puede observar la clase es totalmente diferente al estándar de las demás clases desarrolladas. Para solucionar esto se creó una clase que adaptara la clase CompañerosTrabajopara que implemente la interfaz Contacto.

¡Listo! Ya tenemos nuestra clase CompañerosTrabajo adaptada y que implementa la interfaz Contacto. Si la llamamos desde nuestro código principal tendremos lo siguiente.

2.7.

Patrón Bridge

2.7.1. Definición 

El patrón Bridge, también conocido como Handle/Body, es una técnica usada en programación para desacoplar una abstracción de su implementación, de manera que ambas puedan ser modificadas independientemente sin necesidad de alterar por ello la otra.



Esto es, se desacopla una abstracción de su implementación para que puedan variar independientemente.

2.7.2. Esquema



Abstraction define una interface abstracta. Mantiene una referencia a un objeto de tipo Implementor.



RefinedAbstraction extiende la interface definida por Abstraction



Implementor define la interface para la implementación de clases. Esta interface no se tiene que corresponder exactamente con la interface de Abstraction; de hecho, las dos interfaces pueden ser bastante diferente. Típicamente la interface Implementorprovee

sólo operaciones primitivas, y Abstraction define operaciones de alto nivel basadas en estas primitivas. 

ConcreteImplementor implementa la interface de Implementor y define su implementación concreta.

2.7.3. Ejemplo General

interface Implementador { public abstract void operacion(); }

/** primera implementacion de Implementador **/ class ImplementacionA implements Implementador{ public void operacion() { System.out.println("Esta es la implementacion A"); } } /** segunda implementacion de Implementador **/ class ImplementacionB implements Implementador{ public void operacion() { System.out.println("Esta es una implementacion de B"); } } /** interfaz de abstracción **/ interface Abstraccion { public void operacion(); } /** clase refinada que implementa la abstraccion **/ class AbstraccionRefinada implements Abstraccion{ private Implementador implementador;

public AbstraccionRefinada(Implementador implementador){ this.implementador = implementador; } public void operacion(){ implementador.operacion(); } } /** aplicacion que usa el patrón Bridge **/ public class EjemploBridge { public static void main(String[] args) { Abstraccion[] abstracciones = new Abstraccion[2]; abstracciones[0] = new AbstraccionRefinada(new ImplementacionA()); abstracciones[1] = new AbstraccionRefinada(new ImplementacionB()); for(Abstraccion abstraccion:abstracciones) abstraccion.operacion(); } } 2.7.4. Ejemplo con acceso a BD

public class Abstraction { public ImplementationBase Implementer { get; set; }

public virtual void Operation() { Console.WriteLine("ImplementationBase:Operation()"); Implementer.OperationImplementation(); } }

public class RefinedAbstraction : Abstraction { public override void Operation() { Console.WriteLine("RefinedAbstraction:Operation()"); Implementer.OperationImplementation(); } }

public abstract class ImplementationBase { public abstract void OperationImplementation(); }

public class ConcreteImplementation1 : ImplementationBase { public override void OperationImplementation() { Console.WriteLine("ConcreteImplementation1:OperationImplementation()"); } } public class ConcreteImplementation2 : ImplementationBase { public override void OperationImplementation() { Console.WriteLine("ConcreteImplementation2:OperationImplementation()");

} }

2.8.

Patrón Composite

2.8.1. Definición 

El patrón Composite sirve para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.



Esto simplifica el tratamiento de los objetos creados, ya que al poseer todos ellos una interfaz común, se tratan todos de la misma manera. Dependiendo de la implementación, pueden aplicarse procedimientos al total o una de las partes de la estructura compuesta como si de un nodo final se tratara, aunque dicha parte esté compuesta a su vez de muchas otras. Un claro ejemplo de uso extendido de este patrón se da en los entornos de programación 2D para aplicaciones gráficas. Un videojuego puede contener diferentes capas "layers" de sprites (como una capa de enemigos) pudiéndose invocar un método que actúe sobre toda esta capa de sprites a la vez (por ejemplo, para ocultarlos, darles un filtro de color etc.).

2.8.2. Esquema



Component Es la abstracción de todos los componentes, incluyendo los compuestos declara la interfaz de objetos en la composición (Opcional) define una interfaz para acceder a los padres de un componente en la estructura recursiva, y aplica esto si lo considera apropiado



Leaf Representa objetos hoja en la composición implementa todos los métodos de componentes



Composite

Representa un compuesto de componentes (componente de tener hijos) implementa métodos para manipular los niños implementa todos los métodos de componentes, en general, por delegación a sus hijos

2.8.3. Ejemplo General import java.util.*; public abstract class Componente { protected String nombre; public Componente (String nombre) { this.nombre = nombre; } abstract public void agregar(Componente c); abstract public void eliminar(Componente c); abstract public void mostrar(int profundidad); } class Compuesto extends Componente

{ private ArrayList hijo = new ArrayList(); public Compuesto (String name) { super(name); } @Override public void agregar(Componente componente) { hijo.add(componente); } @Override public void eliminar(Componente componente) { hijo.remove(componente); } @Override public void mostrar(int profundidad) { System.out.println(nombre + " nivel: " + profundidad); for (int i = 0; i < hijo.size(); i++) hijo.get(i).mostrar(profundidad + 1); } } class Hoja extends Componente { public Hoja (String nombre) { super(nombre);

} public void agregar(Componente c) { System.out.println("no se puede agregar la hoja"); } public void eliminar(Componente c) { System.out.println("no se puede quitar la hoja"); } public void mostrar(int depth) { System.out.println('-' + "" + nombre); } } public class Client { public static void main(String[] args) { Compuesto raiz = new Compuesto("root"); raiz.agregar(new Hoja("hoja A")); raiz.agregar(new Hoja("hoja B")); Compuesto comp = new Compuesto("compuesto X"); comp.agregar(new Hoja("hoja XA")); comp.agregar(new Hoja("hoja XB")); raiz.agregar(comp); raiz.agregar(new Hoja("hoja C")); Hoja l = new Hoja("hoja D"); raiz.agregar(l); raiz.eliminar(l);

raiz.mostrar(1); } }

2.8.4. Ejemplo con acceso a BD

package corepatterns.apps.psa.ejb;

import corepatterns.apps.psa.core.*; import corepatterns.apps.psa.dao.*; import java.sql.*; import javax.sql.*; import java.util.*; import javax.ejb.*; import javax.naming.*;

public class ResourceEntity implements EntityBean { public String employeeId; public String lastName; public String firstName; public String departmentId; public String practiceGroup; public String title; public String grade; public String email; public String phone; public String cell; public String pager; public String managerId;

// Collection of BlockOutTime Dependent objects public Collection blockoutTimes;

// Collection of SkillSet Dependent objects public Collection skillSets;

...

private EntityContext context; // Entity Bean methods implementation public String ejbCreate(ResourceTO resource) throws CreateException { try { this.employeeId = resource.employeeId; setResourceData(resource); getResourceDAO().create(resource); } catch(Exception ex) { throw new EJBException("Reason:" + ...); } return this.employeeId; }

public String ejbFindByPrimaryKey(String primaryKey) throws FinderException { boolean result; try { ResourceDAO resourceDAO = getResourceDAO(); result = resourceDAO.selectByPrimaryKey(primaryKey);

} catch(Exception ex) { throw new EJBException("Reason:" + ...); } if(result) { return primaryKey; } else { throw new ObjectNotFoundException(...); } }

public void ejbRemove() { try { // Remove dependent objects if(this.skillSets != null) {

SkillSetDAO skillSetDAO = getSkillSetDAO(); skillSetDAO.setResourceID(employeeId); skillSetDAO.deleteAll(); skillSets = null; } if(this.blockoutTime != null) { BlockOutTimeDAO blockouttimeDAO = getBlockOutTimeDAO(); blockouttimeDAO.setResourceID(employeeId); blockouttimeDAO.deleteAll(); blockOutTimes = null; }

// Remove the resource from the persistent store ResourceDAO resourceDAO = new ResourceDAO(employeeId); resourceDAO.delete(); } catch(ResourceException ex) { throw new EJBException("Reason:"+...); } catch(BlockOutTimeException ex) { throw new EJBException("Reason:"+...); } catch(Exception exception) { ... } } public void setEntityContext(EntityContext context) { this.context = context; }

public void unsetEntityContext() { context = null; }

public void ejbActivate() { employeeId = (String)context.getPrimaryKey(); }

public void ejbPassivate() { employeeId = null; }

public void ejbLoad() { try { // load the resource info from ResourceDAO resourceDAO = getResourceDAO(); setResourceData((ResourceTO) resourceDAO.load(employeeId));

// Load other dependent objects, if necessary ... } catch(Exception ex) { throw new EJBException("Reason:" + ...); } }

public void ejbStore() { try { // Store resource information getResourceDAO().update(getResourceData());

// Store dependent objects as needed ... } catch(SkillSetException ex) { throw new EJBException("Reason:" + ...); } catch(BlockOutTimeException ex) { throw new EJBException("Reason:" + ...); } ... } public void ejbPostCreate(ResourceTO resource) {

}

// Method to Get Resource Transfer Object public ResourceTO getResourceTO() { // create a new Resource Transfer Object ResourceTO resourceTO = new ResourceTO(employeeId);

// copy all values resourceTO.lastName = lastName; resourceTO.firstName = firstName; resourceTO.departmentId = departmentId; ... return resourceTO; }

public void setResourceData(ResourceTO resourceTO) { // copy values from Transfer Object into entity bean employeeId = resourceTO.employeeId; lastName = resourceTO.lastName; ... }

// Method to get dependent Transfer Objects public Collection getSkillSetsData() { // If skillSets is not loaded, load it first. // See Lazy Load strategy implementation.

return skillSets;

} ...

// other get and set methods as needed ...

// Entity bean business methods public void addBlockOutTimes(Collection moreBOTs) throws BlockOutTimeException { // Note: moreBOTs is a collection of // BlockOutTimeTO objects try { Iterator moreIter = moreBOTs.iterator(); while(moreIter.hasNext()) { BlockOutTimeTO botTO = (BlockOutTimeTO) moreIter.next(); if (! (blockOutTimeExists(botTO))) { // add BlockOutTimeTO to collection botTO.setNew(); blockOutTime.add(botTO); } else { // BlockOutTimeTO already exists, cannot add throw new BlockOutTimeException(...); } } } catch(Exception exception) { throw new EJBException(...); } }

public void addSkillSet(Collection moreSkills) throws SkillSetException { // similar to addBlockOutTime() implementation ... }

...

public void updateBlockOutTime(Collection updBOTs) throws BlockOutTimeException { try { Iterator botIter = blockOutTimes.iterator(); Iterator updIter = updBOTs.iterator(); while (updIter.hasNext()) { BlockOutTimeTO botTO = (BlockOutTimeTO) updIter.next(); while (botIter.hasNext()) { BlockOutTimeTO existingBOT = (BlockOutTimeTO) botIter.next(); // compare key values to locate BlockOutTime if (existingBOT.equals(botTO)) { // Found BlockOutTime in collection // replace old BlockOutTimeTO with new one botTO.setDirty(); //modified old dependent botTO.resetNew(); //not a new dependent existingBOT = botTO; } }

} } catch (Exception exc) { throw new EJBException(...); } }

public void updateSkillSet(Collection updSkills) throws CommitmentException { // similar to updateBlockOutTime... ... }

...

} 2.9.

Patrón Decorator

2.9.1. Definición 

El patrón Decorator responde a la necesidad de añadir dinámicamente funcionalidad a un Objeto. Esto nos permite no tener que crear sucesivas clases que hereden de la primera incorporando la nueva funcionalidad, sino otras que la implementan y se asocian a la primera.

2.9.2. Esquema 

Componente Define la interfaz para los objetos que pueden tener responsabilidades añadidas.



Componente Concreto Define un objeto al cual se le puede agregar responsabilidades adicionales.



Decorador

Mantiene una referencia al componente asociado. Implementa la interfaz de la superclase Componente delegando en el componente asociado. 

Decorador Concreto

Añade responsabilidades al componente.

2.9.3. Ejemplo General public abstract class Componente{ abstract public void operacion(); } public class ComponenteConcreto extends Componente{ public void operacion(){ System.out.println("ComponenteConcreto.operacion()"); } } public abstract class Decorador extends Componente{ private Componente _componente; public Decorador(Componente componente){ _componente = componente; } public void operacion(){ _componente.operacion(); } }

public class DecoradorConcretoA extends Decorador{ private String _propiedadAñadida;

public DecoradorConcretoA(Componente componente){ super(componente);

}

public void operacion(){ super.operacion(); _propiedadAñadida = "Nueva propiedad"; System.out.println("DecoradorConcretoA.operacion()"); } } public class DecoradorConcretoB extends Decorador{ public DecoradorConcretoB(Componente componente){ super(componente); } public void operacion(){ super.operacion(); comportamientoAñadido(); System.out.println("DecoradorConcretoB.operacion()"); } public void comportamientoAñadido(){ System.out.println("Comportamiento B añadido"); } } public class Cliente{ public static void main(String[] args){ ComponenteConcreto c = new ComponenteConcreto(); DecoradorConcretoA d1 = new DecoradorConcretoA(c); DecoradorConcretoB d2 = new DecoradorConcretoB(d1); d2.operacion(); } }

2.9.4. Ejemplo con acceso a BD

2.10. Patrón Facade

2.10.1.

Definición

Los patrones de diseño dan una solución probada y documentada a problemas de desarrollo de software que aparecen en un contexto similar. El patrón de diseño Fachada (Facade) es un tipo de patrón estructural.

2.10.2. 

Esquema

Fachada (Facade)

Conoce qué clases del subsistema son responsables de una determinada petición, y delega esas peticiones de los clientes a los objetos apropiados del subsistema. 

Subclases (ModuleA, ModuleB, ModuleC...):

Implementan la funcionalidad del subsistema. Realizan el trabajo solicitado por la fachada. No conocen la existencia de la fachada.

2.10.3.

Ejemplo General

package com.genbetadev; public class Impresora { private String tipoDocumento; private String hoja; private boolean color; private String texto; public String getTipoDocumento() { return tipoDocumento; } public void setTipoDocumento(String tipoDocumento) {

this.tipoDocumento = tipoDocumento; } public String getHoja() { return hoja; } public void setHoja(String hoja) { this.hoja = hoja; } } Se trata de una clase sencilla que imprime documentos en uno u otro formato. El código de la clase cliente nos ayudará a entender mejor su funcionamiento.

package com.genbetadev; public class PrincipalCliente { public static void main(String[] args) { Impresora i = new Impresora(); i.setHoja("a4"); i.setColor(true); i.setTipoDocumento("pdf"); i.setTexto("texto 1"); i.imprimirDocumento(); Impresora i2 = new Impresora(); i2.setHoja("a4"); i2.setColor(true); i2.setTipoDocumento("pdf"); i2.setTexto("texto 2"); i2.imprimirDocumento(); Impresora i3 = new Impresora(); i3.setHoja("a3"); i3.setColor(false);

i3.setTipoDocumento("excel"); i3.setTexto("texto 3"); i3.imprimirDocumento();}} Como podemos ver la clase cliente se encarga de invocar a la impresora, y configurarla para después imprimir varios documentos .Ahora bien prácticamente todos los documentos que escribimos tienen la misma estructura (formato A4, Color , PDF). Estamos continuamente repitiendo código. Vamos a construir una nueva clase FachadaImpresoraNormal que simplifique la impresión de documentos que sean los más habituales.

package com.genbetadev; public class FachadaImpresoraNormal { Impresora impresora; public FachadaImpresoraNormal(String texto) { super(); impresora= new Impresora(); impresora.setColor(true); impresora.setHoja("A4"); impresora.setTipoDocumento("PDF"); impresora.setTexto(texto); } public void imprimir() { impresora.imprimirDocumento();} } De esta forma el cliente quedará mucho más sencillo : package com.genbetadev; public class PrincipalCliente2 { public static void main(String[] args) { FachadaImpresoraNormal fachada1= new FachadaImpresoraNormal("texto1"); fachada1.imprimir(); FachadaImpresoraNormal fachada2= new FachadaImpresoraNormal("texto2");

fachada2.imprimir(); Impresora i3 = new Impresora(); i3.setHoja("a4"); i3.setColor(true); i3.setTipoDocumento("excel"); i3.setTexto("texto 3"); i3.imprimirDocumento();} }

2.10.4. Ejemplo con acceso a BD package com.genbetadev; public class Impresora { private String tipoDocumento; private String hoja; private boolean color; private String texto; public String getTipoDocumento() { return tipoDocumento; } public void setTipoDocumento(String tipoDocumento) { this.tipoDocumento = tipoDocumento;} public String getHoja() { return hoja; } public void setHoja(String hoja) { this.hoja = hoja;} } Se trata de una clase sencilla que imprime documentos en uno u otro formato. El código de la clase cliente nos ayudará a entender mejor su funcionamiento.

package com.genbetadev; public class PrincipalCliente { public static void main(String[] args) { Impresora i = new Impresora();

i.setHoja("a4"); i.setColor(true); i.setTipoDocumento("pdf"); i.setTexto("texto 1"); i.imprimirDocumento(); Impresora i2 = new Impresora(); i2.setHoja("a4"); i2.setColor(true); i2.setTipoDocumento("pdf"); i2.setTexto("texto 2"); i2.imprimirDocumento(); Impresora i3 = new Impresora(); i3.setHoja("a3"); i3.setColor(false); i3.setTipoDocumento("excel"); i3.setTexto("texto 3"); i3.imprimirDocumento();} } Como podemos ver la clase cliente se encarga de invocar a la impresora, y configurarla para después imprimir varios documentos .Ahora bien prácticamente todos los documentos que escribimos tienen la misma estructura (formato A4, Color , PDF). Estamos continuamente repitiendo código. Vamos a construir una nueva clase FachadaImpresoraNormal que simplifique la impresión de documentos que sean los más habituales. package com.genbetadev; public class FachadaImpresoraNormal { Impresora impresora; public FachadaImpresoraNormal(String texto) { super(); impresora= new Impresora(); impresora.setColor(true); impresora.setHoja("A4");

impresora.setTipoDocumento("PDF"); impresora.setTexto(texto); } public void imprimir() { impresora.imprimirDocumento();}} De esta forma el cliente quedará mucho más sencillo : package com.genbetadev; public class PrincipalCliente2 { public static void main(String[] args) { FachadaImpresoraNormal fachada1= new FachadaImpresoraNormal("texto1"); fachada1.imprimir(); FachadaImpresoraNormal fachada2= new FachadaImpresoraNormal("texto2"); fachada2.imprimir(); Impresora i3 = new Impresora(); i3.setHoja("a4"); i3.setColor(true); i3.setTipoDocumento("excel"); i3.setTexto("texto 3"); i3.imprimirDocumento(); }package com.ezjamvc.modelo.facade; import com.ezjamvc.modelo.dao.ArticuloDAO; import com.ezjamvc.modelo.dto.ArticuloDTO; import java.sql.Connection; import java.sql.SQLException; import java.util.List; /** @author asuncion */ public class ArticuloFacade { private Connection cnn; private ArticuloDAO dao;

}

public ArticuloFacade(Connection cnn) { this.cnn = cnn; dao = new ArticuloDAO(); } public void crear(ArticuloDTO dto) throws SQLException { dao.create(dto, cnn); } public List listar() throws SQLException { return dao.loadAll(cnn); } public ArticuloDTO leer(ArticuloDTO dto)throws SQLException { return dao.load(dto, cnn); } public void actualiza(ArticuloDTO dto)throws SQLException { dao.update(dto, cnn); } public void elimina(ArticuloDTO dto)throws SQLException { dao.delete(dto, cnn); } }

package com.ezjamvc.modelo.delegate; import com.ezjamvc.modelo.dto.ArticuloDTO; import com.ezjamvc.modelo.facade.ArticuloFacade; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.List;

/** * * @author asuncion */ public class EzjaMVCDelegate { private Connection cnn; private ArticuloFacade artFacade; public EzjaMVCDelegate() { String user = "root"; String pwd = "admin"; String url = "jdbc:mysql://localhost:3306/EzjaMVC"; String mySqlDriver = "com.mysql.jdbc.Driver"; try { Class.forName(mySqlDriver); cnn = DriverManager.getConnection(url, user, pwd); } catch (Exception e) { e.printStackTrace(); } artFacade = new ArticuloFacade(cnn); } //Codigo para los Articulos public void crearArticulo(ArticuloDTO dto) throws SQLException { artFacade.crear(dto); } public List listarArticulos() throws SQLException { return artFacade.listar(); } public ArticuloDTO leerArticulo(ArticuloDTO dto) throws SQLException { return artFacade.leer(dto);

} public void actualiza(ArticuloDTO dto) throws SQLException { artFacade.actualiza(dto); } public void elimina(ArticuloDTO dto) throws SQLException { artFacade.elimina(dto); } } Al revisar el codigo, nos podemos dar cuenta que al momento de definir el constructor, se obtiene una conexion a la base de datos, la cual se pasa como parametro a la instancia de ArticuloFacade que se crea. Cabe mencionar que la estructura final de la aplicacion es la siguiente.

Ahora modificaremos ligeramente la clase de prueba para verificar el funcionamiento de la aplicacion. package com.ezjamvc.vista.formularios; import com.ezjamvc.modelo.delegate.EzjaMVCDelegate; import com.ezjamvc.modelo.dto.ArticuloDTO; import java.sql.SQLException; import java.util.logging.Level; import java.util.logging.Logger; /** @author asuncion */ public class PruebaDelegate { public PruebaDelegate() { } public static void main(String[] args) { EzjaMVCDelegate del = new EzjaMVCDelegate(); ArticuloDTO dto = new ArticuloDTO(); try { System.out.println(del.listarArticulos()); } catch (SQLException ex) {

Logger.getLogger(PruebaDelegate.class.getName()).log(Level.SEVERE, null, ex); } }} Y con esto terminamos con todas las capas del modelo de la aplicacion propuesta. En la cual se aplican los 4 patrones antes mencionados: 1. 2. 3. 4.

Data Transfers object Data Access object Session Facade Bussines Delegate

2.11. Patrón Chain of Responsability

2.11.1. 

Definición

El patrón de diseño Chain of Responsibility es un patrón de comportamiento que evita acoplar el emisor de una petición a su receptor dando a más de un objeto la posibilidad de responder a una petición. Para ello, se encadenan los receptores y pasa la petición a través de la cadena hasta que es procesada por algún objeto. Este patrón es utilizado a menudo en el contexto de las interfaces gráficas de usuario donde un objeto puede estar compuesto de varios objetos (que generalmente heradán de una super clase "vista"). No se debe confundir con el patrón Composite (patrón de diseño) que se basa en un concepto similar.

2.11.2. 

Esquema

Manejador Define una interfaz para tratar las peticiones. Opcionalmente, implementa el enlace al sucesor.



ManejadorConcreto

Trata las peticiones de las que es responsable; si el ManejadorConcreto puede manejar la petición, lo hace; en caso contrario la reenvía a su sucesor. 

Cliente Inicializa la petición a un Manejador Concreto de la cadena.

2.11.3.

Ejemplo General

public class Cliente { public static void main(String argv[]) { Unidad smith = new Coronel("Smith", null); Unidad truman = new Coronel("Truman", "Tomar posición enemiga"); Unidad ryan = new Soldado("Ryan"); Unidad rambo = new Soldado("Rambo");

System.out.println(rambo.orden());

// rambo ->

rambo.establecerMando(truman); System.out.println(rambo.orden());

// rambo -> truman

ryan.establecerMando(rambo); System.out.println(ryan.orden()); }} public abstract class Unidad { public Unidad(String nombre) {

// ryan -> rambo -> truman

_mando = null; _nombre = nombre; } public String toString() { return _nombre; } public void establecerMando(Unidad mando) { _mando = mando; } public String orden() { return (_mando != null ? _mando.orden() : "(sin orden)"); } private Unidad _mando; private String _nombre; } public class Coronel extends Unidad { public Coronel(String nombre, String orden) { super(nombre); _orden = orden; } public String orden()

{ return (_orden != null ? _orden : super.orden()); }

public String toString() { return ("Coronel " + super.toString()); } private String _orden; } public class Soldado extends Unidad { public Soldado(String nombre) { super(nombre); } public String toString() { return ("Soldado " + super.toString()); } }

2.11.4.

Ejemplo con acceso a BD

2.12. Patrón Command

2.12.1.

Definición

 Este patrón permite solicitar una operación a un objeto sin conocer realmente el contenido de esta operación, ni el receptor real de la misma. Para ello se encapsula la petición como un objeto, con lo que además facilita la parametrización de los métodos.

2.12.2. 

Esquema

AbstractCommand. Clase que ofrece una interfaz para la ejecución de órdenes. Define los métodos do y undo que se implementarán en cada clase concreta.



ConcreteCommand. Clase que implementa una orden concreta y sus métodos do y undo. Su constructor debe inicializar los parámetros de la orden.



Invoker. Clase que instancia las órdenes, puede a su vez ejecutarlas inmediatamente (llamando a do) o dejar que el CommandManager lo haga.



CommandManager. Responsable de gestionar una colección de objetos orden creadas por el Invoker. llamará a los métodos do y unDo. Gestionará su secuenciación y reordenación (sobre la base de prioridades por ejemplo).

2.12.3. Ejemplo General /* The Invoker class */ public class Switch { private Command flipUpCommand; private Command flipDownCommand; public Switch(Command flipUpCmd, Command flipDownCmd) { this.flipUpCommand = flipUpCmd; this.flipDownCommand = flipDownCmd; } public void flipUp() { flipUpCommand.execute();} public void flipDown() { flipDownCommand.execute(); }} public class Light { public Light() { } public void turnOn() { System.out.println("The light is on");

}

public void turnOff() { System.out.println("The light is off"); }} public interface Command { void execute(); } public class FlipUpCommand implements Command { private Light theLight; public FlipUpCommand(Light light) { this.theLight=light; } public void execute(){ theLight.turnOn();}}

public class FlipDownCommand implements Command { private Light theLight; public FlipDownCommand(Light light) { this.theLight=light; } public void execute() { theLight.turnOff(); }}

public class PressSwitch { public static void main(String[] args){ if (args.length != 1) { System.err.println("Argument \"ON\" or \"OFF\" is required."); System.exit(-1); } Light lamp = new Light(); Command switchUp = new FlipUpCommand(lamp); Command switchDown = new FlipDownCommand(lamp); Switch mySwitch = new Switch(switchUp, switchDown);

switch (args[0]) { case "ON": mySwitch.flipUp(); break; case "OFF": mySwitch.flipDown(); break; default: System.err.println("Argument \"ON\" or \"OFF\" is required."); System.exit(-1); } } }

2.12.4.

Ejemplo con acceso a BD

using Oracle.DataAccess.Client; public class OracleDatabase : Database { public override IDbConnection CreateConnection() { return new OracleConnection(connectionString); } public override IDbCommand CreateCommand() { return new OracleCommand(); } public override IDbConnection CreateOpenConnection() {

OracleConnection connection = (OracleConnection)CreateConnection(); connection.Open(); return connection; } public override IDbCommand CreateCommand(string commandText, IDbConnection connection) { OracleCommand command = (OracleCommand)CreateCommand(); command.CommandText = commandText; command.Connection = (OracleConnection)connection; command.CommandType = CommandType.Text; return command; } public override IDbCommand CreateStoredProcCommand(string procName, IDbConnection connection) { OracleCommand command = (OracleCommand)CreateCommand(); command.CommandText = procName; command.Connection = (OracleConnection)connection; command.CommandType = CommandType.StoredProcedure; return command; } public override IDataParameter CreateParameter(string parameterName, object parameterValue) { return new OracleParameter(parameterName, parameterValue); } }

2.13. Patrón Observer

2.13.1.



Definición

Este patrón también se conoce como el patrón de publicacióninscripción o modelo-patrón. Estos nombres sugieren las ideas básicas del patrón, que son: el objeto de datos, que se le puede llamar Sujeto a partir de ahora, contiene atributos mediante los cuales cualquier objeto Observador o vista se puede suscribir a él pasándole una referencia a sí mismo. El Sujeto mantiene así una lista de las referencias a sus observadores. Los observadores a su vez están obligados a implementar unos métodos determinados mediante los cuales el Sujeto es capaz de notificar a sus observadores suscritos los cambios que sufre para que todos ellos tengan la oportunidad de refrescar el contenido representado. De manera que cuando se produce un cambio en el Sujeto, ejecutado, por ejemplo, por alguno de los observadores, el objeto de datos puede recorrer la lista de observadores avisando a cada uno. Este patrón suele observarse en los frameworks de interfaces gráficas orientados a objetos, en los que la forma de capturar los eventos es suscribir listeners a los objetos que pueden disparar eventos.

2.13.2. 

Esquema Sujeto (Subject): El sujeto proporciona una interfaz para agregar (attach) y eliminar (detach) observadores. El Sujeto conoce a todos sus observadores.



Observador (Observer):

Define el método que usa el sujeto para notificar cambios en su estado (update/notify). 

Sujeto Concreto (ConcreteSubject): Mantiene el estado de interés para los observadores concretos y los notifica cuando cambia su estado. No tienen por qué ser elementos de la misma jerarquía.



Observador Concreto (ConcreteObserver): Mantiene una referencia al sujeto concreto e implementa la interfaz de actualización, es decir, guardan la referencia del objeto que observan, así en caso de ser notificados de algún cambio, pueden preguntar sobre este cambio.

2.13.3. import java.util.*;

Ejemplo General

class FuenteEvento extends Observable implements Runnable { public void run() { while (true) { String respuesta = new Scanner(System.in).next(); setChanged(); notifyObservers(respuesta); } } } import java.util.Observable; import static java.lang.System.out;

class MiApp { public static void main(String[] args) { out.println("Introducir Texto >"); FuenteEvento fuenteEvento = new FuenteEvento();

fuenteEvento.addObserver( (Observable obj, Object arg) -> { out.println("\nRespuesta recibida: " + arg); });

new Thread(fuenteEvento).start(); } }

2.13.4. import java.util.*;

Ejemplo con acceso a BD

public class Database implements Subject { private Vector observers;

public Database() { observers = new Vector(); } . } Cuando se utiliza un vector, hacer el seguimiento de los observadores es simple. Cuando un observador quiere dar de alta, se llama al método registerObserver del sujeto, haciéndose pasar como un objeto. El sujeto objeto de nuestra clase de base de datos - sólo tiene que añadir que el vector de observador a los observadores en el método registerObserver, utilizando el método de complemento de la clase Vector. import java.util.*; public class Database implements Subject { private Vector observers; public Database() { observers = new Vector(); } public void registerObserver(Observer o) { observers.add(o); } .

. . }

¿Qué hay de la eliminación de un observador del vector de los observadores? No hay problema. Cuando se quiere eliminar un objeto de un vector, puede utilizar el método de eliminación de los vectores; aquí está la manera que funciona en el método removeObserver de la clase de base de datos: import java.util.*; public class Database implements Subject { private Vector observers;

public Database() { observers = new Vector(); }

public void registerObserver(Observer o) { observers.add(o); }

public void removeObserver(Observer o) { observers.remove(o); } . . .

}

Cuando el usuario realmente hace algo con la base de datos - elimina un registro, por ejemplo - que él llama método editRecord de la clase de base de datos. Por ejemplo, para eliminar el registro 1, es posible llamar a este método como este: database.editRecord(“delete”, “record 1”);

Esto es lo que el método editRecord se ve así: Cuando se llama a este método, se le pasa la operación de base de datos que desea realizar y el registro que desea trabajar, tanto como cadenas en este ejemplo. Esas cadenas se almacenan para que puedan ser transmitidas a los observadores. Una vez guardados los hilos, el método notifyObservers, que viene a continuación, se llama a notificar a todos los observadores. import java.util.*; public class Database implements Subject { private Vector observers; private String operation; private String record;

public Database() { observers = new Vector(); } . . . public void editRecord(String operation, String record) { this.operation = operation; this.record = record;

notifyObservers(); } }

Aquí está la carne del código, la parte que notifica a cada observador que ha habido un cambio: el método notifyObservers. Cada observador implementa la interfaz de este ejemplo Observador - que significa que tiene un método de actualización - por lo notifyObservers acaba de bucle para todos los observadores registrados en el vector de observadores, llamando al método de actualización de cada uno con la operación de base de datos y registro afectado. import java.util.*; public class Database implements Subject { private Vector observers; private String operation; private String record; . . . public void notifyObservers() { for (int loopIndex = 0; loopIndex < observers.size(); loopIndex++) { Observer observer = (Observer)observers.get(loopIndex); observer.update(operation, record); } }

public void editRecord(String operation, String record) { this.operation = operation;

this.record = record; notifyObservers(); } }

Eso es todo lo que necesita para Database.java, el tema en este ejemplo. El tema le permitirá observadores registran a sí mismos, anular el registro de sí mismos, y recibir una notificación cuando un registro de base de datos ha sido editado (que lo hace con el método editRecord de la clase de base de datos). Todo lo que queda por hacer para conseguir este espectáculo en el camino es la creación de los propios observadores. 2.14. Patrón Mediator

2.14.1. 

2.14.2. 

Definición

El patrón mediador define un objeto que encapsula cómo un conjunto de objetos interactúan. Este patrón de diseño está considerado como un patrón de comportamiento debido al hecho de que puede alterar el comportamiento del programa en ejecución.

Esquema

Mediador - define la interfaz para la comunicación entre objetos amigos



MediadorConcreto - implementa la interfaz Mediador y coordina la comunicación entre objetos amigos. Es consciente de todos los amigos y su propósito en lo que concierne a la comunicación entre ellos.



AmigoConcreto - se comunica con otros amigos a través de su Mediador

2.14.3. Ejemplo General import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; //Interfaz Amigo interface Command { void execute(); }

//Mediador Abstracto interface IMediator { void book(); void view(); void search(); void registerView(BtnView v); void registerSearch(BtnSearch s); void registerBook(BtnBook b); void registerDisplay(LblDisplay d); }

//Mediador Concreto class Mediator implements IMediator {

BtnView btnView; BtnSearch btnSearch; BtnBook btnBook; LblDisplay show;

//.... void registerView(BtnView v) { btnView = v; }

void registerSearch(BtnSearch s) { btnSearch = s; }

void registerBook(BtnBook b) { btnBook = b; }

void registerDisplay(LblDisplay d) { show = d; }

void book() { btnBook.setEnabled(false); btnView.setEnabled(true); btnSearch.setEnabled(true); show.setText("booking..."); }

void view() { btnView.setEnabled(false); btnSearch.setEnabled(true); btnBook.setEnabled(true); show.setText("viewing..."); }

void search() { btnSearch.setEnabled(false); btnView.setEnabled(true); btnBook.setEnabled(true); show.setText("searching..."); }

}

//Un amigo concreto class BtnView extends JButton implements Command {

IMediator med;

BtnView(ActionListener al, IMediator m) { super("View"); addActionListener(al); med = m; med.registerView(this); }

public void execute() { med.view(); }

}

//Un amigo concreto class BtnSearch extends JButton implements Command {

IMediator med;

BtnSearch(ActionListener al, IMediator m) { super("Search"); addActionListener(al); med = m; med.registerSearch(this); }

public void execute() { med.search(); }

}

//Un amigo concreto class BtnBook extends JButton implements Command {

IMediator med;

BtnBook(ActionListener al, IMediator m) { super("Book"); addActionListener(al); med = m; med.registerBook(this); }

public void execute() { med.book(); }

}

class LblDisplay extends JLabel {

IMediator med;

LblDisplay(IMediator m) { super("Just start..."); med = m; med.registerDisplay(this); setFont(new Font("Arial", Font.BOLD, 24)); }

}

class MediatorDemo extends JFrame implements ActionListener {

IMediator med = new Mediator();

MediatorDemo() { JPanel p = new JPanel(); p.add(new BtnView(this, med)); p.add(new BtnBook(this, med)); p.add(new BtnSearch(this, med)); getContentPane().add(new LblDisplay(med), "North"); getContentPane().add(p, "South"); setSize(400, 200); setVisible(true); }

public void actionPerformed(ActionEvent ae) { Command comd = (Command) ae.getSource(); comd.execute(); }

public static void main(String[] args) { new MediatorDemo(); }

}

2.14.4.

Ejemplo con acceso a BD

Utilizando el patrón de diseño de la fachada que le "ocultar" todas las relaciones de la funcionalidad existente detrás de un solo "interfaz" el que expone la fachada. Client code:

Logger logger = new Logger(); logger.initLogger("someLogger"); logger.debug("message"); La aplicación puede implicar la interacción de muchos objetos. Pero al final, la funcionalidad ya existe. Probablemente, el método "debug" se implementa como sigue: Implementation:

class Logger {

private LoggerImpl internalLogger; private LoggerManager manager;

public void initLogger( String loggerName ) { this.internalLogger = manager.getLogger( loggerName ); }

public void debug( String message ) { this.internalLogger.debug( message ); } } La funcionalidad ya existe. La fachada sólo se esconde. En este caso hipotético, la LoggerManager controla la creación del registrador correcta, y la LoggerImpl es un objeto privado paquete que tiene el método de "debug". De esta manera la fachada no es la adición de la funcionalidad que se acaba de delegar en algunos de los objetos existentes. En el otro lado el mediador agregar la nueva funcionalidad mediante la combinación de diferentes objetos. Same Client code:

Logger logger = new Logger();

logger.initLogger("someLogger"); logger.debug("message"); Implementation:

class Logger {

private java.io.PrintStream out; private java.net.Socket client; private java.sql.Connection dbConnection; private String loggerName;

public void initLogger( String loggerName ) { this.loggerName = loggerName; if ( loggerName == "someLogger" ) { out = new PrintStream( new File("app.log")); } else if ( loggerName == "serverLog" ) { client = new Socket("127.0.0.1", 1234 ); } else if( loggerName == "dblog") { dbConnection = Class.forName()... . }

}

public void debug( String message ) {

if ( loggerName == "someLogger" ) { out.println( message ); } else if ( loggerName == "serverLog" ) {

ObjectOutputStrewam oos = new ObjectOutputStrewam( client.getOutputStream()); oos.writeObject( message ); } else if( loggerName == "dblog") { Pstmt pstmt = dbConnection.prepareStatment( LOG_SQL ); pstmt.setParameter(1, message ); pstmt.executeUpdate(); dbConnection.commit(); } } } En este código, el mediador es el que contiene la lógica de negocio para crear el "canal" para iniciar una sesión y también para hacer el registro en ese canal. Él es "crear" la funcionalidad. Por supuesto, hay mejores formas de implementar esto usando polimorfismo, pero el punto aquí es mostrar cómo el mediador "agrega" nueva funcionalidad mediante la combinación de la funcionalidad existente (en mi muestra no mostró mucho lo siento), pero se imaginan el mediador, leen a partir de la base de datos del host remoto para iniciar la sesión en donde, a continuación, crea un cliente y finalmente escribir en la impresora del cliente que transmitir los mensajes de registro. De esta manera el mediador sería "mediar" entre los diferentes objetos. Por último, la fachada es un patrón estructural, es decir que describe la composición de los objetos, mientras que el mediador es un comportamiento, es decir, que describe la forma interactúan los objetos. 2.15. Patrón Template Method

2.15.1. 

Definición

En ingeniería de software, el patrón de método de la plantilla es un patrón de diseño de comportamiento que define el esqueleto de programa de un algoritmo en un método, llamado método de plantilla, el cual difiere algunos pasos a las subclases. Permite redefinir ciertos pasos seguros de un algoritmo sin cambiar la estructura del algoritmo.

2.15.2.

Esquema

2.15.3.

Ejemplo General

abstract class Game { /* Hook methods. Concrete implementation may differ in each subclass*/ protected int playersCount; abstract void initializeGame(); abstract void makePlay(int player); abstract boolean endOfGame(); abstract void printWinner();

/* A template method : */ public final void playOneGame(int playersCount) { this.playersCount = playersCount; initializeGame(); int j = 0;

while (!endOfGame()) { makePlay(j); j = (j + 1) % playersCount; } printWinner(); } }

//Now we can extend this class in order //to implement actual games:

class Monopoly extends Game {

/* Implementation of necessary concrete methods */ void initializeGame() { // Initialize players // Initialize money } void makePlay(int player) { // Process one turn of player } boolean endOfGame() { // Return true if game is over // according to Monopoly rules } void printWinner() { // Display who won } /* Specific declarations for the Monopoly game. */

// ... }

class Chess extends Game {

/* Implementation of necessary concrete methods */ void initializeGame() { // Initialize players // Put the pieces on the board } void makePlay(int player) { // Process a turn for the player } boolean endOfGame() { // Return true if in Checkmate or // Stalemate has been reached } void printWinner() { // Display the winning player } /* Specific declarations for the chess game. */

// ... }

2.15.4. DataParser.java

Ejemplo con acceso a BD

package org.arpit.javapostsforlearning; abstract public class DataParser { //Template method //This method defines a generic structure for parsing data public void parseDataAndGenerateOutput() { readData(); processData(); writeData(); } //This methods will be implemented by its subclass abstract void readData(); abstract void processData(); //We have to write output in a CSV file so this step will be same for all subclasses public void writeData() { System.out.println('Output generated,writing to CSV'); }} In below class,CSV specific steps are implement in this class

CSVDataParser.java package org.arpit.javapostsforlearning; public class CSVDataParser extends DataParser { void readData() { System.out.println('Reading data from csv file'); } void processData() { System.out.println('Looping through loaded csv file');

}}

In below class,database specific steps are implement in this class

DatabaseDataParser.java package org.arpit.javapostsforlearning;

public class DatabaseDataParser extends DataParser { void readData() { System.out.println('Reading data from database'); } void processData() { System.out.println('Looping through datasets');

}}

TemplateMethodMain.java package org.arpit.javapostsforlearning; public class TemplateMethodMain { public static void main(String[] args) { CSVDataParser csvDataParser=new CSVDataParser(); csvDataParser.parseDataAndGenerateOutput(); System.out.println('**********************'); DatabaseDataParser databaseDataParser=new DatabaseDataParser(); databaseDataParser.parseDataAndGenerateOutput(); } } output: Reading data from csv file Looping through loaded csv file Output generated,writing to CSV ********************** Reading data from database Looping through datasets Output generated,writing to CSV

2.16. Patrón Visitor

2.16.1.

Definición

 La idea básica es que se tiene un conjunto de clases elemento que conforman la estructura de un objeto. Cada una de estas clases elemento tiene un método aceptar (accept()) que recibe al objeto visitante (visitor) como argumento. El visitante es una interfaz que tiene un método visit diferente para cada clase elemento; por tanto habrá implementaciones de la interfaz visitor de la forma: visitorClase1, visitorClase2... visitorClaseN. El método accept de una clase elemento llama al método visit de su clase. Clases concretas de un visitante pueden entonces ser escritas para hacer una operación en particular.

2.16.2. 

Esquema

Visitante (Visitor) Declara una operación de visita para cada elemento concreto en la estructura de objetos, que incluye el propio objeto visitado



Visitante Concreto (ConcreteVisitor1/2) Implementa las operaciones del visitante y acumula resultados como estado local



Elemento (Element) Define una operación “Accept” que toma un visitante como argumento



Elemento Concreto (ConcreteElementA/B):

Implementa la operación “Accept”

2.16.3. Ejemplo General package expresion; public abstract class Expresion { abstract public void aceptar(VisitanteExpresion v); }

package expresion; public class Constante extends Expresion { public Constante(int valor) { _valor = valor; } public void aceptar(VisitanteExpresion v) { v.visitarConstante(this); } int _valor; }

package expresion;

public class Variable extends Expresion { public Variable(String variable) { _variable = variable; } public void aceptar(VisitanteExpresion v) { v.visitarVariable(this); } String _variable; }

package expresion; public abstract class OpBinaria extends Expresion { public OpBinaria(Expresion izq, Expresion der) { _izq = izq; _der = der; } Expresion _izq, _der; } package expresion; public class Suma extends OpBinaria { public Suma(Expresion izq, Expresion der) { super(izq, der); } public void aceptar(VisitanteExpresion v) { v.visitarSuma(this); } }

package expresion; public class Mult extends OpBinaria { public Mult(Expresion izq, Expresion der) { super(izq, der); } public void aceptar(VisitanteExpresion v) { v.visitarMult(this); } }

/* * Esta es la clase abstracta que define la interfaz de los visitantes * de la jerarquía Expresion -- en realidad, utilizamos una interfaz Java * dado que todos los métodos son abstractos. */

package expresion; public interface VisitanteExpresion { public void visitarSuma(Suma s); public void visitarMult(Mult m); public void visitarVariable(Variable v); public void visitarConstante(Constante c); }

/** * Uno de los posibles visitantes de las Expresiones es un pretty printer * que convierte a cadena de caracteres la expresión aritmética. El algoritmo * usado no optimiza el uso de paréntesis... El resultado se acumula en * el atributo privado _resultado, pudiéndose acceder a éste desde el exterior * mediante el método obtenerResultado() */

package expresion; public class PrettyPrinterExpresion implements VisitanteExpresion {

// visitar la variable en este caso es guardar en el resultado la variable // asociada al objeto... Observe que accedemos al estado interno del objeto // confiando en la visibilidad de paquete...

public void visitarVariable(Variable v) { _resultado = v._variable; }

public void visitarConstante(Constante c) { _resultado = String.valueOf(c._valor);

}

// Dado que el pretty-printer de una operación binaria es casi idéntica, // puedo factorizar parte del código con este método privado...

private void visitarOpBinaria(OpBinaria op, String pOperacion) { op._izq.aceptar(this); String pIzq = obtenerResultado();

op._der.aceptar(this); String pDer = obtenerResultado();

_resultado = "(" + pIzq + pOperacion + pDer + ")"; }

// Por último la visita de la suma y la mult se resuelve mediante el método // privado que se acaba de mencionar...

public void visitarSuma(Suma s) { visitarOpBinaria(s, "+"); } public void visitarMult(Mult m) { visitarOpBinaria(m, "*"); }

// El resultado se almacena en un String privado. Se proporciona un método // de acceso público para que los clientes del visitante puedan acceder // al resultado de la visita

public String obtenerResultado() { return _resultado; } private String _resultado; }

import expresion.*; class Main { static public void main(String argv[]) { // Construcción de una expresión (a+5)*(b+1) Expresion expresion = new Mult( new Suma( new Variable("a"), new Constante(5) ), new Suma( new Variable("b"), new Constante(1) ));

// Pretty-printing... PrettyPrinterExpresion pretty = new PrettyPrinterExpresion(); expresion.aceptar(pretty);

// Visualizacion de resultados System.out.println("Resultado: " + pretty.obtenerResultado()); } }

2.16.4.

Ejemplo con acceso a BD

Así que por la intuición, el paso 1 sería la siguiente interface QueryPart { String getSQL();

// Que las variables se unen a un preparado QueryPart // Declaración, dada la siguiente índice se unen, volviendo // El último índice se unen int bind(PreparedStatement statement, int nextIndex); } Con esta API, podemos fácilmente resumen de una consulta SQL y delegar las responsabilidades a los objetos de nivel inferior. Un BetweenCondition por ejemplo. Se ocupa de ordenar correctamente las partes de un [campo] Entre [baja] condición AND [superior], lo que hace SQL sintácticamente correcta, piezas de delegación de las tareas a sus niños-QueryParts: class BetweenCondition { Field field; Field lower; Field upper;

public String getSQL() { return field.getSQL() + " between " + lower.getSQL() + " and " + upper.getSQL(); }

public int bind(PreparedStatement statement, int nextIndex) { int result = nextIndex;

result = field.bind(statement, result); result = lower.bind(statement, result); result = upper.bind(statement, result);

return result; }

} Mientras que bindValue por el contrario, sería principalmente cuidar de enlace variable

class BindValue { Object value; public String getSQL() { return "?"; } public int bind(PreparedStatement statement, int nextIndex) { statement.setObject(nextIndex, value); return nextIndex + 1; } } En combinación, ahora podemos crear fácilmente las condiciones de esta forma:? ENTRE ? Y?. Cuando se implementan más QueryParts, también podríamos imaginar cosas como MY_TABLE.MY_FIELD ENTRE? Y (SELECT? FROM DUAL), cuando las implementaciones de campo apropiados están disponibles. Eso es lo que hace que el patrón compuesto tan potente, una API común y muchos componentes que encapsulan el comportamiento, delegando partes del comportamiento de los subcomponentes. Paso 2 se ocupa de la evolución de la API

El patrón de material compuesto que hemos visto hasta ahora es bastante intuitiva, ya la vez muy poderosa. Pero tarde o temprano, necesitaremos más parámetros, como nos damos cuenta que queremos pasar del estado de QueryParts matrices a sus hijos. Por ejemplo, queremos ser capaces de inline algunos valores se unen para algunas cláusulas. Tal vez, algunos dialectos SQL no permiten valores vinculados en la cláusula BETWEEN. Cómo manejar eso con la API actual? Ampliarlo, añadiendo un parámetro "booleano en línea"? ¡No! Esa es una de las razones por las cuales el visitor fue inventado. Para mantener la API de los elementos de estructura compuesta simples (sólo tienen que aplicar "aceptar"). Pero en este caso, mucho mejor que la implementación de un verdadero patrón de visitante es reemplazar los parámetros de un "contexto": interface QueryPart {

void toSQL(RenderContext context); void bind(BindContext context); } interface RenderContext { boolean inline(); RenderContext inline(boolean inline); boolean declareFields(); RenderContext declareFields(boolean declare); boolean declareTables(); RenderContext declareTables(boolean declare); boolean cast(); RenderContext sql(String sql); RenderContext sql(char sql); RenderContext keyword(String keyword); RenderContext literal(String literal); RenderContext sql(QueryPart sql); } Lo mismo ocurre con la BindContext. Como se puede ver, es muy extensible, nuevas propiedades se pueden añadir a esta API, otros medios comunes de prestación de SQL se puede añadir, también. Pero el BetweenCondition no tiene que renunciar a su conocimiento encapsulado acerca de cómo hacer que su SQL, y si las variables se unen se les permite o no. Se va a mantener que el conocimiento de sí mismo: class BetweenCondition { Field field; Field lower; Field upper; public void toSQL(RenderContext context) { context.sql(field).keyword(" between ") .sql(lower).keyword(" and ") .sql(upper);

} public void bind(BindContext context) { context.bind(field).bind(lower).bind(upper); } } class BindValue { Object value;

public void toSQL(RenderContext context) { context.sql("?"); } public void bind(BindContext context) { context.statement().setObject(context.nextIndex(), value); } }

View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF