Wzór obserwatora
W projektowaniu i inżynierii oprogramowania wzorzec obserwatora to wzorzec projektowy oprogramowania , w którym obiekt , nazwany podmiotem , utrzymuje listę swoich zależnych, zwanych obserwatorami , i powiadamia ich automatycznie o wszelkich zmianach stanu , zwykle poprzez wywołanie jednej z ich metod .
Jest często używany do implementacji rozproszonych systemów obsługi zdarzeń w oprogramowaniu sterowanym zdarzeniami . W takich systemach podmiot jest zwykle nazywany „strumieniem zdarzeń” lub „źródłem strumienia zdarzeń”, podczas gdy obserwatorzy nazywani są „pojemnikami zdarzeń”. Nazewnictwo strumienia nawiązuje do fizycznej konfiguracji, w której obserwatorzy są fizycznie oddzieleni i nie mają kontroli nad zdarzeniami emitowanymi przez podmiot/źródło strumienia. Ten wzorzec pasuje zatem do każdego procesu, w którym dane przychodzą z jakiegoś wejścia, które nie jest dostępne dla procesora podczas uruchamiania , ale zamiast tego pojawia się pozornie losowo ( żądania HTTP , dane GPIO , dane wprowadzane przez użytkownika z urządzeń peryferyjnych , rozproszone bazy danych i łańcuchy bloków itp.).
Większość współczesnych języków programowania zawiera wbudowane konstrukcje zdarzeń implementujące komponenty wzorca obserwatora. Chociaż nie jest to obowiązkowe, większość implementacji obserwatora wykorzystuje wątki w tle nasłuchujące zdarzeń podmiotu i inne mechanizmy wsparcia zapewniane przez jądro.
Przegląd
Wzorzec projektowy obserwatora to wzorzec behawioralny wymieniony wśród 23 dobrze znanych wzorców projektowych „Gang of Four” , które odpowiadają na powtarzające się wyzwania projektowe w celu zaprojektowania elastycznego i wielokrotnego użytku zorientowanego obiektowo oprogramowania, dając obiekty, które są łatwiejsze do wdrożenia, zmiany, testowania i ponownie użyć.
Jakie problemy może rozwiązać wzorzec projektowy obserwatora?
Wzorzec obserwatora rozwiązuje następujące problemy:
- Zależność jeden-do-wielu między obiektami powinna być zdefiniowana bez ścisłego wiązania obiektów.
- Kiedy jeden obiekt zmienia stan, nieograniczona liczba zależnych obiektów powinna zostać automatycznie zaktualizowana.
- Obiekt może powiadamiać wiele innych obiektów.
Definiowanie zależności jeden-do-wielu między obiektami poprzez zdefiniowanie jednego obiektu (podmiotu), który bezpośrednio aktualizuje stan obiektów zależnych, jest nieelastyczne, ponieważ łączy podmiot z określonymi obiektami zależnymi. Może to jednak mieć zastosowanie z punktu widzenia wydajności lub jeśli implementacja obiektu jest ściśle powiązana (na przykład struktury jądra niskiego poziomu, które są wykonywane tysiące razy na sekundę). Ściśle powiązane obiekty mogą być trudne do zaimplementowania w niektórych scenariuszach i nie są łatwe do ponownego wykorzystania, ponieważ odwołują się i są świadome wielu obiektów z różnymi interfejsami. W innych scenariuszach ściśle powiązane obiekty mogą być lepszą opcją, ponieważ kompilator jest w stanie wykryć błędy w czasie kompilacji i zoptymalizować kod na poziomie instrukcji procesora.
Jakie rozwiązanie opisuje wzorzec projektowy Obserwator?
- Zdefiniuj obiekty
Podmiot
iObserwator
. - Kiedy podmiot zmienia stan, wszyscy zarejestrowani obserwatorzy są powiadamiani i aktualizowani automatycznie (i prawdopodobnie asynchronicznie).
Wyłącznym obowiązkiem podmiotu jest utrzymywanie listy obserwatorów i powiadamianie ich o zmianach stanu poprzez wywołanie ich operacji update() .
Obowiązkiem obserwatorów jest rejestracja i wyrejestrowanie się z podmiotu (w celu powiadamiania o zmianach stanu) oraz aktualizacja swojego stanu (w celu zsynchronizowania ich stanu ze stanem podmiotu), gdy są powiadamiani. To sprawia, że podmiot i obserwatorzy są luźno powiązani. Podmiot i obserwatorzy nie mają o sobie wyraźnej wiedzy. Obserwatorzy mogą być dodawani i usuwani niezależnie w czasie wykonywania. Ta interakcja polegająca na powiadomieniu i rejestracji jest również nazywana publikowaniem i subskrybowaniem .
Silna kontra słaba referencja
Wzorzec obserwatora może powodować wycieki pamięci , znane jako problem zagubionego słuchacza , ponieważ w podstawowej implementacji wymaga zarówno jawnej rejestracji, jak i wyraźnego wyrejestrowania, jak we wzorcu usuwania , ponieważ podmiot ma silne odniesienia do obserwatorów, utrzymując ich przy życiu. Można temu zapobiec, jeśli podmiot ma słabe odniesienia do obserwatorów.
Sprzężenie i typowe implementacje publikowania i subskrybowania
Zazwyczaj wzorzec obserwatora jest realizowany w taki sposób, że obserwowany podmiot jest częścią obiektu, dla którego obserwowane są zmiany stanu (i komunikowane obserwatorom). Ten rodzaj implementacji jest uważany za ściśle powiązany , zmuszający zarówno obserwatorów, jak i podmiot do bycia świadomymi siebie nawzajem i dostępu do ich wewnętrznych części, co stwarza możliwe problemy ze skalowalnością , szybkością, odzyskiwaniem wiadomości i konserwacją (zwane również utratą zdarzeń lub powiadomień) , brak elastyczności w warunkowym rozproszeniu i możliwe przeszkody dla pożądanych środków bezpieczeństwa. W niektórych ( bez głosowania ) implementacje wzorca publikuj-subskrybuj , można to rozwiązać, tworząc dedykowany serwer kolejki komunikatów (a czasem dodatkowy obiekt obsługi komunikatów) jako dodatkowy etap między obserwatorem a obserwowanym obiektem, oddzielając w ten sposób komponenty. W takich przypadkach dostęp do serwera kolejki komunikatów mają obserwatorzy z wzorcem obserwatora, subskrybujący określone komunikaty i wiedzący (lub nie wiedzący w niektórych przypadkach) tylko o oczekiwanej wiadomości, nie wiedząc nic o samym nadawcy wiadomości; nadawca może również nic nie wiedzieć o obserwatorach. Inne implementacje wzorca publikuj-subskrybuj, które osiągają podobny efekt powiadomienia i komunikacji z zainteresowanymi stronami, nie wykorzystują wzorca obserwatora.
We wczesnych implementacjach wielookienkowych systemów operacyjnych, takich jak OS/2 i Windows , terminy „wzorzec publikowania-subskrybowania” i „tworzenie oprogramowania sterowane zdarzeniami” były używane jako synonimy wzorca obserwatora.
Wzorzec obserwatora, jak opisano w książce Design Patterns , jest bardzo podstawową koncepcją i nie odnosi się do usuwania zainteresowania zmianami w obserwowanym obiekcie lub specjalnej logiki, którą ma wykonać obserwowany podmiot przed lub po powiadomieniu obserwatorów. Wzorzec nie zajmuje się również rejestrowaniem powiadomień o zmianach ani gwarantowaniem ich otrzymania. Problemy te są zwykle rozwiązywane w systemach kolejkowania wiadomości, w których wzorzec obserwatora odgrywa tylko niewielką rolę.
Pokrewne wzorce obejmują publikowanie-subskrybowanie, mediator i singleton .
Niesprzężony
Wzorzec obserwatora może być używany w przypadku braku publikowania-subskrybowania, na przykład w przypadku częstej aktualizacji statusu modelu. Częste aktualizacje mogą spowodować, że widok przestanie odpowiadać (np. przez wywoływanie wielu odświeżania ); tacy obserwatorzy powinni zamiast tego używać stopera. Zamiast zostać przeciążonym komunikatem o zmianie, obserwator spowoduje, że widok będzie reprezentował przybliżony stan modelu w regularnych odstępach czasu. Ten tryb obserwatora jest szczególnie przydatny w przypadku pasków postępu , w których często zmienia się postęp podstawowej operacji.
Struktura
Diagram klas i sekwencji UML
Na tym diagramie klas UML klasa Subject
nie aktualizuje bezpośrednio stanu obiektów zależnych. Zamiast tego podmiot
odwołuje się do interfejsu obserwatora
( update()
) w celu aktualizacji stanu, co sprawia, że podmiot
jest niezależny od tego, w jaki sposób aktualizowany jest stan obiektów zależnych. Klasy Observer1
i Observer2
implementują interfejs Observer
poprzez synchronizację swojego stanu ze stanem podmiotu.
Diagram sekwencji UML przedstawia interakcje w czasie wykonywania: Obiekty Observer1
i Observer2
wywołują attach(this)
na obiekcie Subject1
, aby się zarejestrować. Zakładając, że stan podmiotu Subject1
ulegnie zmianie, podmiot Subject1
wywołuje na sobie funkcję notify() .
notify()
wywołuje funkcję update()
na zarejestrowanych obiektach Observer1
i Observer2
, które żądają zmienionych danych ( getState()
) z Podmiot1
w celu aktualizacji (zsynchronizowania) swojego stanu.
Diagram klas UML
Przykład
Chociaż istnieją klasy biblioteczne java.util.Observer
i java.util.Observable
, w Javie 9 zostały one uznane za przestarzałe , ponieważ zaimplementowany model był dość ograniczony.
Poniżej znajduje się przykład napisany w Javie , który pobiera dane wejściowe z klawiatury i obsługuje każdą linię wejściową jako zdarzenie. Kiedy łańcuch jest dostarczany z System.in
, wówczas wywoływana jest metoda notifyObservers()
w celu powiadomienia wszystkich obserwatorów o wystąpieniu zdarzenia w formie wywołania ich metod aktualizacji.
Jawa
importuj java.util.List ; importuj java.util.ArrayList ; import java.util.Scanner ; interfejs Obserwator { pusta aktualizacja ( zdarzenie String ); } class EventSource { Lista < Obserwator > obserwatorzy = nowa ArrayList <> (); void notifyObservers ( zdarzenie łańcuchowe ) {
obserwatorzy . forEach ( obserwator -> obserwator . aktualizacja ( zdarzenie )); } void addObserver ( Obserwator obserwator ) { obserwatorzy . dodaj ( obserwator ); } void scanSystemIn () { var skaner = nowy skaner ( System . in ); podczas gdy (
skaner . hasNextLine ()) { String line = skaner . następna linia (); notifyObservers ( linia ); } } } public class ObserverDemo { public static void main ( String [] args ) { System . na zewnątrz println ( "Podaj tekst:" ); rozm
Źródło zdarzenia = nowe Źródło zdarzenia (); źródło zdarzenia . addObserver ( zdarzenie -> { System . out . println ( "Otrzymano odpowiedź: " + zdarzenie ); }); źródło zdarzenia . scanSystemIn (); } }
Groovy
class EventSource { prywatnych obserwatorów = [] prywatnych notifyObservers ( zdarzenie String ) { obserwatorów . each { to ( zdarzenie ) } } void addObserver ( obserwator ) { obserwatorzy += obserwator } void scanSystemIn () { var skaner = nowy skaner
( System . in ) while ( skaner ) { var linia = skaner . nextLine () notifyObservers ( line ) } } } println 'Wpisz tekst: ' var eventSource = new EventSource () eventSource . addObserver { event -> println "Otrzymano odpowiedź: $event"
} Źródło zdarzenia . scanSystemIn ()
Kotlin
import java.util.Scanner typealias Observer = ( zdarzenie : String ) -> Jednostka ; class EventSource { private var obserwatorzy = mutableListOf < Obserwator > () prywatna zabawa notifyObservers ( zdarzenie : String ) { obserwatorzy . forEach { to ( zdarzenie
) } } zabawa addObserver ( obserwator : Obserwator ) { obserwatorzy + = obserwator } zabawa scanSystemIn () { val scanner = Scanner ( System . `in` ) while ( scanner . hasNext ()) { val line = skaner . nextLine () powiadomi Obserwatorów
( linia ) } } }
fun main ( arg : List < String > ) { println ( "Wpisz tekst: " ) val eventSource = EventSource () eventSource . addObserver { event -> println ( „Otrzymano odpowiedź: $ event ” ) } eventSource . scanSystemIn () }
Delfy
korzysta z Systemu . Generyki . Kolekcje , system . SysUtils ; type IObserver = interface [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] procedura Update ( const AValue : string ) ; koniec ; wpisz TObserverManager = klasa prywatna FObservers : TList < IObserver
>; konstruktor publiczny Utwórz ; przeciążenie ; destruktor Zniszcz ; zastąpić ; procedura NotifyObservers ( const AValue : string ) ; procedura AddObserver ( const AObserver : IObserver ) ; procedura UnregisterObsrver ( const AObserver : IObserver ) ; koniec
; wpisz TListener = class ( TItinterfacedObject , IObserver ) private FName : string ; publiczny konstruktor Utwórz ( const AName : string ) ; ponownie wprowadzić ; aktualizacja procedury ( const AValue : string ) ; koniec ; procedura TObserverManager .
AddObserver ( const AObserver : IObserver ) ; rozpocznij jeśli nie FObservers . Zawiera ( AObserver ) następnie FObservers . Dodaj ( AObserver ) ; koniec ; rozpocząć FreeAndNil ( FObservers ) ; odziedziczony ; koniec ; procedura TObserverManager . Powiadom Obserwatorów (
0
const AValue : string ) ; zmienna i : liczba całkowita ; zacznij od i := do FObservers . Count - 1 do FObservers [ i ] . Aktualizuj ( wartość AV ) ; koniec ; procedura TObserverManager . UnregisterObsrver ( const AObserver : IObserver )
; rozpocznij , jeśli FObservers . Zawiera ( AObserver ) następnie FObservers . Usuń ( AObserver ) ; koniec ; konstruktor TListener . Utwórz ( const ANazwa : ciąg ) ; rozpocząć dziedziczone Utwórz ; FNazwa := NAzwa ; koniec ; procedura TListener .
Aktualizacja ( const AValue : string ) ; begin WriteLn ( FName + 'słuchacz otrzymał powiadomienie:' + AValue ) ; koniec ; procedura TMyForm . ObserverExampleButtonClick ( Sender : TObject ) ; var LDoorNotify : TObserverManager ; LListenerHusband : IObserver ;
LListenerWife : IObserver ; rozpocząć LDoorNotify := TObserverManager . Utwórz ; spróbuj LListenerHusband := TListener . Utwórz ( „Mąż” ) ; LDoorNotify . AddObserver ( LListenerHusband ) ; LLsłuchaczŻona := TLsłuchacz . Utwórz ( „Żona” ) ; LDoorNotify .
AddObserver ( LListenerWife ) ; LDoorNotify . NotifyObservers ( „Ktoś puka do drzwi” ) ; wreszcie FreeAndNil ( LDoorNotify ) ; koniec ; koniec ;
Wyjście
Słuchacz męża otrzymał powiadomienie: Ktoś puka do drzwi Słuchacz żony otrzymał powiadomienie: Ktoś puka do drzwi
Pyton
Podobny przykład w Pythonie :
klasa Obserwowalna : def __init__ ( self ): self . _observers = [] def register_observer ( self , obserwator ): self . _obserwatorzy . append ( obserwator ) def notify_observers ( self , * args , ** kwargs ): for obs in self
. _obserwatorzy : obs . powiadomić ( self , * args , ** kwargs ) klasa Obserwator : def __init__ ( self , obserwowalny ): obserwowalny . register_observer ( self ) def powiadamia ( self , obserwowalne , * args , ** kwargs ):
print ( "Got" , args , kwargs , "From" , obserwowalny ) podmiot = Obserwowalny () obserwator = Obserwator ( podmiot ) podmiot . notify_observers ( "test" , kw = "python" ) # drukuje: Got ('test',) {'kw': 'python'} From <__main__.Observable object at 0x0000019757826FD0>
C#
klasa publiczna Ładunek { public string Wiadomość { pobierz ; zestaw ; } } public class Temat : IObservable < Ładunek > { public ICollection < IObserver < Ładunek >> Obserwatorzy { get ; zestaw ; } podmiot publiczny () { Obserwatorzy
= nowa lista < IObserver < Ładunek >>(); } public IDisposable Subscribe ( IObserver < Ładunek > obserwator ) { if (! Obserwatorzy . Zawiera ( obserwator )) { Obserwatorzy . Dodaj ( obserwator ); } zwróć nowy Anuluj subskrypcję ( obserwator ,
obserwatorzy ); } public void SendMessage ( wiadomość łańcuchowa ) { foreach ( var obserwator w Obserwatorzy ) { obserwator . OnNext ( nowy ładunek { Wiadomość = wiadomość }); } } } public class Rezygnacja z subskrypcji : IDisposable { private IObserver <
Ładunek > obserwator ; private IList < IObserver < Ładunek >> obserwatorzy ; public Unsubscriber ( IObserver < Ładunek > obserwator , IList < IObserver < Ładunek >> obserwatorzy ) { this . obserwator = obserwator ; to . obserwatorzy
= obserwatorzy ; } public void Usuń () { if ( obserwator != null && obserwatorzy . Zawiera ( obserwator )) { obserwatorzy . Usuń ( obserwator ); } } } public class Observer : IObserver < Payload > { public string Wiadomość
{ dostać ; zestaw ; } public void OnCompleted () { } public void OnError ( błąd wyjątku ) { } public void OnNext ( wartość ładunku ) { Wiadomość = wartość . Wiadomość ; } public IDisposable Register ( Temat podmiotu ) {
temat zwrotu . Zapisz się ( to ); } }
JavaScript
JavaScript ma przestarzałą funkcję Object.observe
, która była dokładniejszą implementacją wzorca obserwatora. Spowodowałoby to uruchomienie zdarzeń po zmianie obserwowanego obiektu. Bez przestarzałej Object.observe
wzorzec można zaimplementować z bardziej jawnym kodem:
0
niech Temat = { _stan : , _obserwatorzy : [] , dodaj : funkcja ( obserwator ) { this . _obserwatorzy . pchać ( obserwator ); }, getState : function () { zwróć to . _stan ; }, setState : function ( value ) { this
0
. _stan = wartość ; for ( niech i = ; i < this . _obserwatorzy . długość ; i ++ ) { this . _obserwatorzy [ i ]. sygnał ( to ); } } }; niech Obserwator = { sygnał : funkcja ( podmiot ) {
niech bieżąca wartość = temat . pobierz stan (); konsola . dziennik ( aktualnaWartość ); } } Temat . dodaj ( obserwator ); Temat . ustaw stan ( 10 ); //Wyjście w console.log - 10
Zobacz też
- Niejawne wywołanie
- Model klient-serwer
- Wzorzec obserwatora jest często używany we wzorcu jednostka-komponent-system
Linki zewnętrzne
- Implementacje obserwatorów w różnych językach w Wikibooks