En diseño e ingeniería de software , el patrón observador es un patrón de diseño de software en el que un objeto , llamado sujeto , mantiene una lista de sus dependientes, llamados observadores , y les notifica automáticamente cualquier cambio de estado , generalmente llamando a uno de sus métodos .
Se utiliza a menudo para implementar sistemas de manejo de eventos distribuidos en software controlado por eventos . En tales sistemas, el sujeto suele denominarse "flujo de eventos" o "fuente de flujo de eventos", mientras que los observadores se denominan "sumideros de eventos". La nomenclatura de flujo alude a una configuración física en la que los observadores están separados físicamente y no tienen control sobre los eventos emitidos desde el sujeto/fuente de flujo. Por lo tanto, este patrón se adapta a cualquier proceso mediante el cual los datos llegan desde alguna entrada que no está disponible para la CPU en el inicio , sino que pueden llegar en momentos arbitrarios o indeterminados ( solicitudes HTTP , datos GPIO , entrada de usuario desde periféricos y bases de datos distribuidas , etc.).
El patrón de diseño del observador es un patrón de comportamiento que figura entre los 23 patrones de diseño conocidos del "Grupo de los Cuatro" y que abordan desafíos de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, produciendo objetos que son más fáciles de implementar, cambiar, probar y reutilizar. [1]
El patrón de observador aborda los siguientes problemas: [2]
La definición de una dependencia de uno a muchos entre objetos mediante la definición de un objeto (sujeto) que actualiza el estado de los objetos dependientes directamente es inflexible porque acopla el sujeto a objetos dependientes particulares. Sin embargo, podría ser aplicable desde un punto de vista de rendimiento o si la implementación del objeto está estrechamente acoplada (como las estructuras de kernel de bajo nivel que se ejecutan miles de veces por segundo). Los objetos estrechamente acoplados pueden ser difíciles de implementar en algunos escenarios y no se reutilizan fácilmente porque hacen referencia a muchos objetos con diferentes interfaces y son conscientes de ellos. En otros escenarios, los objetos estrechamente acoplados pueden ser una mejor opción porque el compilador puede detectar errores en tiempo de compilación y optimizar el código en el nivel de instrucción de la CPU.
Subject
y Observer
objetos.La única responsabilidad de un sujeto es mantener una lista de observadores y notificarles los cambios de estado llamando a su update()
operación. La responsabilidad de los observadores es registrarse y cancelar su registro con un sujeto (para recibir notificaciones de cambios de estado) y actualizar su estado (para sincronizar su estado con el estado del sujeto) cuando reciben una notificación. Esto hace que el sujeto y los observadores estén débilmente acoplados. El sujeto y los observadores no tienen conocimiento explícito el uno del otro. Los observadores se pueden agregar y eliminar de forma independiente en tiempo de ejecución. Esta interacción de notificación-registro también se conoce como publicación-suscripción .
El patrón observador puede provocar fugas de memoria , conocidas como el problema del oyente inactivo , porque en una implementación básica, requiere tanto un registro explícito como una anulación explícita del registro, como en el patrón dispose , porque el sujeto mantiene referencias sólidas a los observadores, lo que los mantiene activos. Esto se puede evitar si el sujeto mantiene referencias débiles a los observadores.
Por lo general, el patrón de observador se implementa de modo que el sujeto que se observa sea parte del objeto para el que se observan los cambios de estado (y se comunican a los observadores). Este tipo de implementación se considera estrechamente acoplada , lo que obliga tanto a los observadores como al sujeto a estar conscientes el uno del otro y tener acceso a sus partes internas, lo que crea posibles problemas de escalabilidad , velocidad, recuperación y mantenimiento de mensajes (también llamado pérdida de eventos o notificaciones), la falta de flexibilidad en la dispersión condicional y el posible obstáculo a las medidas de seguridad deseadas. En algunas implementaciones ( sin sondeo ) del patrón de publicación-suscripción , esto se resuelve creando un servidor de cola de mensajes dedicado (y, a veces, un objeto controlador de mensajes adicional) como una etapa adicional entre el observador y el objeto que se observa, desacoplando así los componentes. En estos casos, los observadores con el patrón de observador acceden al servidor de cola de mensajes, suscribiéndose a ciertos mensajes y sabiendo (o no sabiendo, en algunos casos) solo sobre el mensaje esperado, mientras que no saben nada sobre el remitente del mensaje en sí; el remitente también puede no saber nada sobre los observadores. Otras implementaciones del patrón de publicación-suscripción, que logran un efecto similar de notificación y comunicación a las partes interesadas, no utilizan el patrón de observador. [3] [4]
En las primeras implementaciones de sistemas operativos multiventana como OS/2 y Windows , los términos "patrón de publicación-suscripción" y "desarrollo de software impulsado por eventos" se usaban como sinónimos para el patrón de observador. [5]
El patrón de observador, tal como se describe en el libro Design Patterns , es un concepto muy básico y no aborda la eliminación del interés en los cambios en el sujeto observado o la lógica especial que debe realizar el sujeto observado antes o después de notificar a los observadores. El patrón tampoco aborda el registro de las notificaciones de cambio ni garantiza su recepción. Estas cuestiones se suelen abordar en sistemas de colas de mensajes, en los que el patrón de observador desempeña solo un papel pequeño.
Los patrones relacionados incluyen publicación-suscripción, mediador y singleton .
El patrón de observador se puede utilizar en ausencia de publicación-suscripción, como cuando el estado del modelo se actualiza con frecuencia. Las actualizaciones frecuentes pueden hacer que la vista deje de responder (por ejemplo, al invocar muchas llamadas de repintado ); dichos observadores deberían utilizar un temporizador. En lugar de sobrecargarse con mensajes de cambio, el observador hará que la vista represente el estado aproximado del modelo a intervalos regulares. Este modo de observador es particularmente útil para las barras de progreso , en las que el progreso de la operación subyacente cambia con frecuencia.
En este diagrama de clases UML , la clase no actualiza el estado de los objetos dependientes directamente. En cambio, hace referencia a la interfaz ( ) para actualizar el estado, lo que hace que la clase sea independiente de cómo se actualiza el estado de los objetos dependientes. Las clases y implementan la interfaz sincronizando su estado con el estado del sujeto.Subject
Subject
Observer
update()
Subject
Observer1
Observer2
Observer
El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución: los objetos y llaman a para registrarse a sí mismos. Suponiendo que el estado de cambia, se llama a sí mismo. llama a los objetos y registrados , que solicitan los datos modificados ( ) de para actualizar (sincronizar) su estado.Observer1
Observer2
attach(this)
Subject1
Subject1
Subject1
notify()
notify()
update()
Observer1
Observer2
getState()
Subject1
Si bien existen las clases de biblioteca java.util.Observer y java.util.Observable, han quedado obsoletas en Java 9 porque el modelo implementado era bastante limitado.
A continuación se muestra un ejemplo escrito en Java que toma la entrada del teclado y maneja cada línea de entrada como un evento. Cuando se proporciona una cadena desde System.in
, se llama al método notifyObservers()
para notificar a todos los observadores sobre la ocurrencia del evento, en forma de una invocación de sus métodos de actualización.
importar java.util.ArrayList ; importar java.util.List ; importar java.util.Scanner ; interfaz Observer { void update ( String evento ); } clase EventSource { Lista < Observer > observadores = new ArrayList < > ( ); public void notifyObservers ( String evento ) { observadores.forEach ( observador - > observador.update ( evento )) ; } public void addObserver ( Observer observador ) { observadores.add ( observador ) ; } public void scanSystemIn ( ) { Escáner escáner = new Scanner ( System.in ) ; while ( escáner.hasNextLine ( ) ) { String línea = escáner.nextLine ( ) ; notifyObservers ( línea ) ; } } } clase pública ObserverDemo { public static void main ( String [] args ) { System . println ( "Ingrese texto: " ); EventSource eventSource = new EventSource (); eventSource . addObserver ( evento -> System . out . println ( "Respuesta recibida : " + evento )) ; eventSource.scanSystemIn ( ) ; } }
Esta es una implementación de C++11.
#include <funcional> #include <iostream> #include <lista> clase Sujeto ; //Declaración anticipada para uso en Observer clase Observador { público : explícito Observador ( Sujeto & subj ); virtual ~ Observador (); Observador ( const Observador & ) = eliminar ; // regla de tres Observador & operador = ( const Observador & ) = eliminar ; virtual void update ( Subject & s ) const = 0 ; private : // Referencia a un objeto Subject a separar en el destructor Subject & subject ; }; // Subject es la clase base para la generación de eventos class Subject { public : using RefObserver = std :: reference_wrapper < const Observer > ; // Notificar a todos los observadores adjuntos void notify () { for ( const auto & x : observers ) { x . get (). update ( * this ); } } // Añadir un observador void bind ( const Observer & observer ) { observers . push_front ( observer ); } // Eliminar un observador void detach ( Observer & observer ) { observers . remove_if ( [ & observer ]( const RefObserver & obj ) { return & obj . get () ==& observer ; }); } private : std :: list < RefObserver > observers ; }; Observador :: Observador ( Sujeto & subj ) : sujeto ( subj ) { sujeto.adjuntar ( * this ) ; } Observador ::~ Observador ( ) { subject.detach ( * this ) ; } // Ejemplo de uso class ConcreteObserver : public Observer { public : ConcreteObserver ( Subject & subj ) : Observer ( subj ) {} // Obtener notificación void update ( Subject & ) const override { std :: cout << "Recibí una notificación" << std :: endl ; } }; int main () { Sujeto cs ; ConcreteObserver co1 ( cs ); ConcreteObserver co2 ( cs ); cs . notificar (); }
La salida del programa es como
Recibí una notificación Recibí una notificación
clase EventSource { observadores privados = [] notifyObservers privado ( String evento ) { observadores . each { it ( evento ) } } void addObserver ( observador ) { observadores += observador } void scanSystemIn ( ) { var scanner = new Scanner ( System.in ) while ( scanner ) { var line = scanner.nextLine ( ) notifyObservers ( line ) } } } println 'Ingrese texto: ' var eventSource = new EventSource () eventSource . addObserver { event -> println "Respuesta recibida: $event" } eventSource.scanSystemIn ( )
importar java.util.Scanner typealias Observador = ( evento : String ) -> Unidad ; clase EventSource { privada var observadores = mutableListOf < Observer > () fun privado notifyObservers ( evento : String ) { observadores.forEach { it ( evento ) } } fun addObserver ( observador : Observador ) { observadores += observador } diversión scanSystemIn ( ) { val escáner = Scanner ( System.`in` ) mientras ( scanner.hasNext ( ) ) { val línea = scanner.nextLine ( ) notifyObservers ( línea ) } } }
fun main ( arg : Lista < String > ) { println ( "Ingrese texto: " ) val eventSource = EventSource () eventSource . addObserver { event -> println ( "Respuesta recibida: $ event " ) } eventSource.scanSystemIn ( )
utiliza System . Genéricos . Colecciones , System . SysUtils ; tipo IObserver = interfaz [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] procedimiento Actualizar ( const AValue : string ) ; fin ; tipo TObserverManager = clase privada FObservers : TList < IObserver >; constructor público Crear ; sobrecarga ; destructor Destruir ; anular ; procedimiento NotifyObservers ( const AValue : string ) ; procedimiento AddObserver ( const AObserver : IObserver ) ; procedimiento UnregisterObsrver ( const AObserver : IObserver ) ; fin ; tipo TListener = clase ( TInterfacedObject , IObserver ) privado FName : cadena ; público constructor Crear ( const AName : cadena ) ; reintroducir ; procedimiento Actualizar ( const AValue : cadena ) ; fin ; procedimiento TObserverManager . AddObserver ( const AObserver : IObserver ) ; comienza si no es FObservers . Contiene ( AObserver ) entonces FObservers . Add ( AObserver ) ; fin ; comienza FreeAndNil ( FObservers ) ; heredado ; fin ; procedimiento TObserverManager . NotifyObservers ( const AValue : string ) ; var i : Integer ; inicio para i := 0 a FObservers . Count - 1 hacer FObservers [ i ] . Update ( AValue ) ; fin ; procedimiento TObserverManager . UnregisterObsrver ( const AObserver : IObserver ) ; inicio si FObservers . Contiene ( AObserver ) entonces FObservers . Eliminar ( AObserver ) ; fin ; constructor TListener . Create ( const AName : string ) ; comienzo heredado Create ; FName := AName ; fin ; procedimiento TListener . Update ( const AValue : string ) ; begin WriteLn ( FName + ' el oyente recibió la notificación: ' + AValue ) ; end ; procedimiento TMyForm . ObserverExampleButtonClick ( Sender : TObject ) ; var LDoorNotify : TObserverManager ; LListenerHusband : IObserver ; LListenerWife : IObserver ; comienzo LDoorNotify := TObserverManager . Create ; intento LListenerHusband := TListener . Create ( 'Esposo' ) ; LDoorNotify . AddObserver ( LListenerHusband ) ; LListenerWife := TListener . Create ( 'Esposa' ) ; LDoorNotify . AddObserver ( LListenerWife ) ; LDoorNotify . NotifyObservers ( 'Alguien está llamando a la puerta' ) ; finalmente FreeAndNil ( LDoorNotify ) ; fin ; fin ;
Producción
El marido oyente recibió una notificación: Alguien está llamando a la puerta.La esposa oyente recibió una notificación: Alguien está llamando a la puerta.
Un ejemplo similar en Python :
clase Observable : def __init __ ( self ): self._observers = [ ] def register_observer ( self , observador ) - > None : self._observers.append ( observador ) def notificar_observadores ( self , * args , ** kwargs ) - > Ninguno : para el observador en self._observers : observador.notificar ( self , * args , ** kwargs ) clase Observador : def __init __ ( self , observable ) : observable.register_observer ( self ) def notificar ( self , observable , * args , ** kwargs ) -> None : print ( "Obtuve" , args , kwargs , "De" , observable )sujeto = Observable () observador = Observador ( sujeto ) sujeto.notificar_observadores ( " prueba " , kw = " python" ) # impresiones: Obtuve ('test',) {'kw': 'python'} de <__main__.Observable object en 0x0000019757826FD0>
clase Payload { cadena interna Mensaje { obtener ; establecer ; } } clase Asunto : IObservable < Payload > { privado de solo lectura ICollection < IObserver < Payload >> _observers = nueva Lista < IObserver < Payload >> (); IDisposable IObservable < Payload > . Subscribe ( IObserver < Payload > observador ) { if ( ! _observers . Contains ( observador )) { _observers . Add ( observador ); } devolver nuevo Cancelar suscripción ( observador , _observadores ); } void interno SendMessage ( string mensaje ) { foreach ( var observador en _observadores ) { observador.OnNext ( new Payload { Mensaje = mensaje } ) ; } } } clase interna Unsubscriber : IDisposable { privada de solo lectura IObserver < Payload > _observer ; privada de solo lectura ICollection < IObserver < Payload >> _observers ; Cancelar suscripción interna ( IObserver < Payload > observador , ICollection < IObserver < Payload >> observadores ) { _observer = observador ; _observers = observadores ; } void IDisposable . Dispose () { if ( _observador != null && _observadores . Contiene ( _observador )) { _observadores . Eliminar ( _observador ); } } } clase interna Observer : IObserver < Payload > { cadena interna Message { obtener ; establecer ; } público void OnCompleted () { } public void OnError ( Error de excepción ) { } public void OnNext ( Valor de carga útil ) { Mensaje = valor.Mensaje ; } Registro interno IDisposable ( IObservable < Payload > subject ) { return subject . Subscribe ( this ); } }
JavaScript tiene una función obsoleta Object.observe
que era una implementación más precisa del patrón observador. [7] Esta función activaba eventos al producirse un cambio en el objeto observado. Sin la Object.observe
función obsoleta, el patrón se puede implementar con un código más explícito: [8]
deje que Sujeto = { _estado : 0 , _observadores : [ ] , agregue : función ( observador ) { this._observadores.push ( observador ) ; } , obtenerEstado : función ( ) { devuelva this._estado ; } , establecerEstado : función ( valor ) { this._estado = valor ; para ( deje que i = 0 ; i < this._observadores.length ; i ++ ) { this._observadores [ i ] .señal ( this ) ; } } } ; deje que Observer = { señal : función ( sujeto ) { deje que currentValue = sujeto.getState ( ) ; consola.log ( currentValue ) ; } } Sujeto . add ( Observador ); Sujeto . setState ( 10 ); //Salida en console.log - 10