Home Blog Статті Переваги диспетчера повідомлень
November 17, 2023
471
9 хвилини читання

Переваги диспетчера повідомлень

У Вас великий та успішний проект, який постійно росте та розвивається. Але зовнішній вигляд та зручність використання ніяк не покажуть із чого складається такий потрібний продукт. І лише розробники розуміють усю глибину «Маріанської западини». Що я цим хочу сказати? Давайте по порядку.

З чого складається програма написана з використанням ОВП? Звісно з класів. Кожен клас відповідає за свій функціонал (в ідеалі), який він має реалізовувати. І це взаємодія є архітектурою докладання.

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

Як зрозуміти гарна архітектура чи ні? Все просто. Якщо Ви в проект додаєте новий функціонал без особливих проблем і Вам не доводиться змінювати логіку взаємодії класів – гарна архітектура. І навпаки. Чим більше Ви вноситимете зміни у взаємодію класів, тим архітектура гірша.

Що в результаті виходить? Якщо архітектура погана, то розробнику доводиться робити більше роботи, реалізуючи як новий функціонал, але правлячи каркас докладання. Як наслідок, додаткові помилки, налагодження змін та багато інших поганих моментів.

То як зробити гарну архітектуру? Універсальним інструментом для цього є досвід та знання. Перше залежить тільки від Вас, а ось невелику частину другого я постараюся розповісти нижче, ґрунтуючись на своєму досвіді.

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

Що таке диспетчер повідомлень?

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

Ну і виникає слушне питання, як це може спростити архітектуру? Все просто. При використанні диспетчера повідомлень класам не потрібно знати одне одного. Класам потрібно лише знати про самого диспетчера та про повідомлення, з яким ці класи можуть працювати. Це дозволяє спростити взаємодію класів між собою і, як наслідок, спростити архітектуру програми. Спробую пояснити докладніше.

Жирний плюс диспетчер повідомлень.

Основний плюс диспетчера повідомлень у призначенні, тобто у тому навіщо його вигадали. Як писав раніше, основне його завдання прибрати залежності об'єктів один від одного. Використовуючи диспетчер повідомлень, нам не потрібні посилання на одержувача у відправнику. Все що повинні знати відправник та одержувач – це певний формат повідомлення.

То що ми можемо поліпшити за допомогою диспетчера повідомлень? Найперше що спадає на думку – це події. Припустимо, у нас є така подія:

public class A
{
    public Action<object> OnDoIt;
}

І ми на нього підписуємось.

public class B
{
    public void Init(A a)
    {
        a.OnDoIt += DoIt;
    }
}

І нам потрібно одразу написати ось це:

public class B
{
    ...
    public void Deinit()
    {
        a.OnDoIt -= DoIt
    }
}

Як усі Ви, сподіваюся, знаєте, якщо не зробити відписку від події, то підписаний об'єкт не вийде з пам'яті. Наслідки також, підозрюю, розумієте. І якщо Ви думаєте, що це нісенітниця і не проблема, то скажу так. Я багато разів зустрічав ситуацію, коли забували робити відписку і більше того, підписку копіювали та забували виправляти плюс на мінус. Результат виходив веселий.

Тож чим тут може допомогти диспетчер повідомлень? Перше, класу B не потрібно знати про клас A. Отже, ми не прив'яжемо один до одного і якщо клас A стане не потрібен, то збирач сміття його видалить без проблем. Все, що нам потрібно – це домовитися про формат повідомлення, яке клас A може надіслати, а клас B отримати та обробити.

І тут постає питання, як диспетчер повідомлень дізнається, що клас A надсилає повідомлення класу B? Ну на те він і диспетчер, щоб знати. Описувати нутрощі диспетчера повідомлень тут не буду. Це тема окремої статті. Це тема окремої статті.

У класу A має бути посилання диспетчер повідомлень. А клас B повинен підписатися у диспетчері на потрібне йому повідомлення. І коли клас A відправляє повідомлення до диспетчера, то диспетчер переправляє це повідомлення до класу B, який підписаний на це повідомлення. І тут Ви можете сказати: Так, а в чому різниця? І там, і там потрібне посилання. І там, і там треба передплатити. І класи будуть залежні від диспетчера повідомлень. Начебто логічно, але є нюанс.

Якщо грамотно реалізувати диспетчер повідомлень, то збирач сміття може без проблем усунути підписаний клас, коли він буде вже не потрібен. Навіть якщо ми забудемо відписати клас. Наступне, класам потрібне лише одне посилання, це посилання диспетчер повідомлень. Класам знати одне про одного не потрібно.

Давайте порівняємо. Допустимо, у нас є три класи A, B, C. Класи B і C повинні реагувати на подію в класі A. А клас C реагувати на подію в класі B.

public class A
{
    public Action<object> EventInA;
}


public class B
{
    public Action<object> EventInB;

    public void Init(A a)
    {
        a.EventInA += ProcessA;
    }

    public void Deinit()
    {
        a.EventInA -= ProcessA;
    }

    private void ProcessA(object messageA)
    {
        ...
    }
}


public class C
{
    public void Init(A a, B b)
    {
        a.EventInA += ProcessA;
        b.EventInB += ProcessB;
    }

    public void Deinit()
    {
        a.EventInA -= ProcessA;
        b.EventInB -= ProcessB;
    }

    private void ProcessA(object messageA)
    {
        ...
    }

    private void ProcessB(object messageB)
    {
        ...
    }
}

Як Ви бачите, як і раніше, щоб обробити події класу нам потрібне посилання на об'єкт цього класу. Думаєте не реально? Ось Вам простий приклад.

Програма руху автомобіля. Коли їде автомобіль, то відбуваються різні події: поворот ліворуч, натиснута педаль газу, включено п'яту швидкість, натиснуто гальмо тощо. Як думаєте скільки подій буде під час руху автомобіля? Очевидно більше двох десятків. І є клас, який реагує на всі ці події, обробляє та записує їх у базу. Виходить, він повинен підписатися на всі ці події. І як виглядатиме ця передплата?

Тепер подивимося на приклад із диспетчером повідомлень.

public class MessageDispatcher
{
}


public class A
{
    public void Init(MessageDispatcher md)
    {
    }

    public void ProcessA()
    {
        md.SendMessage(messageA);
    }
}


public class B : IMessageClient
{
    public void Init(MessageDispatcher md)
    {
        md.Subscribe(MessageA, this);
    }

    public void ProcessMessage(IMessage msg)
    {
        // Обработка сообщений
    }

    public void ProcessB()
    {
        md.SendMessage(messageB);
    }
}


public class C : IMessageClient
{
    public void Init(MessageDispatcher md)
    {
        md.Subscribe(MessageA, this);
        md.Subscribe(MessageB, this);
    }

    public void ProcessMessage(IMessage msg)
    {
        // Обработка сообщений
    }
}

Краса? Все, що нам потрібно – це посилання на диспетчер повідомлень. Ми можемо підписуватися на сотні повідомлень від інших класів, не знаючи про них нічого.

Давайте розглянемо ще один приклад, де може бути корисним диспетчер повідомлень. Є клас A, у якому є клас B, який виконує обробку певних даних. І функціонал цієї обробки потрібно викликати у класі C. Як це буде виглядати у класичному варіанті. Нам потрібно буде або делегувати виклик методу обробки класу B із класу A, або повернути посилання на клас B. Візьмемо перший варіант.

public class A
{
    private B b;

    public void Init()
    {
        b = new B();
    }

    public void ProcessB(MessageC messageC)
    {
        b.ProcessB(messageC)
    }
}


public class B
{
    public void ProcessB(MessageC)
    {
        // Обрабатываем данные
    }
}


public class C
{
    public void Init(A a)
    {
    }

    public void ProcessC()
    {
        a.ProcessB(messageC);
    }
}

Як бачите нам знову потрібні посилання на класи, що реалізує потрібний функціонал. Плюс додатковий код із делегування. А якщо ці класи у різних бібліотеках і вони не повинні знати одне про одного? Тоді нам доведеться створити інтерфейси. Іще додатковий код.

А як це виглядатиме з диспетчером повідомлень.

public class MessageDispatcher
{
}


public class A
{
    private B b;

    public void Init()
    {
        b = new B();
    }
}


public class B : IMessageClient
{
    public void Init(MessageDispatcher md)
    {
        md.Subscribe(MessageC, this);
    }

    public void ProcessMessage(IMessage msg)
    {
        // Обработка сообщения
    }
}


public class C
{
    public void Init(MessageDispatcher md)
    {
    }

    public void ProcessC()
    {
        md.SendMessage(messageC);
    }
}

І знову краса. Класи не залежать один від одного. Вони взагалі не знають одне про одного.

І що ж ми маємо?

Я навів маленькі приклади якомога стандартні реалізації замінити патерном Посередник, який реалізує диспетчер повідомлень.

Як я спочатку писав, у нас в компанії на проекті використовується диспетчер повідомлень. У проекті використовується плагінна система і є функціонал перестарту плагінів. Грубо кажучи, під час перестарту всі плагіни повинні видалятися і створюватися знову. І оскільки у нас класи плагінів не прив'язуються до інших класів подіями та сильними посиланнями, то вони видаляються збирачем сміття без проблем. Що не призводить до витоків пам'яті. Це було встановлено неодноразовими тестами. І ось цей момент дозволяє подальшу розробку та підтримку проекту без огляду на проблему втрати пам'яті.

Якщо брати саме наш диспетчер повідомлень, він дає додаткову гнучкість у обробці повідомлень. Наприклад, ми можемо обробляти повідомлення по черзі асинхронно окремому потоці або синхронно в поточному потоці. Також можемо виставити пріоритети обробникам повідомлень, тобто вказати хто з обробників оброблятиме це повідомлення першим, хто другим і т.д. Можемо після обробки повідомлення перервати його подальшу обробку, щоб не витрачати час на виклики інших оброблювачів, зареєстрованих для обробки цього повідомлення. Ще реалізовано можливість повернення результатів обробки повідомлення.

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

Що стосується мене особисто, то використання диспетчера повідомлень мені приносить своєрідне задоволення. З ним багато спрощується, зокрема трудовитрати.

Мінуси?

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

Також досить часто виникає необхідність зайвого коду для додаткової обробки повідомлень.

Що ще? При налагодженні не вийде йти рядково і відстежити відсилання та отримання повідомлення простим способом. Дебаг у лоб не вийде.

Можливо, є й інші мінуси, які я забув, але скажу так. Для мене плюси затьмарюють усі мінуси використання диспетчера повідомлень. Якщо у Вас інша ситуація, то Вашому проекту не підходить цей патерн і потрібно використовувати щось інше.

Навіщо це все писав?

Все просто. Хотів поділитись своїм досвідом від використання диспетчера повідомлень. Можливо, хтось відкриє для себе диспетчер повідомлень або подивиться на нього з іншого боку і також отримає задоволення в розробці, застосовуючи цю корисну штуку.

Усім добра та удачі.

BitHide Team

Рейтинг статті

1 голосів. Рейтинг 5 / 5
  1. 5
  2. 4
  3. 3
  4. 2
  5. 1

Зміст

Популярні

September 14, 2023
Статті
Гарячі гаманці проти холодних гаманців: у чому різниця і що безпечніше?
Докладніше
November 14, 2023
Статті
Як прийняти криптовалюту для свого бізнесу
Докладніше
November 14, 2023
Статті
Як бізнес може захистити свої криптовалютні кошти від втрати?
Докладніше
July 1, 2023
BitHide оновлення
Реліз 2.23: Опція для управління користувачами
Докладніше
July 1, 2022
BitHide оновлення
Реліз 1.16: Перегляд адреси – Blockchain Explorer
Докладніше

Схожістатті

November 19, 2024
Статті
Автоматизація масових криптовалютних виплат з BitHide: Топ-5 переваг
Надсилайте криптовалюту сотням одержувачів в один клік.
Докладніше
November 15, 2024
Статті
2 години замість 16: як автоматизація масових виплат у криптовалюті спростила відправлення зарплатні
Читайте, як ми допомогли гемблінговій компанії заощадити 180 годин на рік на виплатах співробітникам.
Докладніше
November 11, 2024
Статті
BitHide х SiGMA Malta 2024: зустрічаємось на головному гемблінговому івенті року!
BitHide візьме участь у найбільшій європейській iGaming-конференції SiGMA. Чому варто поїхати, і що чекає на г...
Докладніше
Більше

У Вас ще залишилися питання?

Задайте їх у формі зворотного зв'язку. Спеціаліст BitHide відповість Вам найближчим часом.