patternphpMinor
Observer Pattern with an Observer observing multiple Subjects
Viewed 0 times
observingwithobserversubjectsmultiplepattern
Problem
Is the below a good solution for an observer to monitor multiple subjects. My only fear is that if an observer was monitoring a wide range of subjects then the update method could get quite large checking each instance to see what type it is but I can't think of another way to implement this:
Journalist Subject
Website Subject
```
class Website implements SplSubject {
protected $observers;
protected $website;
protected $webPage;
public function __construct( $website ) {
$this->website = $website;
$this->observers = new SplObjectStorage();
}
public function attach( SplObserver $observer ) {
$this->observers->attach( $observer );
}
public function detach( SplObserver $observer ) {
$this->observers->detach( $observer );
}
public function notify() {
foreach( $this->observers as $observer ) {
$observer->update( $this );
}
}
public function addWebPage( $webPage ) {
$this->webPage = $webPage;
$this->notify();
}
public function g
Journalist Subject
class Journalist implements SplSubject {
protected $observers;
protected $name;
protected $story;
public function __construct( $name ) {
$this->name = $name;
$this->observers = new SplObjectStorage();
}
public function attach( SplObserver $observer ) {
$this->observers->attach( $observer );
}
public function detach( SplObserver $observer ) {
$this->observers->detach( $observer );
}
public function notify() {
foreach( $this->observers as $observer ) {
$observer->update( $this );
}
}
public function addStory( $story ) {
$this->story = $story;
$this->notify();
}
public function getStory() {
return $this->story;
}
public function addName( $name ) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}Website Subject
```
class Website implements SplSubject {
protected $observers;
protected $website;
protected $webPage;
public function __construct( $website ) {
$this->website = $website;
$this->observers = new SplObjectStorage();
}
public function attach( SplObserver $observer ) {
$this->observers->attach( $observer );
}
public function detach( SplObserver $observer ) {
$this->observers->detach( $observer );
}
public function notify() {
foreach( $this->observers as $observer ) {
$observer->update( $this );
}
}
public function addWebPage( $webPage ) {
$this->webPage = $webPage;
$this->notify();
}
public function g
Solution
Traits
First of all, we have to remove duplications caused by the implementation of the SplObject interface. We have here a great case for a trait: http://php.net/manual/en/language.oop5.traits.php.
Here is a refactoring using a trait (the rest is unchanged):
Dedicated subclass
In order to increase the separation of concern and the reusability of your code, I advise you to create specific subclasses for your subjects:
The dispatcher
But there is a better way to separate concerns: the use of a dispatcher:
So we have now very simple entities:
Note: in a real software, the article has to be a separate entity which is injected in the event object
And so we can use the whole system like this:
First of all, we have to remove duplications caused by the implementation of the SplObject interface. We have here a great case for a trait: http://php.net/manual/en/language.oop5.traits.php.
Here is a refactoring using a trait (the rest is unchanged):
getObservers()->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->getObservers()->detach($observer);
}
public function notify()
{
foreach ($this->getObserver as $observer) {
$observer->update($this);
}
}
protected function getObserver()
{
if (is_null($this->observer)) $this->observer = new SplObjectStorage();
return $this->observer;
}
}
class Journalist implements SplSubject
{
use Subject;
protected $name;
protected $story;
public function __construct($name)
{
$this->name = $name;
}
public function addStory($story)
{
$this->story = $story;
$this->notify();
}
public function getStory()
{
return $this->story;
}
public function addName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
Website Subject
class Website implements SplSubject
{
use Subject;
protected $website;
protected $webPage;
public function __construct($website)
{
$this->website = $website;
}
public function addWebPage($webPage)
{
$this->webPage = $webPage;
$this->notify();
}
public function getWebPage()
{
return $this->webPage;
}
public function addWebsite($website)
{
$this->website = $website;
}
}Dedicated subclass
In order to increase the separation of concern and the reusability of your code, I advise you to create specific subclasses for your subjects:
class JournalistAsSubject extends Journalist implements SplSubject
{
use Subject;
public function addStory($story)
{
parent::addStory($story);
$this->notify();
}
}
class Journalist
{
protected $name;
protected $story;
public function __construct($name)
{
$this->name = $name;
}
public function addStory($story)
{
$this->story = $story;
}
public function getStory()
{
return $this->story;
}
public function addName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}The dispatcher
But there is a better way to separate concerns: the use of a dispatcher:
class EventDispatcher
{
private $listeners = [];
public function addListener($event_type, $listener)
{
if(!array_key_exists($event_type, $this->listeners)) $this->listener[$event_type] = [];
$this->listeners[$event_type][] = $listener;
}
public function dispatch($event_type, $event)
{
if(!array_key_exists($event_type, $this->listeners)) throw new \InvalidArgumentException($event_type.' does not exists');
foreach($this->listeners[$event_type] as $listener) {
$listener->update($event);
}
}
}
class ArtclePublishedEvent
{
public function __construct(Author $author, $title, $content)
{
$this->author = $author;
$this->title = $title;
$this->content = $content;
}
public function getAuthor(){ return $this->author; }
public function getTitle(){ return $this->title; }
public function getContent(){ return $this->content; }
}So we have now very simple entities:
class Author
{
public function __construct($name)
{
$this->name = $name;
}
public function getName(){ return $this->name; }
}
class NewsRoom
{
public function update($event)
{
return $event->author->name.' as written a new article called: "'.$event->title.'"';
}
}Note: in a real software, the article has to be a separate entity which is injected in the event object
And so we can use the whole system like this:
$dispatcher = new EventDispatcher();
$dispatcher->addListener('article.publish', new NewsRoom());
$event = new ArticlePublishedEvent(new Author('John'), 'a fairy tale', 'some very short content');
$dispatcher->dispatch('article.publish', $event);Code Snippets
<?php
trait Subject
{
private $observer = null;
public function attach(SplObserver $observer)
{
$this->getObservers()->attach($observer);
}
public function detach(SplObserver $observer)
{
$this->getObservers()->detach($observer);
}
public function notify()
{
foreach ($this->getObserver as $observer) {
$observer->update($this);
}
}
protected function getObserver()
{
if (is_null($this->observer)) $this->observer = new SplObjectStorage();
return $this->observer;
}
}
class Journalist implements SplSubject
{
use Subject;
protected $name;
protected $story;
public function __construct($name)
{
$this->name = $name;
}
public function addStory($story)
{
$this->story = $story;
$this->notify();
}
public function getStory()
{
return $this->story;
}
public function addName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
Website Subject
class Website implements SplSubject
{
use Subject;
protected $website;
protected $webPage;
public function __construct($website)
{
$this->website = $website;
}
public function addWebPage($webPage)
{
$this->webPage = $webPage;
$this->notify();
}
public function getWebPage()
{
return $this->webPage;
}
public function addWebsite($website)
{
$this->website = $website;
}
}class JournalistAsSubject extends Journalist implements SplSubject
{
use Subject;
public function addStory($story)
{
parent::addStory($story);
$this->notify();
}
}
class Journalist
{
protected $name;
protected $story;
public function __construct($name)
{
$this->name = $name;
}
public function addStory($story)
{
$this->story = $story;
}
public function getStory()
{
return $this->story;
}
public function addName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}class EventDispatcher
{
private $listeners = [];
public function addListener($event_type, $listener)
{
if(!array_key_exists($event_type, $this->listeners)) $this->listener[$event_type] = [];
$this->listeners[$event_type][] = $listener;
}
public function dispatch($event_type, $event)
{
if(!array_key_exists($event_type, $this->listeners)) throw new \InvalidArgumentException($event_type.' does not exists');
foreach($this->listeners[$event_type] as $listener) {
$listener->update($event);
}
}
}
class ArtclePublishedEvent
{
public function __construct(Author $author, $title, $content)
{
$this->author = $author;
$this->title = $title;
$this->content = $content;
}
public function getAuthor(){ return $this->author; }
public function getTitle(){ return $this->title; }
public function getContent(){ return $this->content; }
}class Author
{
public function __construct($name)
{
$this->name = $name;
}
public function getName(){ return $this->name; }
}
class NewsRoom
{
public function update($event)
{
return $event->author->name.' as written a new article called: "'.$event->title.'"';
}
}$dispatcher = new EventDispatcher();
$dispatcher->addListener('article.publish', new NewsRoom());
$event = new ArticlePublishedEvent(new Author('John'), 'a fairy tale', 'some very short content');
$dispatcher->dispatch('article.publish', $event);Context
StackExchange Code Review Q#85630, answer score: 7
Revisions (0)
No revisions yet.