HiveBrain v1.2.0
Get Started
← Back to all entries
patternphpMinor

Observer Pattern with an Observer observing multiple Subjects

Submitted by: @import:stackexchange-codereview··
0
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

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):

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.