Padrão do observador - Observer pattern

O padrão de observador é um padrão de design de software no qual um objeto , denominado assunto , mantém uma lista de seus dependentes, chamados de observadores , e os notifica automaticamente sobre quaisquer mudanças de estado, geralmente chamando um de seus métodos .

É usado principalmente para implementar sistemas de manipulação de eventos distribuídos , em software "orientado a eventos". Nesses sistemas, o assunto geralmente é denominado "fluxo de eventos" ou "fonte de fluxo de eventos", enquanto os observadores são chamados de "coletores de eventos". A nomenclatura de fluxo alude a uma configuração física onde os observadores estão fisicamente separados e não têm controle sobre os eventos emitidos pelo sujeito / fonte do fluxo. Esse padrão se adapta perfeitamente a qualquer processo em que os dados chegam de alguma entrada que não está disponível para a CPU na inicialização, mas chegam "ao acaso" (solicitações HTTP, dados GPIO, entrada do usuário do teclado / mouse / ..., bancos de dados distribuídos e blockchains, ...). A maioria das linguagens de programação modernas compreende construções de "evento" integradas que implementam os componentes do padrão do observador. Embora não seja obrigatório, a maioria das implementações de 'observadores' usaria threads em segundo plano ouvindo eventos de assunto e outros mecanismos de suporte fornecidos pelo kernel (Linux epoll , ...).

Visão geral

O padrão de design Observer é um dos vinte e três padrões de design "Gang of Four" bem conhecidos que descrevem como resolver desafios de design recorrentes para projetar software orientado a objetos flexível e reutilizável, ou seja, objetos que são mais fáceis de implementar, alterar, teste e reutilize.

Que problemas o padrão de design Observer pode resolver?

O padrão Observer trata dos seguintes problemas:

  • Uma dependência um-para-muitos entre os objetos deve ser definida sem torná-los fortemente acoplados.
  • Deve-se garantir que, quando um objeto muda de estado, um número aberto de objetos dependentes seja atualizado automaticamente.
  • Deve ser possível que um objeto possa notificar um número aberto de outros objetos.

Definir uma dependência um-para-muitos entre objetos definindo um objeto (sujeito) que atualiza o estado de objetos dependentes diretamente é inflexível porque acopla o sujeito a objetos dependentes específicos. Ainda assim, pode fazer sentido do ponto de vista do desempenho ou se a implementação do objeto estiver fortemente acoplada (pense em estruturas de kernel de baixo nível que são executadas milhares de vezes por segundo). Objetos fortemente acoplados podem ser difíceis de implementar em alguns cenários e difíceis de reutilizar porque eles se referem a e sabem sobre (e como atualizar) muitos objetos diferentes com interfaces diferentes. Em outros cenários, objetos fortemente acoplados podem ser uma opção melhor, pois o compilador será capaz de detectar erros em tempo de compilação e otimizar o código no nível de instrução da CPU.

Que solução o padrão de design do Observer descreve?

  • Defina Subjecte Observerobjetos.
  • de forma que quando um assunto muda de estado, todos os observadores registrados são notificados e atualizados automaticamente (e provavelmente de forma assíncrona).

A única responsabilidade de um sujeito é manter uma lista de observadores e notificá-los das mudanças de estado, ligando para sua update()operação. A responsabilidade dos observadores é registrar-se (e cancelar o registro) em um assunto (para serem notificados sobre mudanças de estado) e atualizar seu estado (sincronizar seu estado com o estado do assunto) quando forem notificados. Isso torna o sujeito e os observadores vagamente acoplados. Sujeito e observadores não têm conhecimento explícito um do outro. Os observadores podem ser adicionados e removidos independentemente em tempo de execução. Essa interação de notificação-registro também é conhecida como publicar-assinar .

Consulte também a classe UML e o diagrama de sequência abaixo.

Referência forte vs. fraca

O padrão do observador pode causar vazamentos de memória , conhecidos como o problema do listener lapsed , porque em uma implementação básica, ele requer tanto o registro explícito quanto o cancelamento do registro explícito, como no padrão dispose , porque o assunto mantém fortes referências aos observadores, mantendo-os vivos. Isso pode ser evitado se o assunto tiver referências fracas aos observadores.

Acoplamento e implementações pub-sub típicas

Normalmente, o padrão do observador é implementado de forma que o "sujeito" sendo "observado" faça parte do objeto para o qual as mudanças de estado estão sendo observadas (e comunicadas aos observadores). Esse tipo de implementação é considerada " fortemente acoplada ", obrigando tanto o observador quanto o sujeito a se conhecerem e terem acesso às suas partes internas, criando possíveis problemas de escalabilidade , velocidade, recuperação e manutenção de mensagens (também chamado de evento ou notificação perda), a falta de flexibilidade na dispersão condicional e possível obstáculo às medidas de segurança desejadas. Em algumas implementações ( não-polling ) do padrão publicar-assinar (também conhecido como padrão pub-sub ), isso é resolvido criando um servidor de "fila de mensagens" dedicado (e às vezes um objeto "gerenciador de mensagens" extra) como um estágio extra entre o observador e o objeto em observação, desacoplando assim os componentes. Nesses casos, o servidor de fila de mensagens é acessado pelos observadores com o padrão observador, "assinando certas mensagens" sabendo apenas sobre a mensagem esperada (ou não, em alguns casos), sem saber nada sobre o próprio remetente da mensagem; o remetente também pode não saber nada sobre os observadores. Outras implementações do padrão publicar-assinar, que alcançam um efeito semelhante de notificação e comunicação às partes interessadas, não usam o padrão do observador de forma alguma.

Nas primeiras implementações de sistemas operacionais de múltiplas janelas, como OS / 2 e Windows, os termos "padrão publicar-assinar" e "desenvolvimento de software orientado a eventos" foram usados ​​como sinônimos para o padrão do observador.

O padrão do observador, conforme descrito no livro GoF , é um conceito muito básico e não aborda a remoção do interesse em mudanças no "sujeito" observado ou lógica especial a ser feita pelo "sujeito" observado antes ou depois de notificar os observadores. O padrão também não trata do registro quando as notificações de mudança são enviadas ou da garantia de que estão sendo recebidas. Essas preocupações são normalmente tratadas em sistemas de enfileiramento de mensagens, dos quais o padrão do observador é apenas uma pequena parte.

Padrões relacionados: padrão publicar – assinar , mediador , singleton .

Desacoplado

O padrão do observador pode ser usado na ausência de publicação-assinatura, como no caso em que o status do modelo é atualizado com frequência. Atualizações frequentes podem fazer com que a visualização pare de responder (por exemplo, ao invocar muitas chamadas de redesenho ); Em vez disso, esses observadores devem usar um cronômetro. Assim, em vez de ser sobrecarregado pela mensagem de mudança, o observador fará com que a visualização represente o estado aproximado do modelo em um intervalo regular. Este modo de observador é particularmente útil para barras de progresso , onde o progresso da operação subjacente muda várias vezes por segundo.

Estrutura

Classe UML e diagrama de sequência

Um exemplo de classe UML e diagrama de sequência para o padrão de design Observer.

No diagrama de classes UML acima , a Subjectclasse não atualiza o estado dos objetos dependentes diretamente. Em vez disso, Subjectrefere-se à Observerinterface ( update()) para atualizar o estado, o que torna Subjectindependente de como o estado dos objetos dependentes é atualizado. As classes Observer1e Observer2implementam a Observerinterface sincronizando seu estado com o estado do sujeito.

Os UML de sequência diagrama mostra as interações de tempo de execução: O Observer1e Observer2objectos exigem attach(this)em Subject1registar-se. Supondo que o estado de Subject1mudança, Subject1chama notify()a si mesmo.
notify()chamadas update()nos objetos Observer1e registrados Observer2, que solicitam os dados alterados ( getState()) de Subject1para atualizar (sincronizar) seu estado.

Diagrama de classe UML

Diagrama de classe UML do padrão Observer

Exemplo

Embora as classes de biblioteca java.util.Observer e java.util.Observable existam, elas foram descontinuadas no Java 9 porque o modelo implementado era bastante limitado.

Abaixo está um exemplo escrito em Java que usa a entrada do teclado e trata cada linha de entrada como um evento. Quando uma string é fornecida de System.in, o método notifyObserversé então chamado, a fim de notificar todos os observadores da ocorrência do evento, na forma de uma invocação de seus métodos de 'atualização'.

Java

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

Delphi

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;

Saída

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

Pitão

Um exemplo semelhante em Python :

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 tem uma Object.observefunção obsoleta que era uma implementação mais precisa do padrão Observer. Isso dispararia eventos ao alterar o objeto observado. Sem a Object.observefunção obsoleta , um programador ainda pode implementar o padrão com um código mais explícito:

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

Veja também

Referências

links externos