Wzór obserwatora - Observer pattern

Wzór obserwator jest wzorzec projektowania oprogramowania , w którym obiekt , nazwany przedmiot , utrzymuje listę jego utrzymaniu, zwanych obserwatorów i powiadamia je automatycznie o wszelkich zmianach stanu, zwykle poprzez wywołanie jednej z ich metod .

Jest używany głównie do wdrażania rozproszonych systemów obsługi zdarzeń , w oprogramowaniu „sterowanym zdarzeniami”. W tych systemach podmiot jest zwykle nazywany „strumieniem wydarzeń” lub „strumieniem źródeł wydarzeń”, podczas gdy obserwatorzy nazywani są „zalewami wydarzeń”. Nomenklatura strumieni nawiązuje do fizycznej konfiguracji, w której obserwatorzy są fizycznie oddzieleni i nie mają kontroli nad emitowanymi zdarzeniami z podmiotu/źródła strumienia. Ten wzorzec doskonale pasuje do każdego procesu, w którym dane przychodzą z pewnych danych wejściowych, które nie są dostępne dla procesora podczas uruchamiania, ale zamiast tego docierają „losowo” (żądania HTTP, dane GPIO, dane wprowadzane przez użytkownika z klawiatury/myszy/..., rozproszone bazy danych i blockchain, ...). 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 "obserwatorów" używałaby wątków w tle nasłuchujących tematycznych zdarzeń i innych mechanizmów wsparcia zapewnianych przez jądro (Linux epoll , ...).

Przegląd

Wzorzec projektowy Observer to jeden z dwudziestu trzech dobrze znanych wzorców projektowych „Gang Czterech” opisujących sposób rozwiązywania powtarzających się wyzwań projektowych w celu zaprojektowania elastycznego i wielokrotnego użytku oprogramowania obiektowego, tj. obiektów łatwiejszych do wdrożenia, zmiany, przetestować i ponownie wykorzystać.

Jakie problemy może rozwiązać wzorzec projektowy Observer?

Wzorzec Obserwator rozwiązuje następujące problemy:

  • Należy zdefiniować zależność jeden-do-wielu między obiektami, nie powodując, że obiekty są ściśle powiązane.
  • Należy zadbać o to, aby w przypadku zmiany stanu jednego obiektu nieograniczona liczba obiektów zależnych była automatycznie aktualizowana.
  • Powinno być możliwe, że jeden obiekt może powiadomić nieograniczoną liczbę 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 konkretnymi obiektami zależnymi. Mimo to może to mieć sens z punktu widzenia wydajności lub jeśli implementacja obiektu jest ściśle powiązana (pomyśl o niskopoziomowych strukturach jądra, które wykonują się tysiące razy na sekundę). Obiekty ściśle powiązane mogą być trudne do zaimplementowania w niektórych scenariuszach i trudne do ponownego użycia, ponieważ odwołują się do wielu różnych obiektów z różnymi interfejsami i wiedzą o nich (i jak je aktualizować). W innych scenariuszach ściśle powiązane obiekty mogą być lepszą opcją, ponieważ kompilator będzie w stanie wykryć błędy w czasie kompilacji i zoptymalizować kod na poziomie instrukcji procesora.

Jakie rozwiązanie opisuje wzorzec projektowy Observer?

  • Zdefiniuj Subjecti Observerobiekty.
  • tak, że gdy podmiot zmienia stan, wszyscy zarejestrowani obserwatorzy są powiadamiani i aktualizowani automatycznie (i prawdopodobnie asynchronicznie).

Jedynym obowiązkiem podmiotu jest prowadzenie listy obserwatorów i powiadamianie ich o zmianach stanu poprzez wywołanie ich update()operacji. Obowiązkiem obserwatorów jest zarejestrowanie się (i wyrejestrowanie) się z podmiotu (aby otrzymywać powiadomienia o zmianach stanu) oraz aktualizowanie swojego stanu (synchronizacja stanu ze stanem podmiotu) po otrzymaniu powiadomienia. To sprawia, że ​​podmiot i obserwatorzy są luźno ze sobą związani. Podmiot i obserwatorzy nie mają wyraźnej wiedzy o sobie. Obserwatorzy można dodawać i usuwać niezależnie w czasie wykonywania. Ta interakcja między powiadomieniem a rejestracją jest również nazywana publikacją-subskrypcją .

Zobacz także diagram klas i sekwencji UML poniżej.

Silne kontra słabe odniesienie

Wzorzec obserwatora może powodować wycieki pamięci , znany jako problem z lapsed listener , ponieważ w podstawowej implementacji wymaga zarówno jawnej rejestracji, jak i jawnego wyrejestrowania, tak jak we wzorcu dispose , ponieważ podmiot posiada silne odniesienia do obserwatorów, utrzymując ich przy życiu. Można temu zapobiec, jeśli podmiot ma słabe odniesienia do obserwatorów.

Coupling i typowe wdrożenia pub-sub

Zazwyczaj wzorzec obserwatora jest implementowany tak, że „obserwowany” „obiekt” jest częścią obiektu, dla którego obserwowane są zmiany stanu (i przekazywane obserwatorom). Ten rodzaj implementacji jest uważany za „ ściśle powiązany ”, zmuszając zarówno obserwatorów, jak i podmiot do wzajemnej świadomości i dostępu do swoich wewnętrznych części, stwarzając możliwe problemy skalowalności , szybkości, odtwarzania i utrzymania wiadomości (zwane również zdarzeniem lub powiadomieniem). straty), brak elastyczności w rozproszeniu warunkowym i możliwe utrudnienia w stosowaniu pożądanych środków bezpieczeństwa. W niektórych ( nie odpytujących ) implementacjach wzorca publikuj-subskrybuj (aka wzorca pub-sub ), jest to rozwiązane przez utworzenie dedykowanego serwera „kolejki wiadomości” (i czasami dodatkowego obiektu „obsługi wiadomości”) jako dodatkowego etapu między obserwatorem a obserwowanym obiektem, tym samym oddzielając komponenty. W takich przypadkach do serwera kolejki komunikatów dostęp mają obserwatorzy za pomocą wzorca obserwatora, „subskrybując pewne komunikaty”, wiedząc tylko o oczekiwanym komunikacie (lub nie, w niektórych przypadkach), nie wiedząc nic o samym nadawcy komunikatu; nadawca może również nic nie wiedzieć o obserwatorach. Inne implementacje wzorca publikuj-subskrybuj, które osiągają podobny efekt powiadamiania i komunikacji z zainteresowanymi stronami, w ogóle nie wykorzystują wzorca obserwator.

We wczesnych implementacjach wielookienkowych systemów operacyjnych, takich jak OS/2 i Windows, terminy „wzorzec publikowania i subskrybowania” i „opracowywanie oprogramowania sterowane zdarzeniami” były używane jako synonim wzorca obserwatora.

Wzorzec obserwatora, jak opisano w książce GoF , jest bardzo podstawową koncepcją i nie odnosi się do usuwania zainteresowania zmianami obserwowanego „podmiotu” lub specjalnej logiki, które mają być wykonane przez obserwowany „podmiot” przed lub po powiadomieniu obserwatorów. Wzorzec nie zajmuje się również rejestrowaniem wysyłania powiadomień o zmianach ani gwarantowaniem ich otrzymania. Te problemy są zwykle obsługiwane w systemach kolejkowania wiadomości, których wzorzec obserwatora jest tylko niewielką częścią.

Wzorce pokrewne: wzorzec publikowania-subskrypcji , mediator , singleton .

Rozłączony

Wzorzec obserwatora może być używany w przypadku braku publikowania-subskrypcji, jak w przypadku, gdy status modelu jest często aktualizowany. Częste aktualizacje mogą spowodować, że widok przestanie odpowiadać (np. przez wywołanie wielu wywołań odświeżania ); tacy obserwatorzy powinni zamiast tego używać timera. W ten sposób zamiast być 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 , gdzie postęp podstawowej operacji zmienia się kilka razy na sekundę.

Struktura

Diagram klas i sekwencji UML

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

W powyższym UML diagram klas The Subjectklasa nie bezpośrednio zaktualizować stan obiektów zależnych. Zamiast tego Subjectodnosi się do Observerinterfejsu ( update()) do aktualizacji stanu, który czyni Subjectniezależnym od tego, jak stan obiektów zależnych jest aktualizowany. Observer1I Observer2klasy implementują Observerinterfejs poprzez synchronizację ich stan ze stanem pacjenta.

W UML sekwencji diagram przedstawia interakcje wykonywania czasu: Observer1a Observer2obiekty nazwać attach(this)na Subject1zarejestrować się. Zakładając, że stan Subject1zmian sam się Subject1wzywa notify().
notify()odwołuje update()się do zarejestrowanych Observer1i Observer2 obiektów, które żądają zmienionych danych ( getState()), Subject1aby zaktualizować (zsynchronizować) ich stan.

Diagram klas UML

Diagram klas UML wzorca Observer

Przykład

Chociaż klasy biblioteczne java.util.Observer i java.util.Observable istnieją, zostały one przestarzałe w Javie 9, ponieważ zaimplementowany model był dość ograniczony.

Poniżej znajduje się przykład napisany w Javie, który pobiera dane wejściowe z klawiatury i traktuje każdy wiersz wejściowy jako zdarzenie. Gdy łańcuch jest dostarczany z System.in, metoda notifyObserversjest następnie wywoływana, aby powiadomić wszystkich obserwatorów o wystąpieniu zdarzenia, w formie wywołania ich metod aktualizacji.

Jawa

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));
    }
  
    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();
    }
}

Groovy

class EventSource {
    private observers = []

    private notifyObservers(String event) {
        observers.each { 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 "Received response: $event"
}

eventSource.scanSystemIn()

Kotlin

import java.util.Scanner

typealias Observer = (event: String) -> Unit;

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(line)
        }
    }
}
fun main(arg: List<String>) {
    println("Enter Text: ")
    val eventSource = EventSource()

    eventSource.addObserver { event ->
        println("Received response: $event")
    }

    eventSource.scanSystemIn()
}

Delfy

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;


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;

Wyjście

Husband listener received notification: Someone is knocking on the door
Wife listener received notification: Someone is knocking on the door

Pyton

Podobny przykład w Pythonie :

class Observable:
    def __init__(self):
        self._observers = []

    def register_observer(self, observer):
        self._observers.append(observer)

    def notify_observers(self, *args, **kwargs):
        for obs in self._observers:
            obs.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", kw="python")

# prints: Got ('test',) {'kw': 'python'} From <__main__.Observable object at 0x0000019757826FD0>

C#

    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(
            IObserver<Payload> observer,
            IList<IObserver<Payload>> observers)
        {
            this.observer = observer;
            this.observers = observers;
        }

        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

JavaScript ma przestarzałą Object.observefunkcję, która była dokładniejszą implementacją wzorca Observer. To wywołałoby zdarzenia po zmianie obserwowanego obiektu. Bez przestarzałej Object.observefunkcji programista może nadal zaimplementować wzorzec z bardziej jawnym kodem:

let Subject = {
    _state: 0,
    _observers: [],
    add: function(observer) {
        this._observers.push(observer);
    },
    getState: function() {
        return this._state;
    },
    setState: function(value) {
        this._state = value;
        for (let i = 0; i < this._observers.length; i++)
        {
            this._observers[i].signal(this);
        }
    }
};

let Observer = {
    signal: function(subject) {
        let currentValue = subject.getState();
        console.log(currentValue);
    }
}

Subject.add(Observer);
Subject.setState(10);
//Output in console.log - 10

Zobacz też

Bibliografia

Zewnętrzne linki