Padrão Observer
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 94 de 104.
O padrão Observer define uma dependência de um para muitos entre objetos: quando um objeto (o assunto) muda de estado, todos os seus dependentes (os observadores) são notificados automaticamente. Isso é ideal para sistemas de eventos, atualizações de UI ou qualquer cenário em que vários objetos precisem reagir a mudanças.
O padrão envolve dois papéis principais: um Subject que mantém uma lista de observadores e os notifica, e Observers que implementam uma interface de atualização:
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(int value) = 0;
virtual ~Observer() = default;
};
class Subject {
std::vector<Observer*> observers;
int state = 0;
public:
void attach(Observer* obs) {
observers.push_back(obs);
}
void detach(Observer* obs) {
observers.erase(
std::remove(observers.begin(), observers.end(), obs),
observers.end());
}
void setState(int value) {
state = value;
notify();
}
void notify() {
for (Observer* obs : observers) {
obs->update(state);
}
}
};
class Display : public Observer {
std::string name;
public:
Display(const std::string& n) : name(n) {}
void update(int value) override {
std::cout << name << " received: " << value << "\n";
}
};
int main() {
Subject sensor;
Display screen1("Screen1"), screen2("Screen2");
sensor.attach(&screen1);
sensor.attach(&screen2);
sensor.setState(42); // Ambos os displays notificados
}Quando setState() é chamado, o sujeito percorre todos os observadores registrados e chama o método update() deles. Os observadores podem se conectar ou se desconectar a qualquer momento, tornando o sistema flexível e fracamente acoplado.
Use Observer quando alterações em um objeto exigirem a atualização de outros, e você não quiser que esses objetos sejam fortemente acoplados.
Desafio
FácilVamos criar um sistema de monitoramento de Weather Station usando o padrão Observer. Você criará uma estação meteorológica que acompanha dados de temperatura e notifica automaticamente várias unidades de exibição sempre que a temperatura muda: uma aplicação clássica desse padrão no mundo real.
Você organizará seu código em três arquivos:
Observer.h: Defina a interface do observador e um observador de exibição concreto.Crie uma classe abstrata
Observercom um método virtual puroupdate(double temperature)que os observadores implementam para receber notificações, juntamente com um destrutor virtual.Em seguida, crie uma classe
TemperatureDisplayque herde deObserver. Cada exibição tem um nome (string) definido por meio de seu construtor. Quandoupdate()for chamado, ele deverá imprimir:[DisplayName]: Temperature is [temperature] degreesWeatherStation.h: Crie o sujeito que mantém a temperatura e notifica os observadores.Sua classe
WeatherStationdeverá:- Armazenar uma lista de ponteiros para observadores e a temperatura atual (inicializada como
0.0) - Ter um método
attach(Observer* obs)para registrar observadores - Ter um método
detach(Observer* obs)para remover observadores - Ter um método
setTemperature(double temp)que atualiza a temperatura e notifica todos os observadores anexados - Ter um método privado
notify()que chamaupdate()em cada observador com a temperatura atual
- Armazenar uma lista de ponteiros para observadores e a temperatura atual (inicializada como
main.cpp: Demonstre o padrão Observer em ação.Leia três entradas:
- Nome para a primeira exibição (string)
- Nome para a segunda exibição (string)
- Um valor de temperatura (double)
Crie uma
WeatherStatione dois objetosTemperatureDisplaycom os nomes fornecidos. Anexe ambas as exibições à estação e, em seguida, defina a temperatura como o valor de entrada. Ambas as exibições deverão receber automaticamente a atualização e imprimir suas mensagens.Depois disso, desanexe a primeira exibição e defina uma nova temperatura que seja 5 graus maior que a entrada. Somente a segunda exibição deverá receber essa atualização.
Por exemplo, com as entradas Kitchen, Bedroom e 22.5:
Kitchen: Temperature is 22.5 degrees
Bedroom: Temperature is 22.5 degrees
Bedroom: Temperature is 27.5 degreesCom as entradas Office, Lobby e 18.0:
Office: Temperature is 18 degrees
Lobby: Temperature is 18 degrees
Lobby: Temperature is 23 degreesObserve como o padrão Observer cria um sistema fracamente acoplado: a estação meteorológica não precisa saber nada sobre as exibições específicas; ela apenas notifica quem estiver ouvindo. Quando você desanexa um observador, ele deixa de receber atualizações automaticamente.
Experimente você mesmo
#include <iostream>
#include <string>
#include "WeatherStation.h"
using namespace std;
int main() {
// Leia as entradas
string display1Name, display2Name;
double temperature;
cin >> display1Name;
cin >> display2Name;
cin >> temperature;
// TODO: Crie um objeto WeatherStation
// TODO: Crie dois objetos TemperatureDisplay com os nomes de entrada
// TODO: Anexe ambos os displays à estação meteorológica
// TODO: Defina a temperatura para o valor de entrada
// (Ambos os displays devem imprimir suas mensagens)
// TODO: Desanexe o primeiro display
// TODO: Defina a temperatura para 5 graus a mais que a entrada
// (Apenas o segundo display deve imprimir)
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online