Informe
Modelado del Análisis de Roger Pressman
Presentación
Modelado del Análisis de Roger Pressman
Ingeniería de Software
miércoles, 22 de julio de 2015
Ejemplo de caso de uso
Considerando lo vital que es la gestión de requisitos y lo populares y útiles que son los casos de uso, nunca esta de más intentar una explicación de la técnica. Así que supongamos a un sistema pequeño que nos sirva de ejemplo: el teléfono de un hogar promedio. En dicho sistema, es de interés realizar llamadas de voz, o en otras palabras, el sistema tiene entre sus objetivos el de permitir al usuario del mismo comunicarse remotamente por medio de conexiones de voz.
Primero hemos de identificar al actor. En este caso es claramente el operador o usuario del teléfono. Tendremos en cuenta siempre que cada actor admite solamente cierto tipo de comunicación, impone sus propios requisitos sobre el sistema y exige una funcionalidad bajo ciertas condiciones. Pero para facilitar las cosas podemos simplemente indicarlo; quizás incluso recurriendo a un formalismo gráfico tomado de UML.
Fig. 1 – Ejemplo sencillo de un Actor
Ahora podemos identificar cual es la actividad precisa que queremos expresar en nuestro modelo. Digamos que estamos hablando de las llamadas de voz. Así que decimos:
“El usuario del teléfono levanta el auricular y marca el número de destino. Al completar la secuencia de dígitos la conexión se realiza. Por medio de tonos particulares el sistema indica el estado de error y de progreso en la conexión”.
El párrafo previo contiene la esencia de la situación de llamar por teléfono. A esta breve descripción le llamamos Caso de Uso por cuanto describe la interacción entre un actor -el usuario- y el sistema -el teléfono- indicando el requisito funcional que se exige al sistema.
Ahora bien, ciertamente es difícil referirse a este caso de uso diciendo cada vez el párrafo completo. Por esto le vamos a poner un nombre y un código de identificación. Digamos que le llamamos llamada de voz y que le colocamos el código CS-0100.
Además es claro que antes de hacer la llamada de voz es necesario que el teléfono este colgado así que podemos pensar en una precondición: el teléfono ha de estar colgado.
Armados con estos datos, podemos construir una tabla que resuma lo que tenemos en nuestro caso de uso:
Código: CS-0100.
Nombre: Llamada de voz.
Actores: Usuario.
Descripción: El usuario del teléfono levanta el auricular y marca el número de destino. Al completar la secuencia de dígitos la conexión se realiza. Por medio de tonos particulares el sistema indica el estado de error y de progreso en la conexión.
Precondición: El teléfono está colgado.
Postcondición: Ninguna.
Nombre: Llamada de voz.
Actores: Usuario.
Descripción: El usuario del teléfono levanta el auricular y marca el número de destino. Al completar la secuencia de dígitos la conexión se realiza. Por medio de tonos particulares el sistema indica el estado de error y de progreso en la conexión.
Precondición: El teléfono está colgado.
Postcondición: Ninguna.
Tabla 1 – Detalle de un sencillo Caso de Uso
Y aún más, podemos construir un atractivo diagrama con el fin de comunicar lo que hemos hecho con los clientes.
Fig. 2 – Sencillo ejemplo de un Modelo de Casos de Uso
Si bien en la representación gráfica no aparecen ni la precondición ni la descripción, si en cambio nos ha permitido indicar que el caso de uso (el ovalo) esta en el alcance del proyecto, al haberlo colocado dentro del rectángulo que define los límites del sistema. Por cierto, a dicho rectángulo lo llamamos sujeto en la especificación de UML.
Si en la revisión con los stakeholders identificamos que el caso de uso CS-0100 (el del ejemplo) merece ser detallado más cuidadosamente, entonces y solo entonces, podemos invertir algo de tiempo en la creación de los flujos de eventos. Observemos aquí, que la línea que une al actor con el caso de uso en el modelo gráfico quiere justamente hacer referencia al flujo de eventos.
Al flujo de eventos lo construimos como una tabla, indicando el número del paso el sujeto que realiza la acción y el paso de información que se hace. En aquellos pasos en que estemos hablando del sistema también vamos a indicar la operación o cálculo que este ha de efectuar con los datos. Por otra parte, el flujo de eventos lo vamos a iniciar, casi sin excepción, con el actor.
Flujo Principal:
Paso 1 – Usuario: Levanta el auricular.
Paso 2 – Sistema: Da el tono de marcado.
Paso 3 – Usuario: Indica el número de teléfono.
Paso 4 – Sistema: Realiza la conexión. Da tono de aviso en tanto se levanta el teléfono del lado contrario de la conexión. Permite la conversación al hacerse efectiva la conexión.
Paso 5 – Usuario: Conversa y al finalizar esta, tranca el teléfono.
Paso 6 – Sistema: Termina la conexión.
Paso 1 – Usuario: Levanta el auricular.
Paso 2 – Sistema: Da el tono de marcado.
Paso 3 – Usuario: Indica el número de teléfono.
Paso 4 – Sistema: Realiza la conexión. Da tono de aviso en tanto se levanta el teléfono del lado contrario de la conexión. Permite la conversación al hacerse efectiva la conexión.
Paso 5 – Usuario: Conversa y al finalizar esta, tranca el teléfono.
Paso 6 – Sistema: Termina la conexión.
Tabla 2 – Flujo principal del Caso de Uso
Claro que no siempre las llamadas de voz se corresponden con este flujo de eventos. Sin embargo en un día feliz, cuando todo sale sin problemas, es de esta manera en que sucede. A lo que ocurre en todos los demás casos los vamos a tratar por medio de los flujos alternativos.
Los flujos alternativos pueden ser vistos como una forma de manejo de errores o bien, como un medio para especificar el comportamiento del sistema en caso de un error. Siendo así el caso, una forma de indicarlo es decir una aserción sobre un paso y la acción a tomar cuando esta se viola. Entre las posibles acciones podríamos tener: terminar el caso de uso, saltar a otro paso, presentar un reporte y continuar, etc.
Digamos entonces para efectos del ejemplo, que queremos especificar el comportamiento del sistema cuando el operador a indicado un número inexistente. El siguiente flujo alternativo trata esa situación:
Flujo alternativo: Número incorrecto
Paso 3 – El sistema: Presenta tono de error y el caso de uso termina.
Paso 3 – El sistema: Presenta tono de error y el caso de uso termina.
Tabla 3 – Flujo alternativo del Caso de Uso
Aceptando que claramente es posible indicar tantos flujos alternativos como sean necesarios, además de especificar post condiciones y requisitos no-funcionales relacionados, tenemos entonces un caso de uso sencillo modelado quizás más completamente que lo que en realidad vale la pena. Pongamos todo junto:
Código: CS-0100.
Nombre: Llamada de voz.
Actores: Usuario.
Descripción: El usuario del teléfono levanta el auricular y marca el número de destino. Al completar la secuencia de dígitos la conexión se realiza. Por medio de tonos particulares el sistema indica el estado de error y de progreso en la conexión.
Precondición: El teléfono está colgado.
Postcondición: Ninguna.
Diagrama:
Nombre: Llamada de voz.
Actores: Usuario.
Descripción: El usuario del teléfono levanta el auricular y marca el número de destino. Al completar la secuencia de dígitos la conexión se realiza. Por medio de tonos particulares el sistema indica el estado de error y de progreso en la conexión.
Precondición: El teléfono está colgado.
Postcondición: Ninguna.
Diagrama:
Flujo Principal:
Paso 1 – Usuario: Levanta el auricular.
Paso 2 – Sistema: Da el tono de marcado.
Paso 3 – Usuario: Indica el número de teléfono.
Paso 4 – Sistema: Realiza la conexión. Da tono de aviso en tanto se levanta el teléfono del lado contrario de la conexión. Permite la conversación al hacerse efectiva la conexión.
Paso 5 – Usuario: Conversa y al finalizar esta, tranca el teléfono.
Paso 6 – Sistema: Termina la conexión.
Paso 1 – Usuario: Levanta el auricular.
Paso 2 – Sistema: Da el tono de marcado.
Paso 3 – Usuario: Indica el número de teléfono.
Paso 4 – Sistema: Realiza la conexión. Da tono de aviso en tanto se levanta el teléfono del lado contrario de la conexión. Permite la conversación al hacerse efectiva la conexión.
Paso 5 – Usuario: Conversa y al finalizar esta, tranca el teléfono.
Paso 6 – Sistema: Termina la conexión.
Flujo alternativo: Número incorrecto
Paso 3 – El sistema: Presenta tono de error y el caso de uso termina.
Paso 3 – El sistema: Presenta tono de error y el caso de uso termina.
Tabla 4 – Caso de Uso completo
Un modelo de casos de uso es entonces una especificación de funcionalidad desde el punto de vista de la interacción del sistema con sus actores, apoyada en la representación gráfica para facilidad de comunicación pero constituida principalmente por texto.
De momento es todo. Mucho más se puede decir sobre los casos de uso: características avanzadas como la relación de extensión y la relación de inclusión; pero en casi todos los casos priva que mientras más sencillo mantengamos la especificación mejor, lo sencillo se entiende más, y la especificación de requisitos ha de entenderse bien. Que tan sofisticado lo hagamos no es ni de lejos, tan importante como la claridad.
lunes, 20 de julio de 2015
Caso de Uso - Agencia de Viajes,Pagina web
Presentare un caso de uso en donde el ingeniero de software emplea un método que, con
toda propiedad puede llamarse abductivo. A partir de la enumeración de unas pocas
interacciones típicas debe en primer lugar encontrar el patrón al que se ajustan todas
ellas: formula una hipótesis sobre el comportamiento del sistema, que engloba todas
las interacciones potenciales, no sólo las típicas. En segundo lugar, debe identificar el
objetivo de este comportamiento, a modo de causa que explica el efecto deseado, que lo
hace comprensible.
Ejemplo detallado de la aplicación
ID:
Una aplicación descrita como “agencia de viajes por internet” (recordemos que nos encontramos precisamente en la etapa de definición exacta de los requisitos del usuario, por tanto la finalidad de la aplicación no está aún bien establecida).
ROL:
el usuario a identificar sera aquel que desea realizar una compra de un boleto o pasaje hacia un sierto destino y quiere hacerlo a través de una pagina web.
CARACTERÍSTICAS y FUNCIONALIDAD:
Supongamos el siguiente diálogo ficticio en el que el ingeniero software (I) entrevista al cliente (C) y le pide que describa una interacción típica entre el usuario y el sistema que se desea desarrollar:
I. Explícame cómo quieres que funcione la aplicación.
C. Bueno, lo primero es acceder a la página web de la agencia, ¿no?, entonces se seleccionan las ciudades de origen y destino, el número de pasajeros, y las fechas de ida y vuelta. El sistema muestra el precio de los billetes, y si el usuario está conforme introduce los datos de su tarjeta de crédito para hacer efectivo el pago. Y hay que dar los nombres de los pasajeros, claro.
I. ¿Eso es todo?
C. Ah, sí, por supuesto, si hay varios vuelos en el mismo día, el usuario debe seleccionar uno de ellos. También hay que tener en cuenta que algunos usuarios están dispuestos a variar sus fechas de viaje, con tal de obtener tarifas más baratas.
I. Así que habrá que facilitar la búsqueda de vuelos en fechas parecidas y que sean más baratos, ¿no? Por ejemplo, variando un día adelante o atrás tanto la fecha de ida como la de vuelta.
C. Exactamente, lo has cogido muy bien.
A partir de esta sencilla descripción, un tanto desordenada, de un uso típico del sistema, y otras semejantes, el ingeniero software debe determinar todos los estados por los que puede pasar el sistema, y lo que es más importante, debe formular de modo preciso el objetivo perseguido (“comprar billetes de avión por internet facilitando la búsqueda de tarifas baratas”), que como tal no estaba contenido en la vaga descripción inicial (“agencia de viajes por internet”).
RAZON o RESULTADO: El resultado a consecuencia de los requisitos obtenidos es generar un pasaje con fecha y hora deseada y con el destino escogido.
en el siguiente diagrama de flujo se observa como se realiza la operación mencionada y descrita.
Ejemplo detallado de la aplicación
ID:
Una aplicación descrita como “agencia de viajes por internet” (recordemos que nos encontramos precisamente en la etapa de definición exacta de los requisitos del usuario, por tanto la finalidad de la aplicación no está aún bien establecida).
ROL:
el usuario a identificar sera aquel que desea realizar una compra de un boleto o pasaje hacia un sierto destino y quiere hacerlo a través de una pagina web.
CARACTERÍSTICAS y FUNCIONALIDAD:
Supongamos el siguiente diálogo ficticio en el que el ingeniero software (I) entrevista al cliente (C) y le pide que describa una interacción típica entre el usuario y el sistema que se desea desarrollar:
I. Explícame cómo quieres que funcione la aplicación.
C. Bueno, lo primero es acceder a la página web de la agencia, ¿no?, entonces se seleccionan las ciudades de origen y destino, el número de pasajeros, y las fechas de ida y vuelta. El sistema muestra el precio de los billetes, y si el usuario está conforme introduce los datos de su tarjeta de crédito para hacer efectivo el pago. Y hay que dar los nombres de los pasajeros, claro.
I. ¿Eso es todo?
C. Ah, sí, por supuesto, si hay varios vuelos en el mismo día, el usuario debe seleccionar uno de ellos. También hay que tener en cuenta que algunos usuarios están dispuestos a variar sus fechas de viaje, con tal de obtener tarifas más baratas.
I. Así que habrá que facilitar la búsqueda de vuelos en fechas parecidas y que sean más baratos, ¿no? Por ejemplo, variando un día adelante o atrás tanto la fecha de ida como la de vuelta.
C. Exactamente, lo has cogido muy bien.
A partir de esta sencilla descripción, un tanto desordenada, de un uso típico del sistema, y otras semejantes, el ingeniero software debe determinar todos los estados por los que puede pasar el sistema, y lo que es más importante, debe formular de modo preciso el objetivo perseguido (“comprar billetes de avión por internet facilitando la búsqueda de tarifas baratas”), que como tal no estaba contenido en la vaga descripción inicial (“agencia de viajes por internet”).
RAZON o RESULTADO: El resultado a consecuencia de los requisitos obtenidos es generar un pasaje con fecha y hora deseada y con el destino escogido.
en el siguiente diagrama de flujo se observa como se realiza la operación mencionada y descrita.
sábado, 11 de julio de 2015
METODOLOGÍA DE SOFTWARE SEGÚN PRESMAN
Presentación Resumen - Metodologias de Diseño de Software de Pressman
Documento Informe - Metodologias de Diseño de Software de Pressman
Documento Informe - Metodologias de Diseño de Software de Pressman
jueves, 25 de junio de 2015
PATRONES DE DISEÑO - MEMENTO
Memento
es un patrón de diseño, cuya finalidad es almacenar el estado de un objeto (o del sistema completo) en un momento dado de manera que se pueda restaurar en ese punto de manera sencilla. Para ello se mantiene almacenado el estado del objeto para un instante de tiempo en una clase independiente de aquella a la que pertenece el objeto (pero sin romper la encapsulación), de forma que ese recuerdo permita que el objeto sea modificado y pueda volver a su estado anterior.
Motivación
Se usa este patrón cuando se quiere poder restaurar el sistema desde estados pasados y por otra parte, es usado cuando se desea facilitar el hacer y deshacer de determinadas operaciones, para lo que habrá que guardar los estados anteriores de los objetos sobre los que se opere (o bien recordar los cambios de forma incremental).
Ejemplo en Java
El siguiente programa Java ilustra el uso de "undo" con el patrón Memento. Intervienen tres clases: la clase Originator (Originador) es una clase que cambia de estado; la clase Caretaker (Portero) se encarga de registrar los cambios del Originador; Memento (Recuerdo) guarda el objeto a salvaguardar.
Originator: Setting state to State1 Originator: Setting state to State2 Originator: Saving to Memento. Originator: Setting state to State3 Originator: Saving to Memento. Originator: Setting state to State4 Originator: State after restoring from Memento: State3
import java.util.*;
class Memento
{
private String state;
public Memento(String stateToSave)
{
state = stateToSave;
}
public String getSavedState()
{
return state;
}
}
class Originator
{
private String state;
/* lots of memory consumptive private data that is not necessary to define the
* state and should thus not be saved. Hence the small memento object. */
public void set(String state)
{
System.out.println("Originator: Setting state to "+state);
this.state = state;
}
public Memento saveToMemento()
{
System.out.println("Originator: Saving to Memento.");
return new Memento(state);
}
public void restoreFromMemento(Memento m)
{
state = m.getSavedState();
System.out.println("Originator: State after restoring from Memento: "+state);
}
}
class Caretaker {
private ArrayList<Memento> savedStates = new ArrayList<Memento>();
public void addMemento(Memento m) { savedStates.add(m); }
public Memento getMemento(int index) { return savedStates.get(index); }
}
class MementoExample {
public static void main(String[] args) {
Caretaker caretaker = new Caretaker();
Originator originator = new Originator();
originator.set("State1");
originator.set("State2");
caretaker.addMemento( originator.saveToMemento() );
originator.set("State3");
caretaker.addMemento( originator.saveToMemento() );
originator.set("State4");
originator.restoreFromMemento( caretaker.getMemento(1) );
}
}
/**
* Memento pattern: Copy the information into a another class for recovery in the future if necessary
* @author Pperez
*
*/
public class MementoPattern {
public void main(String args[]){
RegularClass regularClass = new RegularClass();
regularClass.setData("First Report");
System.out.println(regularClass.data);
regularClass.makeBackup();
regularClass.setData("Second Report");
System.out.println(regularClass.data);
regularClass.recoverBackup();
System.out.println(regularClass.data);
}
public class Memento{
public String memoryData;
public Memento(String data){
this.memoryData=data;
}
public String recoverOldInformation(){
return memoryData;
}
}
public class RegularClass{
Memento memento;
String data;
public RegularClass(){
}
public void setData(String data){
this.data = data;
}
public void makeBackup(){
memento = new Memento(data);
}
public void recoverBackup(){
data = memento.recoverOldInformation();
}
}
}
PATRONES DE DISEÑO - OBSERVER
Observer
es un patrón de diseño que define una dependencia del tipo uno-a-muchos entre objetos, de manera que cuando uno de los objetos cambia su estado, notifica este cambio a todos los dependientes. Se trata de un patrón de comportamiento (existen de 3 tipos: Creación, Estructurales y de Comportamiento), es decir, está relacionado con algoritmos de funcionamiento y asignación de responsabilidades a clases y objetos. Los patrones de comportamiento describen no solamente estructuras de relación entre objetos o clases sino también esquemas de comunicación entre ellos y se pueden clasificar en función de que trabajen con clases (Método Plantilla) u objetos (Cadena de Responsabilidad, Comando, Iterador, Recuerdo, Observador, Estado, Estrategia, Visitante).
La variación de la encapsulación es la base de muchos patrones de comportamiento, por lo que cuando un aspecto de un programa cambia frecuentemente, estos patrones definen un objeto que encapsula dicho aspecto. Los patrones definen una clase abstracta que describe la encapsulación del objeto.
Este patrón también se conoce como el patrón de publicación-inscripció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.
El patrón Observer es la clave del patrón de arquitectura Modelo Vista Controlador (MVC).1 De hecho el patrón fue implementado por primera vez en Smalltalk's MVC basado en un framework de interfaz.2 Este patrón está implementado en numerosos librerías y sistemas, incluyendo todos los toolkits de GUI.
Objetivo
Definir una dependencia uno-a-muchos entre objetos, de tal forma que cuando el objeto cambie de estado, todos sus objetos dependientes sean notificados automáticamente. Se trata de desacoplar la clase de los objetos clientes del objeto, aumentando la modularidad del lenguaje, creando las mínimas dependencias y evitando bucles de actualización (espera activa o polling). En definitiva, normalmente, usaremos el patrón Observador cuando un elemento “quiere” estar pendiente de otro, sin tener que estar encuestando de forma permanente si éste ha cambiado o no.
Motivación
Si se necesita consistencia entre clases relacionadas, pero con independencia, es decir, con un bajo acoplamiento.
Aplicabilidad
Puede pensarse en aplicar este patrón cuando una modificación en el estado de un objeto requiere cambios de otros, y no deseamos que se conozca el número de objetos que deben ser cambiados. También cuando queremos que un objeto sea capaz de notificar a otros objetos sin hacer ninguna suposición acerca de los objetos notificados y cuando una abstracción tiene dos aspectos diferentes, que dependen uno del otro; si encapsulamos estos aspectos en objetos separados permitiremos su variación y reutilización de modo independiente.
Estructura
Participantes
Tendremos sujetos concretos cuyos cambios pueden resultar interesantes a otros y observadores a los que al menos les interesa estar pendientes de un elemento y en un momento dado, reaccionar ante sus notificaciones de cambio. Todos los sujetos tienen en común que un conjunto de objetos quieren estar pendientes de ellos. Cualquier elemento que quiera ser observado tiene que permitir indicar:
“Estoy interesado en tus cambios”.
“Ya no estoy interesado en tus cambios”.
El observable tiene que tener, además, un mecanismo de aviso a los interesados.
A continuación tenemos a los participantes de forma desglosada:
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 porque 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.
Colaboraciones
La colaboración más importante en este patrón es entre el sujeto y sus observadores, ya que en el momento en el que el sujeto sufre un cambio, este notifica a sus observadores.
Diagrama de secuencia
Consecuencias
Las consecuencias de aplicar este patrón pueden ser tanto beneficiosas como pueden perjudicar algunos aspectos. Por una parte abstrae el acoplamiento entre el sujeto y el observador, lo cual es beneficioso ya que conseguimos una mayor independencia y además el sujeto no necesita especificar los observadores afectados por un cambio. Por otro lado, con el uso de este patrón ocurre que vamos a desconocer las consecuencias de una actualización, lo cual, dependiendo del problema, puede afectarnos en mayor o menor medida (por ejemplo, al rendimiento).
Implementación
A continuación vamos a ver una serie de opciones o problemas que se pueden presentar a la hora de implementar este patrón:
Opción 1:
Para evitar que el observador concreto tenga una asociación con el sujeto concreto, podríamos hacer que la relación entre sujeto y observador fuese bidireccional, evitando así asociaciones concretas, el problema es que dejaría de ser una interfaz. El que deje de ser una interfaz puede producir problemas si el lenguaje de programación no soporta la herencia múltiple.
Se podría eliminar la bidireccionalidad de la asociación pasando el sujeto como parámeto al método actualizary ya no tendríamos que referenciar el objeto observado. Esto podría causar problemas si observamos varios objetos, tanto de la misma clase como de distintas, ya que no elimina dependencias, y para hacer operaciones específicas sobre el objeto actualizado nos obliga a hacer en la implementación.
Opción 2:
Si hay muchos sujetos sin observador, la estructura de los observadores está desaprovechada, para solucionarlo podemos tener un intermediario que centralice el almacenamiento de la asociación de cada sujeto con sus observadores. Para esta solución creamos ese gestor de observadores usando el patrón Singleton(Instancia única), ya que nos proporciona una única referencia y no una por cada sujeto. El gestor aunque mejora el aprovechamiento del espacio, hace que se reduzca el rendimiento y se pierde eficiencia en el método notificar.
Opción 3:
El responsable de iniciar la comunicación es el sujeto concreto, pero se puede dar un problema cuando el objeto concreto está siendo actualizado de forma continua ya que debido a esto tendríamos que realizar muchas actualizaciones en muy poco tiempo. La solución sería suspender temporalmente las llamadas al método de actualización/notificación; por ejemplo, haciendo que el cliente pueda activar o desactivar las notificaciones y notificar todos los cambios cuando las vuelva a habilitar. El patrón Estado sería una posible solución para diseñar esta variante de no notificar si no se han dado cambios o hacerlo en caso de que si.
Opción 4 (Referencias inválidas):
A la hora de implementar este patrón debemos de ser cuidadosos cuando un elemento observable desaparece. En ciertos lenguajes será el gestor de memoria el que cada cierto tiempo debe de limpiará las referencias liberadas, pero si un observable que sigue siendo observado puede no liberarse nunca. Para solucionar este problema puede crearse una función “destruir (destroy) ” que notifique al gestor que el elemento observable va a desaparecer y si no estamos usando la variante del gestor el observable directamente des-registrará a sus observadores. Antes de esto hay que eliminar las referencias a este elemento, por tanto, hay que eliminar a los observadores antes de eliminar al observable, ya que así evitaremos tanto que aparezcan referencias inválidas al objeto una vez éste haya sido eliminado, como que se produzcan operaciones inválidas intentando invocarlo.
Podemos avisar a los observadores creando un método actualizar especial, en el que tendríamos dos opciones:
El observador también muere.
El observador sigue vivo, pero apunta a nulo.
Opción 5:
Ya que debemos asegurar la consistencia del estado del sujeto antes de iniciar una notificación, siempre notificaremos al final, ya que aunque en entorno multihilo notificamos antes de hacer los cambios, puede que los observadores soliciten información al observable cuando aún se van a hacer más cambios y se darían problemas de consistencia si se accede a un estado que aún no es el definitivo. De esta forma, los observadores ya no accederán a sujetos en estado inconsistente.
Por ejemplo:
Secuencia incorrecta:
a
b
c
notificar()
d
e
f
Secuencia correcta:
a
b
c
d
e
f
notificar()
Jerarquía con varios tipos des observadores: en este caso el hilo redefine cambios, no los notifica.
Jerarquía de varios observadores
Opción 6:
En mecanismos de notificación tradicionalmente hay dos opciones, la pull que es la que propone el patrón observador y la push hacia la que nos moveríamos si incluimos información como parámetros en el actualizar. El problema de hacer esto es que la interfaz del observador se vuelve más específica y por tanto menos genérica y reutilizable.
PULL: los objetos avisan de que han cambiado y el observador pregunta cuál ha sido el cambio.
PUSH: minimiza (eficiencia) que cuando algo cambia e informamos a todos los interesados, se realicen el menor número de llamadas posibles.
Dependiendo del problema ante el que nos encontremos, debemos de valorar que implementación se ajusta mejor para resolverlo de la forma más eficiente y efectiva o si las variantes anteriores pueden combinarse entre sí dependiendo de las características de escenario concreto. Por ejemplo, la opción 2 podría aplicarse cuando nos interesa aplicar en un sujeto concreto n métodos seguidos y no queremos notificar hasta que todos finalicen su ejecución.
Ejemplo en Java
import java.util.ArrayList;
public abstract class Observable {
//El constructor crea el vector con la asociacion Observable-Observador
public Observable() {
_observadores = new ArrayList<Observador>();
}
//Agregar y eliminar sencillamente operan sobre vector _observadores...
public void agregarObservador(Observador o) {
_observadores.add(o);
}
public void eliminarObservador(Observador o) {
_observadores.remove(o);
}
//Notificacion: Para cada observador se invoca el método actualizar().
public void notificarObservadores() {
for (Observador o:_observadores) {
o.actualizar();
}
}
//Este atributo privado mantiene el vector con los observadores
private ArrayList<Observador> _observadores;
}
/*Los observadores deben implementar la siguiente interfaz, es decir,
*tienen un metodo actualizar() para reaccionar a cambios del sujeto
*/
public interface Observador {
public void actualizar();
}
/*Ejemplo de sujeto observable. Es una clase que mantiene un valor entero en
*el rango 0..maximo, donde maximo se establece en la construccion. La clase
*dispone de metodos para modificar el valor y para acceder al estado
*(valor, maximo). Extiende la clase observable heredando su
*/
public class Contador extends Observable {
/*El constructor inicializa el estado del objeto: el maximo y el
*valor inicial, siempre en el rango 0..maximo. Adicionalmente,
*inicializa la parte de Observable que tiene un Contador...
*/
public Contador(int valor, int maximo) {
super();
_maximo = maximo;
_valor = enRango(valor);
}
//Este metodo privado asegura que un valor n esta entre 0..maximo
private int enRango(int n) {
if (n < 0) {
return 0;
} else if (n > _maximo) {
return _maximo;
} else {
return n;
}
}
//Estos dos metodos permiten el acceso al estado del contador
public int valor() {
return _valor;
}
public int maximo() {
return _maximo;
}
/*Este metodo afecta al estado: primero se modifica de forma consistente
*el estado y despues se notifica a los observadores del cambio
*/
public void incrementarContador(int n) {
_valor = enRango(_valor+n);
notificarObservadores();
}
//Atributos privados que mantienen el estado del contador
private int _valor, _maximo;
}
// Observador muy simple que ni siquiera consulta el estado del sujeto... public class Detector implements Observador { public void actualizar() { System.out.println("Detector recibe actualizar!"); } }//Un ejemplo de observador concreto de la clase contador. public class Medidor implements Observador { //El constructor de Medidor establece la asociacion entre Medidor-Contador public Medidor(Contador contador) { _contador = contador; } /*Tras ser notificado de un cambio, un observador Medidor accede *al estado del Contador utilizando la asociacion */ public void actualizar() { int porcentaje = _contador.valor() * 100 / _contador.maximo(); System.out.println("Porcentaje del contador es " + porcentaje + "%"); } //Mantiene la asociacion con el contador private Contador _contador; }//Un ejemplo de observador concreto de la clase contador. public class ValorContador implements Observador { //El constructor establece la asociacion entre ValorContador-Contador public ValorContador(Contador contador) { _contador = contador; } /*Tras ser notificado de un cambio, un observador ValorContador accede *al estado del Contador utilizando la asociacion */ public void actualizar() { System.out.println("Valor del contador es " + _contador.valor() ); } //Mantiene asociacion con el sujeto observable private Contador _contador; }public class Main { public static void main(String... argv) { Contador c = new Contador(0,255); Detector d = new Detector(); c.agregarObservador(d); c.incrementarContador(5); ValorContador v = new ValorContador(c); c.agregarObservador(v); c.incrementarContador(5); Medidor m = new Medidor(c); c.agregarObservador(m); c.eliminarObservador(d); c.incrementarContador(19); } }
Suscribirse a:
Entradas (Atom)