A megfigyelő minta egy olyan szoftvertervezési minta, amelyben egy objektum, az úgynevezett alany, fenntartja a tőle függő objektumok listáját, az úgynevezett megfigyelőket, és automatikusan értesíti őket az állapotváltozásokról, általában az egyik függvényük meghívásával.
Elsősorban elosztott eseménykezelő rendszerek megvalósítására használják, "esemény vezérelt" szoftverekben. Ezekben a rendszerekben az alanyt általában "eseményfolyamnak" vagy "eseményforrásnak", míg a megfigyelőket "események tartályának" hívják. Az adatfolyam-nómenklatúra szimulálja vagy adaptálódik egy fizikai beállításhoz, ahol a megfigyelők fizikailag el vannak választva, és nem képesek ellenőrizni a tárgy / adatforrás kibocsátott eseményeit. Ez a minta ezután tökéletesen megfelel minden olyan folyamatnak, ahol az adatok I / O-n keresztül érkeznek, vagyis amikor az adatok indításkor nem állnak a CPU rendelkezésére, de "véletlenszerűen" érkezhetnek (HTTP-kérések, GPIO-adatok, felhasználói bevitel a billentyűzetről / egérről /. .., elosztott adatbázisok és blokkláncok, ...). A legtöbb modern nyelv beépített "esemény" konstrukciókkal rendelkezik, amelyek megvalósítják a megfigyelő minta alkotóelemeit. Noha nem kötelező, a legtöbb „megfigyelő” implementáció háttérszálakat fog használni az alany eseményeinek megfigyelésére és a rendszermag más támogatási mechanizmusait (Linux epoll, ...)
Áttekintés
A Observer tervezési mintája a huszonhárom közismert "Gang of Four" tervezési minták egyike, amelyek leírják, hogy miként oldhatók meg az ismétlődő tervezési problémák egy rugalmas és újrafelhasználható objektumorientált szoftver, azaz olyan objektumok tervezéséhez, amelyek könnyebben megvalósíthatók, megváltoztathatók , tesztelhetők és újrafelhasználhatók. [1]
Milyen problémákat oldhat meg az Observer tervezési minta?
A Megfigyelő mintája a következő problémákkal foglalkozik:[2]
Meg kell határozni az objektumok közötti függőséget anélkül, hogy az objektumokat szorosan összekapcsolnák.
Gondoskodni kell arról, hogy amikor egy objektum megváltozik, a függő objektumok automatikusan frissülnek.
Lehetséges, hogy egy objektum értesíthet más objektumot.
Az objektumok közötti egy a többhöz függőség meghatározása az objektum (alany) meghatározásával, amely közvetlenül frissíti a függő objektumok állapotát, nem rugalmas, mivel összekapcsolja a tárgyat bizonyos függő objektumokkal. Ennek ellenére is értelmezhető lehet teljesítmény szempontjából, vagy ha az objektum implementáció szorosan összekapcsolt (gondoljon az alacsony szintű kernel struktúrákra, amelyek másodpercenként több ezer alkalommal futnak). A szorosan összekapcsolt objektumokat nehéz lehet implementálni egyes forgatókönyvekben, és nehéz újrafelhasználni, mivel sok különböző objektumra és interfészre hivatkoznak, és tudnak róluk (és arról hogyan kell frissíteni őket). Más esetekben szorosan összekapcsolt objektumok használata jobb választás lehet, mivel a fordító képes lesz észlelni hibákat a fordítás idején és optimalizálni a kódot a CPU utasítás szintjén.
Milyen megoldást ír le a Observer tervezési minta?
Definiálja a Subject és az Observer objektumokat.
úgy, hogy amikor egy alany megváltozik, az összes regisztrált megfigyelőt automatikusan (és valószínűleg aszinkron módon) értesítjük és frissítjük.
Az alany kizárólagos felelőssége a megfigyelők listájának vezetése, és az állapotváltozásokról a megfigyelők update() () metódusuk meghívásával történő értesítése.
A megfigyelők felelőssége, hogy regisztráljanak (és töröljék a regisztrációt) egy alanyra (hogy értesüljenek az állapotváltozásokról), és frissítsék állapotukat (szinkronizálják az állapotukat az alany állapotával), amikor értesítést kapnak.
Ez az alanyt és a megfigyelőket lazán párosítja. Az alanynak és a megfigyelőknek nincs kifejezett ismerete egymásról. A megfigyelőket függetlenül lehet hozzáadni és eltávolítani futás közben.
Ez az értesítés-regisztráció interakció közzététel-feliratkozás néven is ismert. publish-subscribe.
Lásd még az UML osztály és sorrend diagrammot lentebb.
Erős vagy gyenge referencia
Az Observer tervezési minta memóriaszivárgást okozhat, amelyet figyelmen kívül hagyott megfigyelő problémaként ismernek, mivel az alapvető megvalósításban explicit regisztrációt és leregisztrálást igényel, mint a dispose mintában, mivel az alany erős hivatkozásokat tartalmaz a megfigyelőkre, életben tartva őket. Ez megakadályozható, ha a vizsgált alany gyenge hivatkozással rendelkezik a megfigyelőkre.
Összekapcsolás és tipikus pub-sub megvalósítások
Általában a megfigyelő mintát úgy valósítják meg, hogy a "megfigyelt" alany azon objektum része, amelynek állapotváltozásait megfigyelik (és a megfigyelőkhöz továbbítják). Az ilyen típusú megvalósítást „szorosan összekapcsoltnak” tekintik, és arra készteti mind a megfigyelőket, mind az alanyokat, hogy tisztában legyenek egymással, és hozzáférjenek belső részeikhez, ezáltal a skálázhatóság, sebesség, az üzenet helyreállítása és karbantartása kérdéseket vet fel (eseménynek vagy értesítésnek is nevezik), a rugalmasság hiánya a feltételes eloszlásban, és a kívánt biztonsági intézkedések lehetséges akadályozása. A közzététel-feliratkozás mintának (más néven a pub-sub mintának) néhány (nem lekérdezéses) megvalósításában ezt egy dedikált "üzenet sor" kiszolgáló (és néha egy "üzenetkezelő" objektum) létrehozásával extra szakaszként oldják meg, a megfigyelő és a megfigyelt tárgy között, ezáltal leválasztva az összetevőket. Ezekben az esetekben az üzenetsor-kiszolgálót érik el a megfigyelők a megfigyelő mintával, "feliratkozva bizonyos üzenetekre", csak a várható üzenetről tudva (vagy bizonyos esetekben nem), miközben semmit sem tudnak magáról az üzenetküldőről; a feladó úgyszint semmit sem tudhat a megfigyelőkről. A közzététel-feliratkozás mintájának más megvalósításai, amelyek az értesítés és az érdekelt felekkel való kommunikáció hasonló hatását eredményezik, egyáltalán nem használják a megfigyelő mintát. [3][4]
A többablakos operációs rendszerek, mint például az OS / 2 és a Windows korai megvalósításában, a "közzétételi-feliratkozási minta" és az "eseményvezérelt szoftverfejlesztés" kifejezéseket használták a megfigyelő minta szinonimájaként.[5]
A GoF-könyvben leírt megfigyelőmintázat nagyon alapvető fogalom, és nem foglalkozik a megfigyelt "alany" változásaival kapcsolatos érdeklődés megszüntetésével vagy a megfigyelt "alany" speciális logikájával, amelyet a megfigyelők értesítése előtt vagy után kell megtenni. A minta nem foglalkozik a rögzítéssel sem, amikor a változási értesítéseket elküldik, vagy nem garantálja, hogy kézhez kapják azokat. Ezeket az aggályokat általában olyan üzenet-sorba rendező rendszerekben kezelik, amelyeknek a megfigyelő minta csak egy kis része.
A megfigyelői minta használható közzététel-feliratkozás hiányában, mint például abban az esetben, ha a modell állapotát gyakran frissítik. A gyakori frissítés miatt előfordulhat, hogy a nézet nem reagál (például sok újrafestési hívás meghívásával); ezeknek a megfigyelőknek inkább egy időzítőt kellene használniuk. Így ahelyett, hogy a változási üzenet túlterhelné, a nézet a megfigyelő miatt rendszeres időközönként megjeleníti a modell hozzávetőleges állapotát. Ez a megfigyelő mód különösen hasznos az előrehaladási sávoknál, ahol az alapul szolgáló művelet haladása másodpercenként többször változik.
Szerkezet
UML osztály és sorrend diagram
A fenti UML osztálydiagramban a Subject osztály nem frissíti közvetlenül a függő objektumok állapotát. Ehelyett a Subject az Observer felületére (update()) utal az állapot frissítéséhez, amely a Subject-et függetlenné teszi a függő objektumok állapotának frissítésétől.
Az Observer1 és az Observer2 osztályok az Observer felületet implementálják azáltal, hogy szinkronizálják az állapotukat az alany állapotával.
Az UML szekvencia diagram a futási idő közötti interakciókat mutatja: Az Observer1 és az Observer2 objektumok meghívják az attach(this)-t a Subject1-re, hogy regisztrálják magukat. Feltételezve, hogy a Subject1 állapota megváltozik, a
Subject1 meghívja a notify()-t önmagán.
a notify() meghívja az update()-et a regisztrált Observer1 és Observer2
objektumokon, amelyek a megváltozott adatokat (getState()) a Subject1-től kérik az állapotuk frissítéséhez (szinkronizálásához).
UML class diagram
Példa
Noha a java.util.Observer és a java.util.Observable könyvtári osztályok léteznek, a Java 9-ben elavultak, mert a megvalósított modell meglehetősen korlátozott volt.
Az alábbiakban bemutatunk egy Java nyelven írt példát, amely veszi a billentyűzet bevitelét, és minden bemeneti sort eseményként kezel. Amikor egy karakterláncot kapunk a System.in-ből, akkor meghívásra kerül a notifyObservers metódus, hogy az összes megfigyelőt értesítsük az esemény bekövetkezéséről, az „update” metódusuk meghívása formájában.
usesSystem.Generics.Collections,System.SysUtils;typeIObserver=interface['{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}']procedureUpdate(constAValue:string);end;typeTEdijsObserverManager=classstrictprivateFObservers:TList<IObserver>;publicconstructorCreate;overload;destructorDestroy;override;procedureNotifyObservers(constAValue:string);procedureAddObserver(constAObserver:IObserver);procedureUnregisterObsrver(constAObserver:IObserver);end;typeTListener=class(TInterfacedObject,IObserver)strictprivateFName:string;publicconstructorCreate(constAName:string);reintroduce;procedureUpdate(constAValue:string);end;procedureTEdijsObserverManager.AddObserver(constAObserver:IObserver);beginifnotFObservers.Contains(AObserver)thenFObservers.Add(AObserver);end;constructorTEdijsObserverManager.Create;begininheritedCreate;FObservers:=TList<IObserver>.Create;end;destructorTEdijsObserverManager.Destroy;beginFreeAndNil(FObservers);inherited;end;procedureTEdijsObserverManager.NotifyObservers(constAValue:string);vari:Integer;beginfori:=0toFObservers.Count-1doFObservers[i].Update(AValue);end;procedureTEdijsObserverManager.UnregisterObsrver(constAObserver:IObserver);beginifFObservers.Contains(AObserver)thenFObservers.Remove(AObserver);end;constructorTListener.Create(constAName:string);begininheritedCreate;FName:=AName;end;procedureTListener.Update(constAValue:string);beginWriteLn(FName+' listener received notification: '+AValue);end;procedureTEdijsForm.ObserverExampleButtonClick(Sender:TObject);var_DoorNotify:TEdijsObserverManager;_ListenerHusband:IObserver;_ListenerWife:IObserver;begin_DoorNotify:=TEdijsObserverManager.Create;try_ListenerHusband:=TListener.Create('Husband');_DoorNotify.AddObserver(_ListenerHusband);_ListenerWife:=TListener.Create('Wife');_DoorNotify.AddObserver(_ListenerWife);_DoorNotify.NotifyObservers('Someone is knocking on the door');finallyFreeAndNil(_DoorNotify);end;end;
Output
Husband listener received notification: Someone is knocking on the door
Wife listener received notification: Someone is knocking on the door
A megfigyelő minta felhasználásához könyvtárak és keretek léteznek a JavaScript számára. Az egyik ilyen könyvtár az alább látható RxJS.
// import the fromEvent operatorimport{fromEvent}from'rxjs';// grab button referenceconstbutton=document.getElementById('myButton');// create an observable of button clicksconstmyObservable=fromEvent(button,'click');// for now, let's just log the event on each clickconstsubscription=myObservable.subscribe(event=>console.log(event));