Observer tervezési minta

A Wikipédiából, a szabad enciklopédiából

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ó objektum-orientá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 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[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 sample UML class and sequence diagram for the Observer design pattern. [6]

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]

UML class diagram of Observer pattern

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]

  1. Erich Gamma. Design Patterns: Elements of Reusable Object-Oriented Software. Addison Wesley, 293ff. o. (1994). ISBN 0-201-63361-2 
  2. The Observer design pattern - Problem, Solution, and Applicability. w3sDesign.com . (Hozzáférés: 2017. augusztus 12.)
  3. Comparison between different observer pattern implementations Moshe Bindler, 2015 (Github)
  4. Differences between pub/sub and observer pattern The Observer Pattern by Adi Osmani (Safari books online)
  5. The Windows Programming Experience Charles Petzold, Nov 10, 1992, PC Magazine (Google Books)
  6. The Observer design pattern - Structure and Collaboration. w3sDesign.com . (Hozzáférés: 2017. augusztus 12.)