Observatörsmönster
Inom mjukvarudesign och konstruktion är observatörsmönstret ett mjukvarudesignmönster där ett objekt , som heter subjektet , upprätthåller en lista över sina anhöriga, kallade observatörer , och meddelar dem automatiskt om eventuella tillståndsändringar , vanligtvis genom att anropa en av deras metoder .
Det används ofta för att implementera distribuerade händelsehanteringssystem i händelsedriven programvara . I sådana system kallas ämnet vanligtvis för en "ström av händelser" eller "strömkälla för händelser" medan observatörerna kallas "sänkor av händelser". Strömnomenklaturen anspelar på en fysisk uppställning där observatörerna är fysiskt åtskilda och inte har någon kontroll över de emitterade händelserna från ämnet/strömkällan. Detta mönster passar alltså alla processer genom vilka data kommer från någon indata som inte är tillgänglig för CPU:n vid start , utan istället anländer till synes slumpmässigt ( HTTP-förfrågningar , GPIO -data, användarinmatning från kringutrustning , distribuerade databaser och blockkedjor , etc.).
De flesta moderna programmeringsspråk omfattar inbyggda händelsekonstruktioner som implementerar observatörsmönsterkomponenterna. Även om det inte är obligatoriskt, använder de flesta observatörsimplementeringar bakgrundstrådar som lyssnar efter ämneshändelser och andra stödmekanismer som tillhandahålls av kärnan.
Översikt
Observatörsdesignmönstret är ett beteendemönster listat bland de 23 välkända designmönstren "Gang of Four" som adresserar återkommande designutmaningar för att designa flexibel och återanvändbar objektorienterad programvara, vilket ger objekt som är lättare att implementera, ändra, testa och återanvändning.
Vilka problem kan observatörsdesignmönstret lösa?
Observatörsmönstret tar upp följande problem:
- Ett ett-till-många-beroende mellan objekt bör definieras utan att göra objekten tätt kopplade.
- När ett objekt ändrar tillstånd, bör ett öppet antal beroende objekt uppdateras automatiskt.
- Ett objekt kan meddela flera andra objekt.
Att definiera ett ett-till-många-beroende mellan objekt genom att definiera ett objekt (ämne) som uppdaterar tillståndet för beroende objekt direkt är oflexibelt eftersom det kopplar ämnet till särskilda beroende objekt. Det kan dock vara tillämpbart ur prestandasynpunkt eller om objektimplementeringen är tätt kopplad (såsom lågnivåkärnstrukturer som körs tusentals gånger per sekund). Tätt kopplade objekt kan vara svåra att implementera i vissa scenarier och är inte lätta att återanvända eftersom de refererar till och är medvetna om många objekt med olika gränssnitt. I andra scenarier kan tätt kopplade objekt vara ett bättre alternativ eftersom kompilatorn kan upptäcka fel vid kompilering och optimera koden på CPU-instruktionsnivå.
Vilken lösning beskriver Observers designmönster?
- Definiera
subjekt
ochobservatörsobjekt
. - När ett ämne ändrar tillstånd meddelas alla registrerade observatörer och uppdateras automatiskt (och förmodligen asynkront).
En subjekts enda ansvar är att upprätthålla en lista över observatörer och att meddela dem om tillståndsändringar genom att anropa deras update()
operation. Observatörernas ansvar är att registrera och avregistrera sig hos en subjekt (för att bli underrättad om tillståndsändringar) och att uppdatera deras tillstånd (för att synkronisera deras tillstånd med subjektets tillstånd) när de underrättas. Detta gör subjekt och observatörer löst kopplade. Subjekt och observatörer har ingen explicit kunskap om varandra. Observatörer kan läggas till och tas bort oberoende av varandra under körning. Denna interaktion mellan meddelande och registrering kallas även publicera-prenumerera .
Stark kontra svag referens
Observatörsmönstret kan orsaka minnesläckor , känt som problem med förfallna lyssnare , eftersom det i en grundläggande implementering kräver både explicit registrering och explicit avregistrering, som i dispose-mönstret , eftersom subjektet har starka referenser till observatörerna och håller dem vid liv. Detta kan förhindras om försökspersonen har svaga referenser till observatörerna.
Koppling och typiska publicera-prenumerera implementeringar
Vanligtvis implementeras observatörsmönstret så att subjektet som observeras är en del av objektet för vilket tillståndsförändringar observeras (och kommuniceras till observatörerna). Denna typ av implementering anses vara tätt kopplad , vilket tvingar både observatörerna och försökspersonen att vara medvetna om varandra och ha tillgång till deras interna delar, vilket skapar möjliga problem med skalbarhet , hastighet, meddelandeåterställning och underhåll (även kallat händelse- eller meddelandeförlust) , bristen på flexibilitet i villkorlig spridning och eventuellt hinder för önskade säkerhetsåtgärder. I vissa ( icke-polling ) implementeringar av publicerings-prenumerationsmönstret löses detta genom att skapa en dedikerad meddelandeköserver (och ibland ett extra meddelandehanterarobjekt) som ett extra steg mellan observatören och objektet som observeras, och därmed frikoppla komponenter. I dessa fall nås meddelandeköservern av observatörerna med observatörsmönstret, prenumererar på vissa meddelanden och känner till (eller inte vet, i vissa fall) om endast det förväntade meddelandet, samtidigt som de inte vet något om själva meddelandesändaren; avsändaren kanske inte heller vet något om observatörerna. Andra implementeringar av publicera-prenumerera-mönstret, som uppnår en liknande effekt av meddelande och kommunikation till berörda parter, använder inte observatörsmönstret.
I tidiga implementeringar av operativsystem med flera fönster som OS/2 och Windows , användes termerna "publicera-prenumerera-mönster" och "händelsedriven mjukvaruutveckling" som synonymer för observatörsmönstret.
Observatörsmönstret, som beskrivs i Design Patterns- boken, är ett mycket grundläggande koncept och tar inte upp intresset för förändringar av det observerade objektet eller speciell logik som ska utföras av den observerade personen innan eller efter att observatörerna meddelats. Mönstret handlar inte heller om att registrera ändringsmeddelanden eller garantera att de tas emot. Dessa problem hanteras vanligtvis i meddelandekösystem, där observatörsmönstret endast spelar en liten roll.
Relaterade mönster inkluderar publicera-prenumerera, mediator och singleton .
Okopplad
Observatörsmönstret kan användas i avsaknad av publicera-prenumerera, som när modellstatus uppdateras ofta. Frekventa uppdateringar kan göra att vyn inte svarar (t.ex. genom att anropa många ommålningssamtal ); sådana observatörer bör istället använda en timer. Istället för att bli överbelastad av ändringsmeddelande kommer observatören att få vyn att representera modellens ungefärliga tillstånd med ett regelbundet intervall. Det här observationsläget är särskilt användbart för förloppsindikatorer , där den underliggande operationens förlopp ändras ofta.
Strukturera
UML klass och sekvensdiagram
I detta UML- klassdiagram uppdaterar inte Subject -
klassen tillståndet för beroende objekt direkt. Istället Subject
till Observer-
gränssnittet ( update()
) för uppdateringstillstånd, vilket gör Subject
oberoende av hur tillståndet för beroende objekt uppdateras. Klasserna Observer1
och Observer2
implementerar Observer-
gränssnittet genom att synkronisera deras tillstånd med subjektets tillstånd.
UML - sekvensdiagrammet visar körtidsinteraktionerna: Objekten Observer1
och Observer2
anropar attach(this)
på Subject1
för att registrera sig själva. Förutsatt att tillståndet för Ämne1
ändras, anropar Ämne1
notify()
på sig själv. notify()
anropar update()
på de registrerade Observer1-
och Observer2
-objekten, som begär att de ändrade data ( getState()
) från Subject1
ska uppdatera (synkronisera) deras tillstånd.
UML klassdiagram
Exempel
Även om biblioteksklasserna java.util.Observer
och java.util.Observable
existerar, har de fasats ut i Java 9 eftersom den implementerade modellen var ganska begränsad.
Nedan är ett exempel skrivet i Java som tar tangentbordsinmatning och hanterar varje inmatningsrad som en händelse. När en sträng tillhandahålls från System.in anropas sedan
metoden notifyObservers()
för att meddela alla observatörer om händelsen, i form av en anrop av deras uppdateringsmetoder.
Java
importera java.util.List ; importera java.util.ArrayList ; importera java.util.Scanner ; gränssnitt Observer { void update ( String event ); } class EventSource { List < Observer > observers = new ArrayList <> (); void notifyObservers ( String event ) { observers . forEach ( observatör -> observatör . uppdatering ( händelse )); } void addObserver ( Observer observer ) { observers . add ( observatör ); } void scanSystemIn () { var scanner = new Scanner ( System . in ); while ( scanner . hasNextLine ()) { String line = scanner . nästa rad (); notifyObservers ( rad ); } } } public class ObserverDemo { public static void main ( String [] args ) { System . ut . println ( "Ange text: " ); var eventSource = new EventSource (); eventSource . addObserver ( event -> { System . out . println ( "Mottaget svar: " + händelse ); }); eventSource . scanSystemIn (); } }
Häftig
class EventSource { private observers = [] private notifyObservers ( String event ) { observers . varje { it ( event ) } } void addObserver ( observer ) { observers += observer } void scanSystemIn ( ) { var scanner = new Scanner ( System . in ) while ( scanner ) { var line = scanner . nextLine () notifyObservers ( line ) } } } println 'Enter Text: ' var eventSource = new EventSource () eventSource . addObserver { event -> println "Mottaget svar: $event" } eventSource . scanSystemIn ()
Kotlin
import java.util.Scanner typealias Observer = ( händelse : String ) -> Enhet ; class EventSource { private var observers = mutableListOf < Observer > () private fun notifyObservers ( event : String ) { observers . forEach { it ( event ) } } fun addObserver ( observer : Observer ) { observers += observer } fun scanSystemIn () { val scanner = Scanner ( System . `in` ) while ( scanner . hasNext ()) { val line = scanner . nextLine () notifyObservers ( rad ) } } }
fun main ( arg : Lista < String > ) { println ( "Enter Text: " ) val eventSource = EventSource () eventSource . addObserver { event -> println ( "Mottaget svar: $ event " ) } eventSource . scanSystemIn () }
Delphi
0
använder System . Generika . Samlingar , System . SysUtils ; typ IObserver = gränssnitt [ '{0C8F4C5D- 1898-4F24-91DA -63F1DD66A692}' ] procedur Update ( const AValue : string ) ; slut ; typ TObserverManager = klass privata FObservrar : TList < IObserver >; offentlig konstruktör Skapa ; överbelastning ; förstörare Destroy ; åsidosätta ; procedur NotifyObservers ( const AValue : string ) ; procedur AddObserver ( const AObserver : IObserver ) ; procedur UnregisterObsrver ( const AObserver : IObserver ) ; slut ; typ TListener = klass ( TInterfacedObject , IObserver ) privat FName : string ; public constructor Create ( const AName : string ) ; återinföra ; procedur Update ( const AValue : string ) ; slut ; procedur TObserverManager . AddObserver ( const AObserver : IObserver ) ; börja om inte FObservers . Innehåller ( AObserver ) sedan FObservers . Lägg till ( AObserver ) ; slut ; börja FreeAndNil ( FObservers ) ; ärvt ; slut ; procedur TObserverManager . NotifyObservers ( const AValue : string ) ; var i : Heltal ; börja för i := till FObservers . Räkna - 1 gör FObservers [ i ] . Uppdatering ( AValue ) ; slut ; procedur TObserverManager . UnregisterObsrver ( const AObserver : IObserver ) ; börja om FObservers . Innehåller ( AObserver ) sedan FObservers . Ta bort ( AObserver ) ; slut ; konstruktör TListener . Skapa ( const AName : string ) ; börja ärvt Skapa ; FName := AName ; slut ; procedur TListener . Uppdatering ( const AValue : sträng ) ; börja WriteLn ( FName + ' lyssnaren fick meddelande: ' + AValue ) ; slut ; procedur TMyForm . ObserverExampleButtonClick ( Sändare : TObject ) ; var LDoorNotify : TObserverManager ; LListenerHusband : IObserver ; LListenerWife : IObserver ; börja LDoorNotify := TObserverManager . Skapa ; prova LListenerHusband := TListener . Skapa ( 'Man' ) ; LDoorNotify . AddObserver ( LListenerHusband ) ; LListenerWife := TListener . Skapa ( 'Hustru' ) ; LDoorNotify . AddObserver ( LListenerWife ) ; LDoorNotify . NotifyObservers ( 'Någon knackar på dörren') ; slutligen FreeAndNil ( LDoorNotify ) ; slut ; slut ;
Produktion
Make-lyssnare fick meddelande: Någon knackar på dörren Hustru-lyssnare fick meddelande: Någon knackar på dörren
Pytonorm
Ett liknande exempel i Python :
klass Observerbar : def __init__ ( själv ): själv . _observatörer = [] def register_observer ( jag , observatör ): själv . _observatörer . append ( observatör ) def notify_observers ( själv , * args , ** kwargs ): för observationer i jaget . _observatörer : obs . notify ( själv , * args , ** kwargs ) klass Observer : def __init__ ( själv , observerbar ): observerbar . register_observer ( self ) def notify ( self , observable , * args , ** kwargs ): print ( "Got" , args , kwargs , "From" , observable ) subject = Observerbar () observatör = Observer ( subjekt ) subjekt . notify_observers ( "test" , kw = "python" ) # utskrifter: Fick ('test',) {'kw': 'python'} från <__main__.Observable object at 0x0000019757826FD0>
C#
public class Payload { public string Message { get ; set ; } } public class Ämne : IObservable < Payload > { public ICollection < IObserver < Payload >> Observers { get ; set ; } public Subject () { Observers = new List < IObserver < Payload >>(); } public ID Disposable Prenumerera ( IObserver < Payload > observer ) { if (! Observers . Contains ( observer )) { Observers . Lägg till ( observatör ); } returnera ny Avanmälare ( observatör , observatörer ); } public void SendMessage ( strängmeddelande ) { foreach ( var observer i Observers ) { observer . _ OnNext ( ny nyttolast { Message = message }); } } } public class Unsubscriber : IDisposable { private IObserver < Payload > observer ; privat IList < IObserver < Nyttolast >> observatörer ; public Unsubscriber ( IObserver < Payload > observer , IList < IObserver < Payload >> observers ) { this . observatör = observatör ; detta . observers = observers ; } public void Dispose () { if ( observer != null && observers . Innehåller ( observer )) { observers . Ta bort ( observatör ); } } } public class Observer : IObserver < Payload > { public string Message { get ; set ; } public void OnCompleted ( ) { } public void OnError ( Undantagsfel ) { } public void OnNext ( nyttolastvärde ) { Message = value . _ Meddelande ; } public ID Disposable Register ( Ämnesämne ) { returnera ämne . Prenumerera ( detta ); } }
JavaScript
JavaScript har en föråldrad Object.observe-
funktion som var en mer exakt implementering av observatörsmönstret. Detta skulle utlösa händelser vid förändring av det observerade objektet. Utan den föråldrade Object.observe-
funktionen kan mönstret implementeras med mer explicit kod:
0
0
låt Subjekt = { _tillstånd :, _observatörer : [ ], add : funktion ( observatör ) { detta . _observatörer . push ( observatör ); }, getState : function () { returnera detta . _stat ; }, setState : funktion ( värde ) { detta . _state = värde ; för ( låt i = ; i < detta . _observatörer . längd ; i ++ ) { detta . _observatörer [ i ]. signal ( denna ); } } }; let Observer = { signal : function ( subject ) { let currentValue = subject . getState (); konsol . log ( aktuellt värde ); } } Ämne . add ( observatör ); Ämne . setState ( 10 ); //Utdata i console.log - 10
Se även
- Implicit anrop
- Klient-server-modell
- Observatörsmönstret används ofta i mönstret entitet–komponent–system
externa länkar
- Observatörsimplementeringar på olika språk på Wikibooks