O padrón observador é un padrón de deseño software que se encadra dentro da categoría de padróns de comportamento. Neste padrón, un obxecto, que chamaremos observado, mantén unha lista dos seus dependentes (dependencia un a moitos), chamados observadores, aos que notifica automaticamente de calquera cambio no seu estado.
Propósito
Definir unha dependencia un-a-moitos entre obxectos de tal forma que cando o obxecto cambie o seu estado, todos os obxectos dependentes sexan notificados automaticamente.
Outros nomes
- Araña (Spider)
- padrón de publicación-inscrición (Publish/Suscribe)
- Modelo-padrón
Motivación
O padrón xorde da necesidade de consistencia entre clases relacionadas pero mantendo un acoplamento baixo. Por exemplo, separar compoñente da interface gráfica dos obxectos que manteñen os datos da aplicación, para que sexan reutilizables independentemente.
O comportamento dos observadores depende do suxeito observado, que debería notificar calquera cambio no seu estado. Este pode ter un número ilimitado de observadores.
Aplicabilidade
O padrón aplícase cando unha modificación no estado dun obxecto require cambios doutros e non se desexa coñecer cantos obxectos deben ser cambiados. Tamén é de aplicación no caso en que un obxecto debe ser capaz de notificar a outros obxectos sen facer ningunha suposición deles.
O padrón permitenos encapsular os dous aspectos da abstracción en obxectos separados polo que nos permite a súa variación e reutilización de modo independente.
Estrutura
Participantes
SuxeitoObservado
Coñece aos seus observadores. Proporciona unha interface para agregar e para eliminar observadores, así como para notificar aos seus observadores.
SuxeitoObservadoConcreto
Mantén o estado de interese para os observadores concretos. Notifica aos seus observadores ao cambiar de estado.
Observador
Define unha interface co método utilizado polo suxeito para notificar os cambios no seu estado.
ObservadorConcreto
Mantén unha referencia ao suxeito concreto. Parte do seu estado debe permanecer consistente co estadio do suxeito ao que observa. Pon en funcionamento a interface de actualización.
Colaboracións entre participantes
O suxeito concreto notifica aos seus observadores cada vez que se modifica o seu estado. Tras ser informado dun cambio no suxeito concreto, o observador concreto interroga ao suxeito concreto para modificar a percepción que ten do mesmo.
Consecuencias
- Abstrae o acoplamento entre suxeito e observador.
- O suxeito non necesita especificar os observadores afectados por un cambio.
- Descoñecemento das consecuencias dunha actualización.
Posta en funcionamento
Posta en funcionamento suxeito-observadores. Observación de máis dun suxeito. Responsabilidade de inicio da notificación.
- Suxeito invoca a notificación tras executarse un método que cambie o seu estado.
- Clientes do suxeito (posiblemente os propios observadores) son responsables de comezar a notificación de actualición.
Referencias inválidas tras borrar un suxeito.
Asegurar a consistencia do estado do suxeito antes de iniciar a notificación.
Evitar a actualización dependente do observador.
- Suxeito envía información detallada sobre o cambio (push model), esta solución é menos reusable.
- O suxeito notifica o cambio e os observadores piden a información necesaria sobre o cambio (pull model). Esta solución é ineficiente, pero é máis reusable.
Especificación explícita das modificacións.
Código de exemplo (JAVA)
JAVA ten propiamente unha posta en funcionamento para o padrón observador como moitas linguaxes
- Clase java.util.Observable
void addObserver(o Observer)
protected void clearChanged()
int countObservers()
void deleteObserver(o Observer)
void deleteObservers()
boolean hasChanged()
void notifyObserver()
void notifyObservers(Object data)
protected void setChanged()
- Interface java.util.Observer
void update (Observable o, Object data)
Debaixo móstrase como debería ser dita posta en funcionamento sen percorrer as librerías propias da linguaxe JAVA.
Interface Observador
/**
* Os observadores deben poñer en funcionamento a seguinte interface, é dicir,
* teñen un método actualizar() para reaccionar aos cambios do suxeito
*/
public interface Observador {
public void actualizar();
}
Clase abstracta SuxeitoObservable
/**
* Esta clase abstracta representa os obxectos susceptibles de ser
* observados. Incorpora métodos para engadir e eliminar observadores
* e un método de notificación. A asociación Observable-Observadores
* ponse en funcionamento mediante un vector de observadores
*/
import java.util.ArrayList;
public abstract class SuxeitoObservable {
// o construtor crea o vector coa asociación Observable-Observador
public Observable() {
_observadores = new ArrayList<Observador>();
}
// engadir e eliminar sinxelamente operan sobre o vector _observadores...
public void agregarObservador(Observador o) {
_observadores.add(o);
}
public void eliminarObservador(Observador o) {
_observadores.remove(o);
}
// Notificación: Para cada observador invócase o método actualizar().
public void notificarObservadores() {
for (Observador o:_observadores) {
o.actualizar();
}
}
// Este atributo privado mantén o vector cos observadores
private ArrayList<Observador> _observadores;
}
Clase Detector, ObservadorConcreto
/**
* Observador moi simple que nin sequera consulta o estado do suxeito...
*/
public class Detector implements Observador {
public void actualizar() {
System.out.println("Detector recibe actualizar!");
}
}
Clase SuxeitoObservable
/**
* Exemplo de suxeito observable. É unha clase que mantén un valor enteiro no
* rango 0..máximo, onde o máximo se establece na construción. A clase
* dispón de métodos para modificar o valor e para acceder ao estado
* (valor, máximo). Estende a clase observable herdando o seu comportamento.
*/
public class Contador extends SuxeitoObservable {
// o construtor inicializa o estado do obxecto: o máximo e o
// valor inicial, sempre no rango 0..máximo. Adicionalmente,
// inicializa a parte de Observable que ten un Contador...
public Contador(int valor, int maximo) {
super();
_maximo = maximo;
_valor = enRango(valor);
}
// este método privado asegura que un valor n esta entre 0..máximo
private int enRango(int n) {
if (n < 0) {
return 0;
} else if (n > _maximo) {
return _maximo;
} else {
return n;
}
}
// Estes dous métodos permiten o acceso ao estado do contador
public int valor() {
return _valor;
}
public int maximo() {
return _maximo;
}
// Este método afecta ao estado: primeiro modifícase de forma consistente
// o estado e despois notifícase aos observadores do cambio
public void incrementarContador(int n) {
_valor = enRango(_valor+n);
notificarObservadores();
}
// atributos privados que manteñen o estado do contador
private int _valor, _maximo;
}
Clase Medidor, ObservadorConcreto
/**
* Un exemplo de observador concreto da clase Contador.
*/
public class Medidor implements Observador {
// O construtor de Medidor establece a asociación entre Medidor-Contador
public Medidor(Contador contador) {
_contador = contador;
}
// Tras ser notificado dun cambio, un observador Medidor accede
// ao estado do Contador utilizando a asociación
public void actualizar() {
int porcentaxe = _contador.valor() * 100 / _contador.maximo();
System.out.println("A porcentaxe do contador é " + porcentaxe + "%");
}
// mantén a asociación co contador
private Contador _contador;
}
Clase ValorContador, ObservadorConcreto
/**
* Un exemplo de observador concreto da clase contador.
*/
public class ValorContador implements Observador {
// O construtor establece a asociación entre ValorContador-Contador
public ValorContador(Contador contador) {
_contador = contador;
}
// Tras ser notificado dun cambio, un observador ValorContador accede
// ao estado do Contador utilizando a asociación
public void actualizar() {
System.out.println("O valor do contador é " + _contador.valor() );
}
// mantén a asociación co suxeito observable
private Contador _contador;
}
Exemplo de uso, Clase Main
public class Main {
public static void main(String... argv) {
//Xeramos un obxecto observable da clase Contador
Contador c = new Contador(0,255);
//Xeramos un obxecto observador e engadímolo como observador do contador
Detector d = new Detector();
c.agregarObservador(d);
//Producimos unha variación no estado do contador
c.incrementarContador(5);
//Xeramos un obxecto observador e engadímolo como observador do contador
ValorContador v = new ValorContador(c);
c.agregarObservador(v);
//Producimos unha variación no estado do contador
c.incrementarContador(5);
//Xeramos un obxecto observador e engadímolo como observador do contador
Medidor m = new Medidor(c);
c.agregarObservador(m);
//Eliminamos o primeiro observador da lista de observadores do contador
c.eliminarObservador(d);
//Producimos unha variación no estado do contador
c.incrementarContador(19);
}
}
Usos coñecidos
- Habitualmente úsase para poñer en funcionamento sistemas de manexadores de eventos.
- Tamén é unha peza clave na arquitectura MVC (Model-View-Controller, Modelo-Vista-Controlador).
- Mediador (Mediator)
- Instancia única (Singleton)
Véxase tamén
Bibliografía
- E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley Professional Computing Series.
- M. Grand. Patterns in Java, a catalog of reusable design patterns illustrated with UML. Volume I. John Wiley & Sons.