El patrón de 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 de cualquier cambio de estado, generalmente llamando a uno de sus métodos .
Se utiliza principalmente para implementar el manejo de eventos distribuidos.sistemas, en software "impulsado por eventos". En esos sistemas, el sujeto generalmente se denomina "flujo de eventos" o "fuente de flujo de eventos", mientras que los observadores se denominan "sumideros de eventos". La nomenclatura de la secuencia alude a una configuración física en la que los observadores están físicamente separados y no tienen control sobre los eventos emitidos por el sujeto / fuente de la secuencia. Este patrón se adapta perfectamente a cualquier proceso en el que los datos lleguen de alguna entrada que no esté disponible para la CPU al inicio, sino que llegue "al azar" (solicitudes HTTP, datos GPIO, entrada del usuario desde el teclado / mouse / ..., bases de datos distribuidas y blockchains, ...). La mayoría de los lenguajes de programación modernos comprenden construcciones de "eventos" incorporadas que implementan los componentes del patrón de observador. Si bien no es obligatorio, la mayoría de las implementaciones de 'observadores' usarían subprocesos en segundo plano que escuchan eventos de sujeto y otros mecanismos de soporte proporcionados por el kernel (Linuxepoll , ...).
Descripción general
El patrón de diseño Observer es uno de los veintitrés patrones de diseño conocidos "Gang of Four" que describen cómo resolver desafíos de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar, cambiar, probar y reutilizar. [1]
¿Qué problemas puede resolver el patrón de diseño Observer?
El patrón Observer aborda los siguientes problemas: [2]
- Se debe definir una dependencia de uno a muchos entre objetos sin hacer que los objetos estén estrechamente acoplados.
- Debe asegurarse que cuando un objeto cambia de estado, se actualiza automáticamente un número ilimitado de objetos dependientes.
- Debería ser posible que un objeto pueda notificar un número ilimitado de otros objetos.
Definir 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 al sujeto a objetos dependientes particulares. Aún así, puede tener sentido desde el punto de vista del rendimiento o si la implementación del objeto está estrechamente acoplada (piense en 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 difíciles de reutilizar porque se refieren y conocen (y cómo actualizar) muchos objetos diferentes con diferentes interfaces. En otros escenarios, los objetos estrechamente acoplados pueden ser una mejor opción, ya que el compilador podrá detectar errores en tiempo de compilación y optimizar el código en el nivel de instrucción de la CPU.
¿Qué solución describe el patrón de diseño de Observer?
- Definir
Subject
yObserver
objetos. - de modo que cuando un sujeto cambia de estado, todos los observadores registrados son notificados y actualizados automáticamente (y probablemente de forma asincrónica).
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 anular el registro) en un tema (para recibir notificaciones de cambios de estado) y actualizar su estado (sincronizar su estado con el estado del sujeto) cuando se les notifica. Esto hace que el sujeto y los observadores estén débilmente acoplados. El sujeto y los observadores no se conocen explícitamente. Los observadores se pueden agregar y eliminar de forma independiente en tiempo de ejecución. Esta interacción notificación-registro también se conoce como publicación-suscripción .
Consulte también el diagrama de secuencia y clase UML a continuación.
Referencia fuerte frente a débil
El patrón de observador puede causar pérdidas de memoria , conocido como el problema del oyente caducado , porque en una implementación básica, requiere tanto el registro explícito como el desregistro explícito, como en el patrón de disposición , porque el sujeto tiene fuertes referencias a los observadores, manteniéndolos con vida. Esto puede evitarse si el sujeto tiene referencias débiles a los observadores.
Acoplamiento e implementaciones típicas de pub-sub
Normalmente, el patrón de observador se implementa de modo que el "sujeto" que se "observa" sea parte del objeto para el que se observan (y se comunican a los observadores) cambios de estado. Este tipo de implementación se considera " estrechamente acoplada ", lo que obliga tanto a los observadores como al sujeto a conocerse y tener acceso a sus partes internas, creando posibles problemas de escalabilidad , velocidad, recuperación de mensajes y mantenimiento (también llamado evento o notificación pérdida), la falta de flexibilidad en la dispersión condicional y el posible obstáculo a las medidas de seguridad deseadas. En algunas implementaciones (que no son de sondeo ) del patrón de publicación-suscripción (también conocido como patrón pub-sub ), 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 observado, desacoplando así los componentes. En estos casos, los observadores acceden al servidor de cola de mensajes con el patrón de observador, "suscribiéndose a ciertos mensajes" sabiendo solo sobre el mensaje esperado (o no, en algunos casos), sin saber 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 en absoluto. [3] [4]
En las primeras implementaciones de sistemas operativos de ventanas múltiples 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ónimo del patrón de observador. [5]
El patrón del observador, como se describe en el libro del GoF , es un concepto muy básico y no aborda la eliminación del interés en los cambios al "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 se ocupa de la grabación cuando se envían notificaciones de cambio o de garantizar que se estén recibiendo. Estos problemas se manejan normalmente en sistemas de cola de mensajes de los cuales el patrón del observador es solo una pequeña parte.
Patrones relacionados: patrón de publicación-suscripción , mediador , singleton .
Desacoplado
El patrón de observador se puede utilizar en ausencia de publicación-suscripción, como en el caso en el que 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 ); en su lugar, esos observadores deberían utilizar un temporizador. Por lo tanto, en lugar de estar sobrecargado por el mensaje de cambio, el observador hará que la vista represente el estado aproximado del modelo en un intervalo regular. Este modo de observador es particularmente útil para las barras de progreso , donde el progreso de la operación subyacente cambia varias veces por segundo.
Estructura
Diagrama de secuencia y clase UML
En el diagrama de clases de UML anterior , la Subject
clase no actualiza el estado de los objetos dependientes directamente. En cambio, se Subject
refiere a la Observer
interfaz ( update()
) para actualizar el estado, lo que hace que sea Subject
independiente de cómo se actualiza el estado de los objetos dependientes. Las clases Observer1
y Observer2
implementan la Observer
interfaz sincronizando su estado con el estado del sujeto.
Los UML diagrama de secuencia muestra las interacciones en tiempo de ejecución: El Observer1
y Observer2
objetos llaman attach(this)
en Subject1
que se registran. Suponiendo que el estado de Subject1
cambios, se Subject1
invoca notify()
a sí mismo. notify()
llama update()
a los objetos registrados Observer1
y Observer2
, que solicitan los datos modificados ( getState()
) Subject1
para actualizar (sincronizar) su estado.
Diagrama de clases UML
Ejemplo
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 trata cada línea de entrada como un evento. Cuando se proporciona una cadena desde System.in, notifyObservers
se llama al método para notificar a todos los observadores de la ocurrencia del evento, en forma de una invocación de sus métodos de 'actualización'.
Java
import java.util.List ; import java.util.ArrayList ; import java.util.Scanner ;class EventSource { Observador de interfaz pública { actualización nula ( evento String ); } Lista final privada < Observador > observadores = new ArrayList <> (); notificarObservadores privados void ( evento String ) { observadores . forEach ( observer -> observer . update ( evento )); } public void addObserver ( observador observador ) { observadores . agregar ( observador ); } public void scanSystemIn () { Escáner escáner = nuevo Escáner ( Sistema . en ); while ( scanner . hasNextLine ()) { String line = scanner . nextLine (); notificarObservadores ( línea ); } } }
public class ObserverDemo { public static void main ( String [] args ) { System . fuera . println ( "Ingresar texto:" ); EventSource eventSource = nuevo EventSource (); eventSource . addObserver ( evento -> { System . out . println ( "Respuesta recibida:" + evento ); }); eventSource . scanSystemIn (); } }
Groovy
class EventSource { observadores privados = [] notificarObservadores privados ( evento de cadena ) { observadores . cada { it ( evento ) } } void addObserver ( observador ) { observadores + = observador } void scanSystemIn () { var scanner = nuevo Scanner ( System . in ) while ( scanner ) { var line = scanner . nextLine () notifyObservers ( línea ) } } }println 'Ingresar texto:' var eventSource = new EventSource ()eventSource . addObserver { evento -> println "Respuesta recibida: $ evento" }eventSource . scanSystemIn ()
Kotlin
importar java.util.Scannertypealias Observer = ( evento : String ) -> Unidad ;class EventSource { observadores var privados = mutableListOf < Observer > () diversión privada notifyObservers ( evento : String ) { observadores . forEach { it ( evento ) } } divertido addObserver ( observador : observador ) { observadores + = observador } fun scanSystemIn () { val scanner = Scanner ( System . `in` ) while ( scanner . hasNext ()) { val line = scanner . nextLine () notifyObservers ( línea ) } } }
fun main ( arg : List < String > ) { println ( "Ingresar texto:" ) val eventSource = EventSource () eventSource . addObserver { evento -> println ( "Respuesta recibida: $ evento " ) } eventSource . scanSystemIn () }
Delphi
usa System . Genéricos . Colecciones , System . SysUtils ;escriba IObserver = interfaz [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] procedimiento Actualización ( const AValue : string ) ; terminar ;tipo TEdijsObserverManager = clase estrictos servidores FObservers privados : TList < IObserver >; constructor público Create ; sobrecarga ; destructor Destroy ; anular ; procedimiento NotifyObservers ( const AValue : string ) ; procedimiento addObserver ( const AObserver : IObserver ) ; procedimiento UnregisterObsrver ( const AObserver : IObserver ) ; terminar ; escriba TListener = class ( TInterfacedObject , IObserver ) estrictamente privado FName : cadena ; constructor público Create ( const AName : string ) ; reintroducir ; Actualización del procedimiento ( const AValue : string ) ; terminar ; procedimiento TEdijsObserverManager . AddObserver ( const AObserver : IObserver ) ; comience si no FObservers . Contiene ( AObserver ) luego FObservers . Agregar ( AObserver ) ; terminar ;comenzar FreeAndNil ( FObservers ) ; heredado ; terminar ;procedimiento TEdijsObserverManager . NotifyObservers ( const AValue : string ) ; var i : entero ; empezar para i : = 0 a FObservers . Count - 1 do FObservers [ i ] . Actualización ( AValue ) ; terminar ;procedimiento TEdijsObserverManager . UnregisterObsrver ( const AObserver : IObserver ) ; comenzar si FObservers . Contiene ( AObserver ) luego FObservers . Eliminar ( AObserver ) ; terminar ;constructor TListener . Crear ( const AName : string ) ; iniciar la creación heredada ; FName : = AName ; terminar ; procedimiento TListener . Actualizar ( const AValue : string ) ; comenzará WriteLn ( FName + 'detector de notificaciones recibidas:' + AValue ) ; terminar ;procedimiento TEdijsForm . ObserverExampleButtonClick ( remitente : TObject ) ; var _DoorNotify : TEdijsObserverManager ; _ListenerHusband : IObserver ; _ListenerWife : IObserver ; begin _DoorNotify : = TEdijsObserverManager . Crear ; intente _ListenerHusband : = TListener . Crear ( 'Marido' ) ; _DoorNotify . AddObserver ( _ListenerHusband ) ; _ListenerWife : = TListener . Crear ( 'Esposa' ) ; _DoorNotify . AddObserver ( _ListenerWife ) ; _DoorNotify . NotifyObservers ( 'Alguien está llamando a la puerta' ) ; finalmente FreeAndNil ( _DoorNotify ) ; terminar ; terminar ;
Producción
El oyente del esposo recibió una notificación: alguien está llamando a la puertaLa esposa oyente recibió una notificación: alguien está llamando a la puerta
Pitón
Un ejemplo similar en Python :
clase Observable : def __init__ ( self ): self . _observers = [] def register_observer ( self , observer ): self . _observadores . append ( observador ) def notify_observers ( self , * args , ** kwargs ): para obs en self . _observadores : obs . notificar ( self , * args , ** kwargs )class Observer : def __init__ ( self , observable ): observable . register_observer ( auto ) def notificar ( self , observable , * args , ** kwargs ): print ( "Got" , args , kwargs , "From" , observable )sujeto = Observable () observador = Observador ( sujeto ) sujeto . notificar_observadores ( "prueba" , kw = "python" )# impresiones: Got ('test',) {'kw': 'python'} De <__ main __. Objeto observable en 0x0000019757826FD0>
C#
Payload de clase pública { Mensaje de cadena pública { get ; establecer ; } }
clase pública Asunto : IObservable < Payload > { public IList < IObserver < Payload >> Observers { get ; establecer ; } Asunto público () { Observadores = nueva Lista < IObserver < Carga útil >> (); } public IDisposable Subscribe ( IObserver < Payload > observer ) { if (! Observers . Contiene ( observer )) { Observers . Agregar ( observador ); } devolver nuevo Unsubscriber ( Observadores , observador ); } public void SendMessage ( mensaje de cadena ) { foreach ( var observer en Observers ) { observer . OnNext ( nueva carga útil { Mensaje = mensaje }); } } }
público de clase unsubscriber : IDisposable { privado IObserver < Carga > observador ; observadores privados IList < IObserver < Payload >> ; pública unsubscriber ( IList < IObserver < Carga >> observadores , IObserver < Carga > observador ) { esto . observadores = observadores ; esto . observador = observador ; } public void Dispose () { if ( observer ! = null && observers . Contiene ( observer )) { observers . Eliminar ( observador ); } } }
público de clase Observador : IObserver < Carga > { pública cadena de mensajes { get ; establecer ; } public void OnCompleted () { } public void OnError ( error de excepción ) { } public void OnNext ( valor de carga útil ) { Mensaje = valor . Mensaje ; } Registro público de IDisposable ( Asunto asunto ) { asunto de retorno . Suscríbete ( esto ); } }
JavaScript
Javascript tiene una Object.observe
función obsoleta que era una implementación más precisa del patrón Observer. [7] Esto desencadenaría eventos al cambiar el objeto observado. Sin la Object.observe
función obsoleta , un programador aún puede implementar el patrón con un código más explícito: [8]
var Asunto = { _state : 0 , _observers : [], add : function ( observer ) { this . _observadores . empujar ( observador ); }, getState : function () { devuelve esto . _state ; }, setState : function ( value ) { this . _state = valor ; para ( var i = 0 ; i < this . _observers . length ; i ++ ) { this . _observadores [ i ]. señal ( esto ); } } };var Observer = { señal : función ( sujeto ) { var currentValue = sujeto . getState (); consola . log ( valorActual ); } }Asunto . añadir ( observador ); Asunto . setState ( 10 ); // Salida en console.log - 10
Ver también
- Invocación implícita
- Modelo cliente-servidor
- El patrón de observador se usa a menudo en el patrón entidad-componente-sistema
Referencias
- ^ Erich Gamma; Richard Helm; Ralph Johnson; John Vlissides (1994). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison Wesley. págs. 293 y siguientes . ISBN 0-201-63361-2.
- ^ "El patrón de diseño del observador: problema, solución y aplicabilidad" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ Comparación entre diferentes implementaciones de patrones de observador Moshe Bindler, 2015 (Github)
- ^ Diferencias entre pub / sub y patrón de observador El patrón de observador de Adi Osmani (libros de Safari en línea)
- ^ La experiencia de programación de Windows Charles Petzold , 10 de noviembre de 1992, PC Magazine ( Google Books )
- ^ "El patrón de diseño del observador - Estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
- ^ https://stackoverflow.com/a/50862441/887092
- ^ https://stackoverflow.com/a/37403125/887092
enlaces externos
- Implementaciones de observadores en varios idiomas en Wikilibros