Executando verificação de segurança...
1

Observer Pattern

  • Este é um pattern comportamental
  • Ele é chamadado por outros nomes também, como: Pub/Sub, Event-Subscribe, Listener
  • É um mecanismo de notificação para vários objetos sobre qualquer evento em um objeto observado
  • Este padrão é descrito em duas partes: Quem observa os eventos e quem é observável.

O que é o Observer

  • É alguém que observa algo e reage baseado nas ações de quem ele observa
  • Ele define que o eventos que reagir de quem observa
class Observer {
	public handler: (data: any) => Promise<void>;
	
	constructor(
		public readonly eventName: string,
		handler: (data: any) => Promise<void>
	) {
		this.handler = handler;
	}
}

O que é um Observable

  • Algo que é observável por alguém externo a ele, que é o observador
  • Possue diversos observadores
  • Ele notifica os observadores
  • Ele registrar quem quer observá-lo
class Observable {
	private _observers: Observer[];

	// registra novos observadores
	addObserver(observer: Observer) {
		this._observers.push(observer);
	}

	// notifica todos os observadores que esperam um determinado evento
	notify(eventName: string, data: any) {
		this._observers
				.filter((observer) => observer.eventName === eventName)
				.forEach((observer) => observer.handler(data));
	}
}

Exemplo 1

  • Eu tenho uma entidade Usuário que podemos observar mudanças no seu estado e/ou ações realizadas
  • Então ela herdas os comportamentos necessários que a torna observável (Observable )
  • Mas antes eu defino quais evento esta entidade realiza
const EVENTS = {
	INSTANCE_USER: 'instance-user',
	UPDATE_USER_NAME: 'update-user-name',
	UPDATE_USER_EMAIL: 'update-user-email',
}

class User extends Observable {
	public name: string;
	public email: string;
	
	constructor(name: string, email: string, instanceEvent?: Observer) {
		super();
		this.name = name;
		this.email = email;
		
		if (instanceEvent) {
			this.addObserver(instanceEvent);
		}
		
		this.notify(EVENTS.INSTANCE_USER, this);
	}
	
	update(name?: string, email?: string) {
		const previous = { ...this }
		
		this.name = name || this.name;
		this.email = email || this.email;
		
		if (email)
			this.notify(EVENTS.UPDATE_USER_EMAIL, { previous: previous.email, current: email });
			
		if (name)
			this.notify(EVENTS.UPDATE_USER_NAME, { previous: previous.name, current: name });
	}
}
  • O próximo passo é criar os observadores desses eventos e registrá-los na em quem quero observar os evento, no caso nossa entidade User
const userInstancedEvent = new Observer(EVENTS.INSTANCE_USER, async (user: User) => {
	console.log(`User ${user.name} created!`);
});

const userEmailUpdatedEvent = new Observer(EVENTS.UPDATE_USER_EMAIL, async (data:{ previous: string, current: string }) => {
	console.log(`User ${data.previous} updated to ${data.current}!`);
});

const userNameUpdatedEvent = new Observer(EVENTS.UPDATE_USER_NAME, async (data: { previous: string, current: string }) => {
	console.log(`User ${data.previous} updated to ${data.current}!`);
});
  • userInstancedEvent: Observar quando um usuário for instanceado e mostar no console o nome dele

  • userEmailUpdatedEvent: Observar quando um usuário alterar seu email e mostar no console

  • userNameUpdatedEvent: Observar quando um usuário alterar seu nome e mostar no console

  • Já temos o cenário para para criar um usuário e reagir aos eventos dele

const user = new User('John Doe', '[email protected]', userInstancedEvent);

user.addObserver(userNameUpdatedEvent);
user.addObserver(userEmailUpdatedEvent);

user.update('Melk de Sousa')
user.update(undefined, '[email protected]')
user.update(undefined, '[email protected]')
  • O resultado em nosso console é...
User John Doe created!
User John Doe updated to Melk de Sousa!
User [email protected] updated to [email protected]!
User [email protected] updated to [email protected]!

Exemplo 2

  • Todo software, praticamente, tem a funcionalidade de realizar um Login.
  • Mas imagine que é preciso tomar alguma decisão quando um login falha ou tem sucesso
  • O Observer Pattern pode ser muito bem aplicável
type Input = {
    username: string;
    password: string;
};

export class LoginUseCase extends Observable {
    async execute({ username, password }: Input) {
        try {
            // do something
            this.notify("success", undefined);
        } catch (error) {
            this.notify("fail", undefined);
        }
    }
}
  • Agora é possível criar os observers de quando o login falhar e tiver sucesso
export const loginSuccess = new Observer('success', async () => {
    console.log('Login success');
});

export const loginFail = new Observer('fail', async () => {
    console.log('Login fail');
})
  • Com os observers e o observable definidos, só é preciso fazer o registro deles no Login
const loginUseCase = new LoginUseCase();

loginUseCase.addObserver(loginFail)
loginUseCase.addObserver(loginSuccess)

loginUseCase.execute({ username: 'admin', password: 'admin' });
  • A partir desse momento, toda vez que um login tiver sucesso, será mostrado uma mensagem no console. O mesmo se aplica em caso de falha.
  • Mas dependendo do cenário, é possível aplicar trativas melhores que isso, como enviar para um serviço externo de logs, filas, etc.

Conclusão

  • É incrível as possibilidades que esse pattern no proporciona
  • Não é atoa sua presença na maioria dos sistemas e framworks
  • Vale a pena ver o cenários que pode ser aplicado em suas aplicações, bem como o seu tradeoff

Referências

Carregando publicação patrocinada...
1
1
1

Muito bacana o conteúdo @melksouza top vei!
E bacana o observer porque ele consegue lidar com "n" tipos de eventos e trabalhar bem com notificação das alterações de estado... Como tu falou, e um pattern sensacional! ;-)
raiz d POO...

Pow... aproveita a vibe... faz um post do pattern de command... é outro pattern muito massa e complementa bem com obvserver vei...

parabéns pelo material!

1