Design Patterns In Action: The Observer

The observer pattern is a design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any changes to its state. The observer pattern is used to implement distributed event handling systems.

In PHP, the observer pattern can be implemented using interfaces and classes. The subject interface defines methods for attaching and detaching observers, while the concrete subject class implements the behavior for storing and notifying observers. The observer interface defines a method for updating the observer, while concrete observer classes implement the behavior for updating themselves.

For example:

interface Hackable {
    public function attach(SecurityTeam $securityTeam);
    public function detach(SecurityTeam $securityTeam);
    public function notify();
}

class AIServer implements Hackable {
    private $securityTeams = array();
    private $data = "sensitive information";

    public function attach(SecurityTeam $securityTeam) {
        $this->securityTeams[] = $securityTeam;
    }

    public function detach(SecurityTeam $securityTeam) {
        $key = array_search($securityTeam, $this->securityTeams, true);
        if ($key) {
            unset($this->securityTeams[$key]);
        }
    }

    public function notify() {
        foreach ($this->securityTeams as $securityTeam) {
            $securityTeam->alert();
        }
    }

    public function hackDetection() {
        // simulate a hack detection
        $this->data = "stolen information";
        $this->notify();
    }
}

interface SecurityTeam {
    public function alert();
}

class CyberSecurityTeam implements SecurityTeam {
    public function alert() {
        echo "Cyber Security Team has been alerted of a hack.\n";
    }
}

class PhysicalSecurityTeam implements SecurityTeam {
    public function alert() {
        echo "Physical Security Team has been alerted of a hack.\n";
    }
}

$aiServer = new AIServer();
$cyberSecurityTeam = new CyberSecurityTeam();
$physicalSecurityTeam = new PhysicalSecurityTeam();

$aiServer->attach($cyberSecurityTeam);
$aiServer->attach($physicalSecurityTeam);

$aiServer->hackDetection();

Here the AIServer class is the subject, and the CyberSecurityTeam and PhysicalSecurityTeam classes are the observers. The AIServer class has an AI-based system that monitors the server and simulates the detection of a hack by changing the value of $data variable. Once the hack is detected, it calls the notify() method to notify all attached observers. The observers then update themselves by alerting themselves.

Here at Design Matrix we’re always a fan of the SOLID principles. Let’s give it a go:

interface Hackable {
    public function attach(SecurityTeam $securityTeam);
    public function detach(SecurityTeam $securityTeam);
    public function notify();
}

class AIServer implements Hackable {
    private $securityTeams = array();
    private $data = "sensitive information";

    public function attach(SecurityTeam $securityTeam) {
        $this->securityTeams[] = $securityTeam;
    }

    public function detach(SecurityTeam $securityTeam) {
        $key = array_search($securityTeam, $this->securityTeams, true);
        if ($key) {
            unset($this->securityTeams[$key]);
        }
    }

    public function notify() {
        foreach ($this->securityTeams as $securityTeam) {
            $securityTeam->alert();
        }
    }

    public function hackDetection() {
        // simulate a hack detection
        $this->data = "stolen information";
        $this->notify();
    }
}

interface SecurityTeam {
    public function alert();
}

class CyberSecurityTeam implements SecurityTeam {
    public function alert() {
        echo "Cyber Security Team has been alerted of a hack.\n";
    }
}

class PhysicalSecurityTeam implements SecurityTeam {
    public function alert() {
        echo "Physical Security Team has been alerted of a hack.\n";
    }
}

class CyberSecurityTeamHandler implements SecurityTeam {
    public function alert() {
        echo "Cyber Security Team has been alerted of a hack.\n";
    }
}

class PhysicalSecurityTeamHandler implements SecurityTeam {
    public function alert() {
        echo "Physical Security Team has been alerted of a hack.\n";
    }
}

$aiServer = new AIServer();
$cyberSecurityTeam = new CyberSecurityTeamHandler();
$physicalSecurityTeam = new PhysicalSecurityTeamHandler();

$aiServer->attach($cyberSecurityTeam);
$aiServer->attach($physicalSecurityTeam);

$aiServer->hackDetection();

The only major change we had to make was in regards to the ISP. Here’s how we’ve followed the SOLID principles:

  1. Single Responsibility Principle (SRP): Each class has a single responsibility and is responsible for only one thing. The AIServer class is responsible for managing the list of observers and notifying them of changes, while the SecurityTeam interface and its implementations (CyberSecurityTeamHandler and PhysicalSecurityTeamHandler) are responsible for updating themselves when notified.
  2. Open-Closed Principle (OCP): The AIServer class is open for extension but closed for modification. New security team classes can be added without modifying the AIServer class, as long as they implement the SecurityTeam interface.
  3. Liskov Substitution Principle (LSP): The SecurityTeam interface and its implementations can be used interchangeably without affecting the correctness of the program.
  4. Interface Segregation Principle (ISP): The SecurityTeam interface defines a single method, alert(), which is used to alert the observer. This ensures that observers are not forced to implement unnecessary methods.
  5. Dependency Inversion Principle (DIP): The AIServer class depends on the SecurityTeam interface, not on the concrete implementations (CyberSecurityTeamHandler and PhysicalSecurityTeamHandler). This allows for greater flexibility and easier testing, as different implementations can be easily swapped out.
Related Posts