Design Patterns: The Observer Pattern

Now here I come with one more behavioral design pattern in this series, which is the Observer Pattern. Observer means that someone is looking at your activity, and it may be possible that the observer takes some action depending on your activity. 

The same concept is applicable to the design pattern as well. We should implement this pattern when we have a one-to-many dependency between our object and one object needs to be changed/notified when any changes are made to any other object.

Wikipedia says the same thing in the words below:

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. The Observer pattern is also a key part in the familiar model–view–controller (MVC) architectural pattern.

To elaborate more about this design pattern, I am taking the example of a simulator which shows the value of different currencies against the US dollar. We are assuming that the simulator shows the price and also updates the price at regular intervals.

Before we start, let’s define the responsibilities of the main simulator class (which is behaving as the observer in this example). 

  1. This observer should have the ability to add a new currency, so that the client can add any as many currencies as they want.
  2. This observer should keep the reference to all the added currencies.
  3. This observer should show the status/value of each registered currency.

In the next section, we will implement our observer:

interface Observer {
    public function addCurrency(Currency $currency);
}

class priceSimulator implements Observer {
        private $currencies;
        
        public function __construct() {
                $this->currencies = array();
        }
        
        public function addCurrency(Currency $currency) {
                array_push($this->currencies, $currency);
        }
        
        public function updatePrice() {
                foreach ($this->currencies as $currency) {
                        $currency->update();
                }
        }
}

If you look at the above code, you can see that it has the ability to perform all the responsibilities which we have mentioned in the previous section.

Now we have our observer ready. What we need now is some different currencies which we can add to this observer and then we are good to go. Let’s implement our currency classes now.

interface Currency {
    public function update();
        public function getPrice();
}

class Pound implements Currency {
        private $price;
        
        public function __construct($price) {
                $this->price = $price;
                echo "<p>Pound Original Price: {$price}</p>";
        }
        
        public function update() {
                $this->price = $this->getPrice();
                echo "<p>Pound Updated Price : {$this->price}</p>";
        }
        
        public function getPrice() {
                return f_rand(0.65, 0.71);
        }
        
}

class Yen implements Currency {
        private $price;

        public function __construct($price) {
                $this->price = $price;
                echo "<p>Yen Original Price : {$price}</p>";
        }

        public function update() {
                $this->price = $this->getPrice();
                echo "<p>Yen Updated Price : {$this->price}</p>";
        }
        
        public function getPrice() {
                return f_rand(120.52, 122.50);
        }
        
}

We are all set now to put everything together and run our observer. 

Putting It All Together

interface Observer {
    public function addCurrency(Currency $currency);
}

class priceSimulator implements Observer {
        private $currencies;
        
        public function __construct() {
                $this->currencies = array();
        }
        
        public function addCurrency(Currency $currency) {
                array_push($this->currencies, $currency);
        }
        
        public function updatePrice() {
                foreach ($this->currencies as $currency) {
                        $currency->update();
                }
        }
}

interface Currency {
        public function update();
        public function getPrice();
}

class Pound implements Currency {
        private $price;
        
        public function __construct($price) {
                $this->price = $price;
                echo "<p>Pound Original Price: {$price}</p>";
        }
        
        public function update() {
                $this->price = $this->getPrice();
                echo "<p>Pound Updated Price : {$this->price}</p>";
        }
        
        public function getPrice() {
                return f_rand(0.65, 0.71);
        }
        
}

class Yen implements Currency {
        private $price;

        public function __construct($price) {
                $this->price = $price;
                echo "<p>Yen Original Price : {$price}</p>";
        }

        public function update() {
                $this->price = $this->getPrice();
                echo "<p>Yen Updated Price : {$this->price}</p>";
        }
        
        public function getPrice() {
                return f_rand(120.52, 122.50);
        }
        
}

function f_rand($min=0,$max=1,$mul=1000000){
        if ($min>$max) return false;
        return mt_rand($min*$mul,$max*$mul)/$mul;
}

$priceSimulator = new priceSimulator();

$currency1 = new Pound(0.60);
$currency2 = new Yen(122);

$priceSimulator->addCurrency($currency1);
$priceSimulator->addCurrency($currency2);

echo "<hr />";
$priceSimulator->updatePrice();

echo "<hr />";
$priceSimulator->updatePrice();

The above code will output:

Pound Original Price: 0.6

Yen Original Price : 122

-------------

Pound Updated Price : 0.65346

Yen Updated Price : 121.287809

-------------

Pound Updated Price : 0.671269

Yen Updated Price : 121.300605

Here you can see that we have updated the prices for all registered currencies and they’re being displayed in our simulator. Now we will consider how we can add new currencies to this simulator with just a minor modification. 

This modification includes the registering of currencies to the simulator only. As such, our client code remains untouched whenever it invokes the price updater in our simulator.

Adding a New Currency

Currency Class

class Euro implements Currency {
    private $price;

        public function __construct($price) {
                $this->price = $price;
                echo "<p>Euro Original Price : {$price}</p>";
        }

        public function update() {
                $this->price = $this->getPrice();
                echo "<p>Euro Updated Price : {$this->price}</p>";
        }
        
        public function getPrice() {
                return f_rand(0.78, 0.85);
        }
        
}

It was easy and straightforward to add a new currency. Now all we need to do is register this new currency in our observer and we are all done!

$priceSimulator = new priceSimulator();

$currency1 = new Pound(0.60);
$currency2 = new Yen(122);
$currency3 = new Euro(122);

$priceSimulator->addCurrency($currency1);
$priceSimulator->addCurrency($currency2);
$priceSimulator->addCurrency($currency3);

echo "<hr />";
$priceSimulator->updatePrice();

echo "<hr />";
$priceSimulator->updatePrice();

Conclusion

The state of any object is very important in Object Oriented Programming, because everything is running between objects and their interaction with each other. It is frequently the case that a few objects need to be notified when changes occur in other objects. The Observer design pattern can be utilized when changes in a subject need to be observed by one or more observers.

Don’t forget to leave your feedback below.