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 och observatö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

Ett exempel på UML-klass och sekvensdiagram för observatörsdesignmönstret.

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) 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

UML klassdiagram av observatörsmönster

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   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

externa länkar