TungNT (Blue)

tungnt.blue@gmail.com

User Tools

Site Tools


development:software-architecture:design-patterns:observer

This is an old revision of the document!


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
 
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.1723556927.txt.gz · Last modified: 2024/08/13 13:48 by tungnt

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki