Observer Pattern com SPL

PHP
Enviado por Eclesiastes em Qua, 11/04/2007 - 15:10.PHP

As interfaces SplObserver e SplSubject da SPL, desde o PHP 5.1, provêm uma interface para implementação do Observer pattern (também conhecido como Publish/Subscribe, Event Generator e Dependents).

Nesse grupo (Observer) na SPL, há também a classe SplObjectStorage, que implementa as interfaces Iterator e Countable.

O Observer Pattern funciona da seguinte forma:

Um ou mais objetos, denominado Observador, são registrados num dado objeto (que vem a ser o Observado) para que ao disparar um evento no mesmo, todos os observadores registrados sejam informados.
O que os Observadores fazem ao receberem a notificação é irrelevante para o Observado.

Um diagrama UML ilustrando a estrutura, pode ser visto através da Wikipedia.

Há uma série de nomes para o objeto que é observado, são eles:

  • "Subject" (GoF)
  • "Observable" (java.util)
  • "Source" ou "Event Source" (java.swing e java.beans)
  • Provedor de informação (Bill Venners)
  • Gerador de eventos (Bill Venners)

E para o objeto observador:

  • Observer (GoF e java.util)
  • Listener (java.swing)

O uso deste padrão dá-se nos seguinte casos:

  • Quando uma abstração tem dois aspectos, um dependente do outro. Encapsular tais aspectos em objetos separados permite que variem e sejam reusados separadamente.
  • Quando uma mudança a um objeto requer mudanças a outros e você não sabe quantos outros objetos devem mudar.
  • Quando um objeto deve ser capaz de avisar outros sem fazer suposições sobre quem são os objetos. Em outras palavras, sem criar um acoplamento fonte entre os objetos.

SplObserver

A classe que utilizar a interface SplObserver deve implementar o seguinte método:

  • update(SplSubject $subject)
  • Método que é invocado pelo objeto observado.

SplSubject

  • attach(SplObserver $observer)
  • Método que registra um observador.

  • detach(SplObserver $observer)
  • Método que remove um observador.

  • notify()
  • Método que envia uma notificação aos observadores registrados, chamando o método update() de cada objeto da lista.

Vamos a um pequeno exemplo!

<pre>
<?php
 
class Investor implements SplObserver
{
    private 
$_name;
 
    public function 
__construct($name)
    {
        
$this->_name $name;
    }
 
    public function 
getName()
    {
        return 
$this->_name;
    }
 
    public function 
update(SplSubject $product)
    {
        
printf("%s notificado! %s -> Novo preço: %.2f\n",
            
$this->getName(), $product->getName(), $product->getPrice());
    }
}
 
class 
Coke implements SplSubject
{
    private 
$_name;
    private 
$_price;
    private 
$_investors = array();
 
    public function 
__construct($price)
    {
        
$this->_name  'Coca-Cola';
        
$this->_price $price;
    }
 
    public function 
setPrice($price)
    {
        
$this->_price $price;
 
        
$this->notify();
    }
 
    public function 
getName()
    {
        return 
$this->_name;
    }
 
    public function 
getPrice()
    {
        return 
$this->_price;
    }
 
    
/* Adiciona um observador */
    
public function attach(SplObserver $investor)
    {
        
$this->_investors[] = $investor;
    }
 
    
/* Remove um observador */
    
public function detach(SplObserver $investor)
    {
        foreach (
$this->_investors as $key => $obj) {
            if (
$obj == $investor) {
                unset(
$this->_investors[$key]);
            }
        }
    }
 
    
/* Notifica os observadores */
    
public function notify()
    {
        foreach (
$this->_investors as $investor) {
            
$investor->update($this);
        }
    }
}
 
/* Investidores */
$Felipe  = new Investor('Felipe');
$Diogo   = new Investor('Diogo');
$Roberto = new Investor('Roberto');
 
/* Produto */
$Coke = new Coke(1.5);
 
/* Adicionando investidores */
$Coke->attach($Felipe);
$Coke->attach($Diogo);
 
/* Alterando o preço */
$Coke->setPrice(1.80);
 
/* Adicionando investidor */
$Coke->attach($Roberto);
 
/* Removendo investidor */
$Coke->detach($Felipe);
 
/* Alterando o preço */
$Coke->setPrice(1.51);
 
?>

Onde terá a seguinte saída:

Felipe notificado! Coca-Cola -> Novo preço: 1.80
Diogo notificado! Coca-Cola -> Novo preço: 1.80
Diogo notificado! Coca-Cola -> Novo preço: 1.51
Roberto notificado! Coca-Cola -> Novo preço: 1.51

Segue abaixo o exemplo herdando a classe SplObjectStorage:

<pre>
<?php
 
class Pessoa implements SplObserver
{
    private 
$_name;
    
    public function 
__construct($name)
    {
        
$this->_name $name;
    }
    
    public function 
getName()
    {
        return 
$this->_name;
    }
    
    public function 
update(SplSubject $obj)
    {
        
printf("-> %s foi informado!\n"$this->getName());
    }
    
    public function 
__toString()
    {
        return 
$this->getName();
    }
}
 
class 
Telefone extends SplObjectStorage implements SplSubject
{
    public function 
attach(SplObserver $obj)
    {
        if (
$this->contains($obj)) {
            
printf("%s já foi registrado!\n"$obj->getName());
        } else {
            
printf("%s adicionado!\n"$obj->getName());
            
parent::attach($obj);
        }
    }
 
    public function 
detach(SplObserver $obj)
    {
        
printf("%s saiu! Não poderá mais atender...\n"$obj->getName());
        
parent::detach($obj);
    }
 
    public function 
notify()
    {
        print 
"\n* Telefone tocando! *\n";
        
printf("* Informando %d pessoa(s)!\n"count($this));
        
        foreach (
$this as $people) {
            
$people->update($this);
        }
    }
 
    
/* Este método chama o Iterator implementado na SplObjectStorage */
    
public function listObservers()
    {
        return 
$this;
    }
}
 
$Felipe = new Pessoa('Felipe');
$Diogo  = new Pessoa('Diogo');
 
$Telefone = new Telefone();
 
/* Pessoas interessadas quando o telefone tocar */
$Telefone->attach($Felipe);
$Telefone->attach($Diogo);
 
/* Tentando registrar uma pessoa já registrada */
$Telefone->attach($Felipe);
 
/* Telefone tocando, informando aos interessados */
$Telefone->notify();
 
/* Removendo um atendente */
$Telefone->detach($Felipe);
 
$Telefone->notify();
 
/* Utilizando a implementação do Iterator */
 
print "\nAtendente(s):\n";
 
/* Listando possíveis atendentes */
foreach ($Telefone->listObservers() as $pessoa) {
    
printf("-> %s\n"$pessoa);
}
 
?>

Onde tem a seguinte saída:

Felipe adicionado!
Diogo adicionado!
Felipe já foi registrado!

* Telefone tocando! *
* Informando 2 pessoa(s)!
-> Felipe foi informado!
-> Diogo foi informado!
Felipe saiu! Não poderá mais atender...

* Telefone tocando! *
* Informando 1 pessoa(s)!
-> Diogo foi informado!

Atendente(s):
-> Diogo

Referências



Enviado por M_a_r_c_u_s (não verificado(a)) em Qua, 15/10/2008 - 13:48.

Gostei bastante deste post, tanto que eu agora estou usando o observer nas minhas aplicaçoes ficou bem mais facil !!

Abraços !
ps: sabe quem eu sou ne ??