@random55

Какой подход использовать?

Каждый раз, когда мне необходимо написать некоторый сервис, я сталкиваюсь со следующим вопросом:

Предположим нужен сервис, который осуществляет оповещения пользователей. Оповещать можно, например, в Slack, по SMS и в Telegram. Вижу 2 варианта решения

1. Создать некоторый абстрактный класс Notifier с абстрактным методом notify,
и для каждого сервиса (Slack, SMS, Telegram) создать дочерние классы, наследующие Notifier (SlackNotifier, SMSNotifier, TelegramNotifier), в которых по своему реализовать метод nofity.
После чего в контроллер из DI контейнера получать необходимую реализацию, в зависимости от конфигурации.

2. Создать вполне конкретный Notifier, а в нём как зависимость инстанцировать SlackDriver, SMSDriver или TelegramDriver реализующие общий интерфейс с методом notify, а в методе Notifier::notify() делигировать выполнение инстанцированному драйверу:
public function notify() {
    $this->driver->notify();
}


И вот я не пойму, какой подход лучше и по каким критериям? В случае с конкретным Notifier контроллер будет зависеть не от абстракции (это плохо?). В первом случае мы можем подменить сам Notifier, в другом - его драйвер. Я так понимаю это тот самый вопрос: "наследование или композиция"? Помогите разобраться.
  • Вопрос задан
  • 200 просмотров
Решения вопроса 1
@Akela_wolf
Extreme Programmer
Нотификации - в реальном мире это очень сложная тема, с большим количеством подводных граблей и регулярно меняющимися требованиями. Давайте посмотрим какие сущности у нас тут есть с точки зрения принципа единственной ответственности (SRP):

1. Канал уведомления (NotificationChannel). Вот это то самое: Email, SMS, Telegram, Slack, VK и т.д. Его ответственностью является отсылка сообщения определенным способом (определенный текст в определенный адрес). Я бы написал его интерфейс так:
interface NotificationChannel {
  void send(String to, String text);
}


2. Уведомлятель (Notifier). Его ответственностью является формирование уведомления. И тут тоже возможны варианты, поскольку разных пользователей нужно уведомлять о разных событиях и разным образом. Его ответственостью является сформировать текст сообщения и отправить через определенный канал. Интерфейс выглядит примерно так:
interface Notifier {
  void notify(User user, Event event);
}


3. Дальше возможны варианты. В частности формирование текста сообщения можно выделить в отдельную ответственность, создав класс (или интерфейс) NotificationFormatter. Если предусматривается настройка каналов уведомления для каждого пользователя, то может потребоваться какой-нибудь NotificationChannelManager и т.д. Погружаться в эту кроличью нору можно очень глубоко. Все зависит от того насколько детально вы хотите разложить подсистему на объекты.

Лично я бы начал как минимум с интерфейса Notifier, который позволяет "закрыть" подробности реализации подсистемы уведомлений от вызывающего кода. Эта граница, на мой взгляд, лишней не будет. А затем уже, по желанию или при необходимости, раскладывал его реализацию на отдельные, несвязанные классы.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
insighter
@insighter
-First time? - Huh? (C#, React, JS)
Здесь первая абстракция это UserNotifier, сервис который оповещает пользователя. Он скрывает в себе способы уведомления. Что то типа одного метода Notify. Его можно внедрять через DI контейнер.

А он внутри реализуется со знанием каналов доставки оповещения. Но и здесь бы я абстрагировался от конкретных реализаций через Slack, SMS, Telegram. Подключением конкретных реализаций должно заниматься приложение, а не реализация сервиса UserNotify, подключение также через DI.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы