Observer tervezési minta
Ezt a szócikket össze kellene dolgozni a Megfigyelő programtervezési minta szócikkel. |
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
[szerkeszté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?
[szerkesztés]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?
[szerkesztés]- Definiálja a
Subject
és azObserver
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
[szerkesztés]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
[szerkesztés]Á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.
Related patterns: Publish–subscribe pattern, mediator, singleton.
Szétkapcsolt
[szerkesztés]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
[szerkesztés]UML osztály és sorrend diagram
[szerkesztés]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
[szerkesztés]Példa
[szerkesztés]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.
Java
[szerkesztés]import java.util.List;
import java.util.ArrayList;
import java.util.Scanner;
class EventSource {
public interface Observer {
void update(String event);
}
private final List<Observer> observers = new ArrayList<>();
private void notifyObservers(String event) {
observers.forEach(observer -> observer.update(event)); //alternative lambda expression: observers.forEach(Observer::update);
}
public void addObserver(Observer observer) {
observers.add(observer);
}
public void scanSystemIn() {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
notifyObservers(line);
}
}
}
public class ObserverDemo {
public static void main(String[] args) {
System.out.println("Enter Text: ");
EventSource eventSource = new EventSource();
eventSource.addObserver(event -> {
System.out.println("Received response: " + event);
});
eventSource.scanSystemIn();
}
}
Kotlin
[szerkesztés]import java.util.Scanner
typealias Observer = (event: String) -> Unit;
class EventSource {
private val 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(line)
}
}
}
fun main(arg: List<String>) {
println("Enter Text: ")
val eventSource = EventSource()
eventSource.addObserver { event ->
println("Received response: $event")
}
eventSource.scanSystemIn()
}
Delphi
[szerkesztés]uses
System.Generics.Collections
, System.SysUtils
;
type
IObserver = interface
['{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}']
procedure Update(const AValue: string);
end;
type
TEdijsObserverManager = class
strict private
FObservers: TList<IObserver>;
public
constructor Create; overload;
destructor Destroy; override;
procedure NotifyObservers(const AValue: string);
procedure AddObserver(const AObserver: IObserver);
procedure UnregisterObsrver(const AObserver: IObserver);
end;
type
TListener = class(TInterfacedObject, IObserver)
strict private
FName: string;
public
constructor Create(const AName: string); reintroduce;
procedure Update(const AValue: string);
end;
procedure TEdijsObserverManager.AddObserver(const AObserver: IObserver);
begin
if not FObservers.Contains(AObserver) then
FObservers.Add(AObserver);
end;
constructor TEdijsObserverManager.Create;
begin
inherited Create;
FObservers := TList<IObserver>.Create;
end;
destructor TEdijsObserverManager.Destroy;
begin
FreeAndNil(FObservers);
inherited;
end;
procedure TEdijsObserverManager.NotifyObservers(const AValue: string);
var
i: Integer;
begin
for i := 0 to FObservers.Count - 1 do
FObservers[i].Update(AValue);
end;
procedure TEdijsObserverManager.UnregisterObsrver(const AObserver: IObserver);
begin
if FObservers.Contains(AObserver) then
FObservers.Remove(AObserver);
end;
constructor TListener.Create(const AName: string);
begin
inherited Create;
FName := AName;
end;
procedure TListener.Update(const AValue: string);
begin
WriteLn(FName + ' listener received notification: ' + AValue);
end;
procedure TEdijsForm.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');
finally
FreeAndNil(_DoorNotify);
end;
end;
Output
Husband listener received notification: Someone is knocking on the door Wife listener received notification: Someone is knocking on the door
Python
[szerkesztés]Egy hasonló példa Python-ban Python:
class Observable:
def __init__(self):
self._observers = []
def register_observer(self, observer):
self._observers.append(observer)
def notify_observers(self, *args, **kwargs):
for observer in self._observers:
observer.notify(self, *args, **kwargs)
class Observer:
def __init__(self, observable):
observable.register_observer(self)
def notify(self, observable, *args, **kwargs):
print('Got', args, kwargs, 'From', observable)
subject = Observable()
observer = Observer(subject)
subject.notify_observers('test')
C#
[szerkesztés] public class Payload
{
public string Message { get; set; }
}
public class Subject : IObservable<Payload>
{
public IList<IObserver<Payload>> Observers { get; set; }
public Subject()
{
Observers = new List<IObserver<Payload>>();
}
public IDisposable Subscribe(IObserver<Payload> observer)
{
if (!Observers.Contains(observer))
{
Observers.Add(observer);
}
return new Unsubscriber(Observers, observer);
}
public void SendMessage(string message)
{
foreach (var observer in Observers)
{
observer.OnNext(new Payload { Message = message });
}
}
}
public class Unsubscriber : IDisposable
{
private IObserver<Payload> observer;
private IList<IObserver<Payload>> observers;
public Unsubscriber(IList<IObserver<Payload>> observers, IObserver<Payload> observer)
{
this.observers = observers;
this.observer = observer;
}
public void Dispose()
{
if (observer != null && observers.Contains(observer))
{
observers.Remove(observer);
}
}
}
public class Observer : IObserver<Payload>
{
public string Message { get; set; }
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(Payload value)
{
Message = value.Message;
}
public IDisposable Register(Subject subject)
{
return subject.Subscribe(this);
}
}
JavaScript
[szerkesztés]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 operator
import { fromEvent } from 'rxjs';
// grab button reference
const button = document.getElementById('myButton');
// create an observable of button clicks
const myObservable = fromEvent(button, 'click');
// for now, let's just log the event on each click
const subscription = myObservable.subscribe(event => console.log(event));
További információk
[szerkesztés]- ↑ Erich Gamma. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley, 293ff. o. (1994). ISBN 0-201-63361-2
- ↑ The Observer design pattern - Problem, Solution, and Applicability. w3sDesign.com . (Hozzáférés: 2017. augusztus 12.)
- ↑ Comparison between different observer pattern implementations Moshe Bindler, 2015 (Github)
- ↑ Differences between pub/sub and observer pattern The Observer Pattern by Adi Osmani (Safari books online)
- ↑ The Windows Programming Experience Charles Petzold, Nov 10, 1992, PC Magazine (Google Books)
- ↑ The Observer design pattern - Structure and Collaboration. w3sDesign.com . (Hozzáférés: 2017. augusztus 12.)