TungNT (Blue)

tungnt.blue@gmail.com

User Tools

Site Tools


development:software-architecture:design-patterns:observer

Observer

Hãy tưởng tượng rằng bạn có hai loại đối tượng: Khách hàng và Cửa hàng. Khách hàng rất quan tâm đến một thương hiệu sản phẩm cụ thể (ví dụ, đó là một mẫu iPhone mới) sẽ sớm có mặt tại cửa hàng.

Khách hàng có thể đến cửa hàng mỗi ngày và kiểm tra tình trạng còn hàng của sản phẩm. Nhưng trong khi sản phẩm vẫn đang trên đường, hầu hết các chuyến đi này sẽ vô nghĩa.

Mặt khác, cửa hàng có thể gửi hàng tấn email (có thể được coi là thư rác) cho tất cả khách hàng mỗi khi có sản phẩm mới. Điều này sẽ giúp một số khách hàng không phải đến cửa hàng liên tục. Đồng thời, nó sẽ làm phiền những khách hàng khác không quan tâm đến sản phẩm mới.

Có vẻ như chúng ta có xung đột. Hoặc là khách hàng mất thời gian kiểm tra tình trạng sản phẩm hoặc cửa hàng mất nguồn lực khi thông báo nhầm khách hàng.

Sử dụng 2 interface có sẵn của PHP (SplSubject, SplObserver):

Ví dụ 1:

<?php
 
/**
 * Subject,that who makes news
 */
class Newspaper implements \SplSubject{
    private $name;
    private $observers = array();
    private $content;
 
    public function __construct($name) {
        $this->name = $name;
    }
 
    //add observer
    public function attach(\SplObserver $observer) {
        $this->observers[] = $observer;
    }
 
    //remove observer
    public function detach(\SplObserver $observer) {
 
        $key = array_search($observer,$this->observers, true);
        if($key){
            unset($this->observers[$key]);
        }
    }
 
    //set breakouts news
    public function breakOutNews($content) {
        $this->content = $content;
        $this->notify();
    }
 
    public function getContent() {
        return $this->content." ({$this->name})";
    }
 
    //notify observers(or some of them)
    public function notify() {
        foreach ($this->observers as $value) {
            $value->update($this);
        }
    }
}
 
/**
 * Observer,that who recieves news
 */
class Reader implements SplObserver{
    private $name;
 
    public function __construct($name) {
        $this->name = $name;
    }
 
    public function update(\SplSubject $subject) {
        echo $this->name.' is reading breakout news <b>'.$subject->getContent().'</b><br>';
    }
}
 
$newspaper = new Newspaper('Newyork Times');
 
$allen = new Reader('Allen');
$jim = new Reader('Jim');
$linda = new Reader('Linda');
 
//add reader
$newspaper->attach($allen);
$newspaper->attach($jim);
$newspaper->attach($linda);
 
//remove reader
$newspaper->detach($linda);
 
//set break outs
$newspaper->breakOutNews('USA break down!');
 
//=====output======
//Allen is reading breakout news USA break down! (Newyork Times)
//Jim is reading breakout news USA break down! (Newyork Times)

Ví dụ 2:

<?php 
 
// Example implementation of Observer design pattern:
 
class MyObserver1 implements SplObserver {
    public function update(SplSubject $subject) {
        echo __CLASS__ . ' - ' . $subject->getName();
    }
}
 
class MyObserver2 implements SplObserver {
    public function update(SplSubject $subject) {
        echo __CLASS__ . ' - ' . $subject->getName();
    }
}
 
class MySubject implements SplSubject {
    private $_observers;
    private $_name;
 
    public function __construct($name) {
        $this->_observers = new SplObjectStorage();
        $this->_name = $name;
    }
 
    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 getName() {
        return $this->_name;
    }
}
 
$observer1 = new MyObserver1();
$observer2 = new MyObserver2();
 
$subject = new MySubject("test");
 
$subject->attach($observer1);
$subject->attach($observer2);
$subject->notify();
 
/* 
will output:
 
MyObserver1 - test
MyObserver2 - test
*/
 
$subject->detach($observer2);
$subject->notify();
 
/* 
will output:
 
MyObserver1 - test
*/
 
?>

Ví dụ 3:

<?php
 
namespace RefactoringGuru\Observer\Conceptual;
 
/**
 * PHP has a couple of built-in interfaces related to the Observer pattern.
 *
 * Here's what the Subject interface looks like:
 *
 * @link http://php.net/manual/en/class.splsubject.php
 *
 *     interface SplSubject
 *     {
 *         // Attach an observer to the subject.
 *         public function attach(SplObserver $observer);
 *
 *         // Detach an observer from the subject.
 *         public function detach(SplObserver $observer);
 *
 *         // Notify all observers about an event.
 *         public function notify();
 *     }
 *
 * There's also a built-in interface for Observers:
 *
 * @link http://php.net/manual/en/class.splobserver.php
 *
 *     interface SplObserver
 *     {
 *         public function update(SplSubject $subject);
 *     }
 */
 
/**
 * The Subject owns some important state and notifies observers when the state
 * changes.
 */
class Subject implements \SplSubject
{
    /**
     * @var int For the sake of simplicity, the Subject's state, essential to
     * all subscribers, is stored in this variable.
     */
    public $state;
 
    /**
     * @var \SplObjectStorage List of subscribers. In real life, the list of
     * subscribers can be stored more comprehensively (categorized by event
     * type, etc.).
     */
    private $observers;
 
    public function __construct()
    {
        $this->observers = new \SplObjectStorage();
    }
 
    /**
     * The subscription management methods.
     */
    public function attach(\SplObserver $observer): void
    {
        echo "Subject: Attached an observer.\n";
        $this->observers->attach($observer);
    }
 
    public function detach(\SplObserver $observer): void
    {
        $this->observers->detach($observer);
        echo "Subject: Detached an observer.\n";
    }
 
    /**
     * Trigger an update in each subscriber.
     */
    public function notify(): void
    {
        echo "Subject: Notifying observers...\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }
 
    /**
     * Usually, the subscription logic is only a fraction of what a Subject can
     * really do. Subjects commonly hold some important business logic, that
     * triggers a notification method whenever something important is about to
     * happen (or after it).
     */
    public function someBusinessLogic(): void
    {
        echo "\nSubject: I'm doing something important.\n";
        $this->state = rand(0, 10);
 
        echo "Subject: My state has just changed to: {$this->state}\n";
        $this->notify();
    }
}
 
/**
 * Concrete Observers react to the updates issued by the Subject they had been
 * attached to.
 */
class ConcreteObserverA implements \SplObserver
{
    public function update(\SplSubject $subject): void
    {
        if ($subject->state < 3) {
            echo "ConcreteObserverA: Reacted to the event.\n";
        }
    }
}
 
class ConcreteObserverB implements \SplObserver
{
    public function update(\SplSubject $subject): void
    {
        if ($subject->state == 0 || $subject->state >= 2) {
            echo "ConcreteObserverB: Reacted to the event.\n";
        }
    }
}
 
/**
 * The client code.
 */
 
$subject = new Subject();
 
$o1 = new ConcreteObserverA();
$subject->attach($o1);
 
$o2 = new ConcreteObserverB();
$subject->attach($o2);
 
$subject->someBusinessLogic();
$subject->someBusinessLogic();
 
$subject->detach($o2);
 
$subject->someBusinessLogic();

Ví dụ 4:

<?php
 
namespace RefactoringGuru\Observer\RealWorld;
 
/**
 * The UserRepository represents a Subject. Various objects are interested in
 * tracking its internal state, whether it's adding a new user or removing one.
 */
class UserRepository implements \SplSubject
{
    /**
     * @var array The list of users.
     */
    private $users = [];
 
    // Here goes the actual Observer management infrastructure. Note that it's
    // not everything that our class is responsible for. Its primary business
    // logic is listed below these methods.
 
    /**
     * @var array
     */
    private $observers = [];
 
    public function __construct()
    {
        // A special event group for observers that want to listen to all
        // events.
        $this->observers["*"] = [];
    }
 
    private function initEventGroup(string $event = "*"): void
    {
        if (!isset($this->observers[$event])) {
            $this->observers[$event] = [];
        }
    }
 
    private function getEventObservers(string $event = "*"): array
    {
        $this->initEventGroup($event);
        $group = $this->observers[$event];
        $all = $this->observers["*"];
 
        return array_merge($group, $all);
    }
 
    public function attach(\SplObserver $observer, string $event = "*"): void
    {
        $this->initEventGroup($event);
 
        $this->observers[$event][] = $observer;
    }
 
    public function detach(\SplObserver $observer, string $event = "*"): void
    {
        foreach ($this->getEventObservers($event) as $key => $s) {
            if ($s === $observer) {
                unset($this->observers[$event][$key]);
            }
        }
    }
 
    public function notify(string $event = "*", $data = null): void
    {
        echo "UserRepository: Broadcasting the '$event' event.\n";
        foreach ($this->getEventObservers($event) as $observer) {
            $observer->update($this, $event, $data);
        }
    }
 
    // Here are the methods representing the business logic of the class.
 
    public function initialize($filename): void
    {
        echo "UserRepository: Loading user records from a file.\n";
        // ...
        $this->notify("users:init", $filename);
    }
 
    public function createUser(array $data): User
    {
        echo "UserRepository: Creating a user.\n";
 
        $user = new User();
        $user->update($data);
 
        $id = bin2hex(openssl_random_pseudo_bytes(16));
        $user->update(["id" => $id]);
        $this->users[$id] = $user;
 
        $this->notify("users:created", $user);
 
        return $user;
    }
 
    public function updateUser(User $user, array $data): User
    {
        echo "UserRepository: Updating a user.\n";
 
        $id = $user->attributes["id"];
        if (!isset($this->users[$id])) {
            return null;
        }
 
        $user = $this->users[$id];
        $user->update($data);
 
        $this->notify("users:updated", $user);
 
        return $user;
    }
 
    public function deleteUser(User $user): void
    {
        echo "UserRepository: Deleting a user.\n";
 
        $id = $user->attributes["id"];
        if (!isset($this->users[$id])) {
            return;
        }
 
        unset($this->users[$id]);
 
        $this->notify("users:deleted", $user);
    }
}
 
/**
 * Let's keep the User class trivial since it's not the focus of our example.
 */
class User
{
    public $attributes = [];
 
    public function update($data): void
    {
        $this->attributes = array_merge($this->attributes, $data);
    }
}
 
/**
 * This Concrete Component logs any events it's subscribed to.
 */
class Logger implements \SplObserver
{
    private $filename;
 
    public function __construct($filename)
    {
        $this->filename = $filename;
        if (file_exists($this->filename)) {
            unlink($this->filename);
        }
    }
 
    public function update(\SplSubject $repository, string $event = null, $data = null): void
    {
        $entry = date("Y-m-d H:i:s") . ": '$event' with data '" . json_encode($data) . "'\n";
        file_put_contents($this->filename, $entry, FILE_APPEND);
 
        echo "Logger: I've written '$event' entry to the log.\n";
    }
}
 
/**
 * This Concrete Component sends initial instructions to new users. The client
 * is responsible for attaching this component to a proper user creation event.
 */
class OnboardingNotification implements \SplObserver
{
    private $adminEmail;
 
    public function __construct($adminEmail)
    {
        $this->adminEmail = $adminEmail;
    }
 
    public function update(\SplSubject $repository, string $event = null, $data = null): void
    {
        // mail($this->adminEmail,
        //     "Onboarding required",
        //     "We have a new user. Here's his info: " .json_encode($data));
 
        echo "OnboardingNotification: The notification has been emailed!\n";
    }
}
 
/**
 * The client code.
 */
 
$repository = new UserRepository();
$repository->attach(new Logger(__DIR__ . "/log.txt"), "*");
$repository->attach(new OnboardingNotification("1@example.com"), "users:created");
 
$repository->initialize(__DIR__ . "/users.csv");
 
// ...
 
$user = $repository->createUser([
    "name" => "John Smith",
    "email" => "john99@example.com",
]);
 
// ...
 
$repository->deleteUser($user);

Tham khảo

development/software-architecture/design-patterns/observer.txt · Last modified: 2024/08/19 09:51 by tungnt

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki