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:
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
- SplObserver, SplSubject e SplObjectStorage
- Observer pattern