Observer Pattern

The pattern allows:

  • one or more objects be notified of state changes in other objects within the system;
  • broadcasting

Observer Pattern


In the Observer pattern, Subject object has collection of Observer objects. When the method 'notify' of Subject object is called, all the attached Observers notified.

PHP Example

I find the pattern very useful for attaching plugins and cross-cutting concerns. In this example I intended to show how you can make your models pluginable.

<?php
/*
 * @category Design Pattern Tutorial
 * @package Observer Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */

class Plugin_Bookmark implements SplObserver
{
    public function deleteByUserId($userId)
    {
        printf ("Bookmarks of user %d deleted\n", $userId);
    }
    public function update(SplSubject $subject)
    {
        $userId = $subject->getId();
        $this->deleteByUserId($userId);
    }
}
class Plugin_Liking implements SplObserver
{
    public function deleteByUserId($userId)
    {
        printf ("Likings of user %d deleted\n", $userId);
    }
    public function update(SplSubject $subject)
    {
        $userId = $subject->getId();
        $this->deleteByUserId($userId);
    }
}

abstract class Pluginable_Abstract implements SplSubject
{
    // Array of observers
    private $_observers = array();

    public function attach(SplObserver $observer)
    {
        $hash = spl_object_hash($observer);
        if (!isset ($this->_observers[$hash])) {
            $this->_observers[$hash] = $observer;
        }
    }
    public function detach(SplObserver $observer)
    {
        unset($this->_observers[spl_object_hash($observer)]);
    }
    /**
     * Implement SplSubject method
     */
    public function notify()
    {
        foreach ($this->_observers as $value) {
            $value->update($this);
        }
    }
}

class Model_Abstract extends Pluginable_Abstract
{
    // Common and abstract properies and methods of Models
}
class Model_User extends Model_Abstract
{
    private $_userId;

    public function setId($userId)
    {
        $this->_userId = $userId;
    }

    public function getId()
    {
        return $this->_userId;
    }

    public function delete($userId)
    {
        $this->setId($userId);
        printf ("User %d deleted\n", $userId);
        $this->notify();
    }
}

/**
 * Usage
 */
$userId = 1;
$model = new Model_User();
$model->attach(new Plugin_Bookmark());
$model->attach(new Plugin_Liking());
$model->delete($userId);

// Output
// -> User 1 deleted
// -> Bookmarks of user 1 deleted
// -> Likings of user 1 deleted

JS/ES5 Example

/*
 * @category Design Pattern Tutorial
 * @package Observer Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {
/**
 * Concrete observer
 */
var SilverLightFallbackPlugin = function() {}
SilverLightFallbackPlugin.prototype.update = function(subject) {
    // Renders SilverLight embedding into subject.node.viewport element
    console.log('SilverLight fallback is shown');
}
/**
 * Abstract subject
 */
var Subject = function() {
    var _observers = [];
    return {
        attach : function(observer) {
            _observers.push(observer);
        },
        notify : function() {
            for (var i in _observers) {
                _observers[i].update(this);
            }
        }
    };
};
/**
 * Concrete subject
 */
var VideoPlayer = function(settings) {
    // Attaches all given observers
    for (var i in settings.plugins) {
        this.attach(settings.plugins[i]);
    }
    //...
    if (this.isVideoTagSupportAvailable()) {
        this.renderUI();
        this.syncUI();
    } else {
        this.renderFallback();
    }
};
VideoPlayer.prototype = new Subject();
VideoPlayer.prototype.node = {
    viewport : null
};
VideoPlayer.prototype.renderUI = function() {
    //...
}
VideoPlayer.prototype.syncUI = function() {
    //...
}
VideoPlayer.prototype.isVideoTagSupportAvailable = function() {
    //...
    return false;
}
VideoPlayer.prototype.renderFallback = function() {
    //...
    this.notify();
}

/**
 * Usage
 */
new VideoPlayer({ plugins: [ new SilverLightFallbackPlugin() ]});

// Output:
// -> SilverLight fallback is shown

})();