sábado, 4 de setembro de 2010

Design Pattern Observer

Salve!

Continuando a série de posts sobre design pattern um assunto muito interessante para desenvolvimento e projeto de softwares: Design Patterns.

O que é design pattern ?

Design Pattern
ou padrões de projetos de software são soluções catalogadas para problemas recorrentes. Existem diversos deles, sendo que cada um tem seu objetivo, que pode ser reuso, performance, manutenibilidade, etc.

Neste post pretendo mostrar um padrão de projeto bastante difundido, o Observer.

O padrão Observer é muito utilizado em diversas linguagens e frameworks. Um exemplo é na manipulação de eventos em java, outro é o "esquema" de plugins do Zend Framework - nele é possível registrar plugins que são notificados sobre a execução da aplicação.

Objetivo

O objetivo do padrão Observer é promover o baixo acoplamento (Low-coupling) entre vários objetos. O baixo acoplamento é desejável em qualquer projeto de software, pois possibilita melhor manutenibilidade e reuso entre os objetos.

Como Funciona?

Um objeto responsável por realizar alguma tarefa notifica a todos os objetos registrados que um certo evento ocorreu.

Observe o modelo abaixo:

Observer Design Pattern

Abaixo um exemplo em PHP



interface ICrudObserver {
public function beforeInsert(ICrudObservable $observable);
public function afterInsert(ICrudObservable $observable);
public function beforeUpdate(ICrudObservable $observable);
public function afterUpdate(ICrudObservable $observable);
public function beforeDelete(ICrudObservable $observable);
public function afterDelete(ICrudObservable $observable);
}
/*
Interface dos objetos que notificam os observadores
*/
interface ICrudObservable {
public function addObserver(ICrudObserver $observer);
public function removeObserver(ICrudObserver $observer);
}




Provavelmente, alguns podem estar se perguntando: "ué...as interfaces não definem o modelo apresentado em UML?". De fato o exemplo em código não é exatamente o mesmo do modelo em UML, afinal o Design Pattern serve de referência para entendimento. Na aplicação prática podem haver algumas alterações, no entanto o seu princípio continua o mesmo.

No modelo apresentado em UML, existe um método que notifica os observadores: o notify. Já no exemplo em código, existem três métodos que o farão, são eles: insert, update e delete. Além disso, no modelo apresentado existe apenas um método nos observadores, que é o update. Já no exemplo em código, existem seis: beforeInsert, afterInsert, beforeUpdate, afterUpdate, beforeDelete e afterDelete. Mas, como foi dito, o princípio é o mesmo, ou seja, um objeto observado informa aos observadores que algo aconteceu.


Vejamos em seguida como implementar nossas classes.


class CrudUsuario implements ICrudObservable {
public $nome;
protected $_observers = array();
public function addObserver(ICrudObserver $observer) {
$this->_observers[] = $observer;
}
public function removeObserver(ICrudObserver $observer) {
$pos = array_search($observer,$this->_observers);
if($pos !== false) {
unset($this->_observers[$pos]);
}
}
/*
Regra de inserção
*/
public function insert() {
/*Notifica todos observadores*/
foreach($this->_observers as $observer) {
$observer->beforeInsert($this);
}
/*
...
//logica de inserção aqui
*/
//notifica observadores
foreach($this->_observers as $observer) {
$observer->afterInsert($this);
}
}
/*
Regra de atualização
*/
public function update() {
/*Notifica todos observadores*/
foreach($this->_observers as $observer) {
$observer->beforeUpdate($this);
}
/*
...
//logica de atualização aqui
*/
//notifica observadores
foreach($this->_observers as $observer) {
$observer->afterUpdate($this);
}
}
public function delete() {
/*Notifica todos observadores*/
foreach($this->_observers as $observer) {
$observer->beforeDelete($this);
}
/*
...
//logica de exclusão aqui
*/
//notifica observadores
foreach($this->_observers as $observer) {
$observer->afterDelete($this);
}
}
}





Com isso, temos um esqueleto de uma implementação simples do padrão observer.

Toda vez que uma operação de insert, delete ou update for chamada, dois métodos serão chamados em cada um dos observadores registrados. Isso será útil se eu quiser, por exemplo, criar um log de inserção, atualização e exclusão de usuários, sem precisar alterar o código da classe de crud que poderia já estar em produção, testado e estável. Para isso, basta criar uma classe para gravar os logs e registrá-la nos objetos (para criar esses objetos poderíamos utilizar 'Builder' para simplificar o registro desses observadores, mas isso fica para um outro post).

Esse modelo permite construir softwares baseados nos princípios S.O.L.I.D, que garante melhor qualidade, com manutenibilidade mais fácil e reuso.

Abaixo segue um exemplo de código que poderia ser utilizado para criar logs:


class FileLogObserver implements ICrudObserver{

protected $_file = 'meu-log.txt';
protected function _appendLog($msg){
$p = fopen($this->_file,'a');
fwrite($p, "$msg\n");
fclose($p);
}

public function afterDelete(ICrudObservable $observable) {
if($observable instanceof CrudUsuario){
$this->_appendLog("$observable->nome excluido ".date('Y-m-d'));
}

}
public function afterInsert(ICrudObservable $observable) {
if($observable instanceof CrudUsuario){
$this->_appendLog("$observable->nome Inserido ".date('Y-m-d'));
}
}
public function afterUpdate(ICrudObservable $observable) {
if($observable instanceof CrudUsuario){
$this->_appendLog("$observable->nome atualizado ".date('Y-m-d'));
}
}
public function beforeDelete(ICrudObservable $observable) {
/*
* Não utilizado mas pode ser utilizado em outro observador
*/
}
public function beforeInsert(ICrudObservable $observable) {
/*
* Não utilizado mas pode ser utilizado em outro observador
*/
}
public function beforeUpdate(ICrudObservable $observable) {
/*
* Não utilizado mas pode ser utilizado em outro observador
*/
}
}



$crud = new CrudUsuario();
$crud->addObserver(new FileLogObserver());
$crud->nome = "reinaldo";
$crud->insert();

Da forma que foi implementado, bastava fazer uma classe que enviasse email toda vez que um novo usuário fosse cadastrado. Para isso, só precisaria criar a classe implementando a interface e registrá-la no CrudUsuario.

Outra utilização do Design Pattern pode ser na checagem de uma regra qualquer antes de excluir um usuário. Um observador poderia lançar uma exception que impediria o usuário de ser excluído.

Conclusão

Padrões de projetos são importantes para ter uma estrutura robusta e sustentável. O padrão observer demonstrou ser uma solução muito interessante de permitir extensão de funcionalidades sem precisar alterar códigos que já estão em funcionamento (Open/closed principle S.O.L.I.D - no exemplo demonstrado nenhum código da classe crudUsuario foi alterado para acrescentar logs). Além disso, ele permite a separação de diversas funcionalidades em classes mais simples e especializadas (Single responsibility principle S.O.L.I.D) e promove o baixo acoplamento entre as classes(Grasp).

Até a próxima!

2 comentários: