Як Реалізувати Шаблон Проєктування Спостерігач: Observer Design Pattern

Лютий 1, 2024

Що таке Шаблон Спостерігач?

Шаблон Спостерігач (Observer Pattern) - це об’єкт, який інформує про зміни стану іншого об’єкту або об’єктів та повідомляє про це своїх підписників. Підписниками виступають інші об’єкти, також відомі як Спостерігачі (Observers).

Проблема

Уявімо собі ситуацію, коли на сайті інтернет-магазину є сторінка продукту. Але цього продукту немає у наявності. Покупець може кожен день перевіряти цей сайт на можливу наявність книги. У більшості випадків, це змарнований час для користувача сайту і він (вона) скоріш за все, знайдуть інший магазин з книгою у наявності та куплять її.

Можна допомогти користувачу, надати форму на сторінці. У такому випадку, користувач може залишити свою поштову адресу та підписатися на оновлення про книгу.

Таких зацікавлених користувачів може бути безліч. Як я можу реалізувати сповіщення

Реалізація

Для того, щоб зрозуміти, як реалізувати шаблон Спостерігач, давайте розглянемо приклад.

Клас EventManager

Для прикладу, реалізуємо клас EventManager. Цей клас відповідає за відстеження змін та повідомлення своїх підписників. У випадку, коли книга з’явиться у наявності, інтернет-магазин може вислати сповіщення на електронну пошту усіх зацікавлених користувачів.

class EventManager
{
    private array $listeners;

    public function addListener(
        string $event, 
        Listener $listener
    ): void {
        $this->listeners[$event][] = $listener;
    }

    public function dispatch(
        string $event, 
        array $args = []
    ): void {
        foreach ($this->listeners[$event] ?? [] as $listener) {
            $listener->execute($args);
        }
    }
}

У класі EventManager є 2 методи addListener() та dispatch(). Метод addListener() дає можливість зареєструвати слухача (або об’єкт), який зацікавлений у тому, коли подія відбудеться.

А метод dispatch(), з іншого боку, повідомляє підписників listeners про подію, яка відбулась.

Клас Listener

Клас Listener відповідатиме за отримання повідомлення. Також цей клас буде нашим підписником на подію, яка станеться у майбутньому та за відстеження якої відповідає клас EventManager.

class Listener
{
    public function execute(array $args): void
    {
        // Реалізація обробки даних
        var_dump($args);
    }
}

Використання

Для того, щоб зрозуміти, як використовувати класи EventManager та Listener, давайте розглянемо приклад.

$eventManager = new EventManager();
$listener = new Listener();

$eventManager->addListener('notify_product_stock', $listener);

$eventManager->dispatch(
    'notify_product_stock', 
    [
        'product' => [
            'id' => 1, 
            'title' => 'Шаблони Проектування',
            'qty' => 3
        ]
    ]
);

Створюємо об’єкти класів EventManager та Listener. За допомогою методу addListener(), я додаю слухача $listener до події під назвою notify_product_stock. Далі, коли подія notify_product_stock, клієнт викликає метод dispatch() та передає дані про подію.

У прикладі вище я передаю інформацію про продукт. Це може бути як вся інформація про продукт, так і якась частина, яку клієнт вирішить надати для подальшої обробки спостерігачами.

Об’єкт $listener отримує інформацію про продукт та оброблює її, наприклад, може вислати або зареєструвати сповіщення для всіх зацікавлених у продукті користувачів.

Інтерфейси

Також, бажано, щоб для EventManager та Listener були реалізовані інтерфейси. Це надасть змогу використовувати типізовані аргументи для методів класу EventManager та використання цих інтерфейсів у клієнтському коді.

Інтерфейс для класу Listener.

interface ListenerInterface
{
    public function execute(array $args): void;
}

Інтерфейс для класу EventManager.

interface EventManagerInterface
{
    public function addListener(
        string $event, 
        ListenerInterface $listener
    ): void;
    
    public function dispatch(string $event, array $args): void;
}

Використання вище створених інтерфейсів можна додати класам.

Клас Listener:

class Listener implements ListenerInterface 
{
    //реалізація
}

Клас EventManager:

class EventManager implements EventManagerInterface 
{
    //реалізація
}

Також, можна додати реалізацію Data Transfer Object (DTO) замість використання масиву $args.

Плюси та Мінуси

Розглянемо плюси та мінуси шаблону Спостерігач.

Плюси

Давайте почнемо з плюсів. По перше, шаблон Спостерігач, якщо правильно використовувати, може додавати підписників під час виконання програми.

Також, цей шаблон чудово допомагає в реалізації коду, який дотримується принципу Open/Closed. Можемо додавати нових підписників без зміни коду Спостерігача. Але для цього необхідні інтерфейси.

Мінуси

У кожного шаблону також можуть бути й мінуси. При реалізації шаблону Спостерігач, можлива ситуація, коли підписники отримують інформацію у випадковому порядку. Але, думаю, це не велика проблема реалізувати реєстрацію нових підписників з додавання порядку сортування.

Висновки

У даному дописі, ми розглянули дізналися що таке Шаблон Спостерігач, яку проблему (одну з проблем) ми вирішуємо при використанні цього шаблону. Також реалізували класи EventManager та Listener з інтерфейсами EventManagerInterface та ListenerInterface.

Клас EventManager також може мати альтернативну назву Publisher, а класів типу Listener (також можна назвати Subscriber) може бути стільки, скільки різних функцій та логіки необхідно системі.

Підписуйтеся на канал “Спільнота програмістів - Developer & Code” в телеграмі


Макс Пронько

Макс Пронько - Програміст, CEO компанії Pronko Consulting, розбираємо Веб технології. Автор YouTube каналу Макс Пронько. Телеграм група сайту.

2024 © Developer & Code. Усі права захищені. Зроблено з ❤️ для 🇺🇦.