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 i Obserwator .
  • 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

Przykładowy diagram klas UML i sekwencji dla wzorca projektowego obserwatora.

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

UML wzorca Observer

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ż

Linki zewnętrzne