ШАБЛОНЫ ПРОЕКТИРОВАНИЯ

Шаблон проектирования посредника в .NET C#

Узнайте о шаблоне проектирования посредника в .NET C# с примерами кода.

Определение шаблона проектирования посредника

Шаблон проектирования посредника – это один из шаблонов поведенческого проектирования.

Согласно Википедии, определение выглядит следующим образом:

Суть шаблона посредника заключается в том, чтобы «определить объект, который инкапсулирует то, как взаимодействует набор объектов». Он способствует слабой связи, не позволяя объектам явно ссылаться друг на друга, и позволяет изменять их взаимодействие независимо друг от друга. Классы клиентов могут использовать посредник для отправки сообщений другим клиентам и могут получать сообщения от других клиентов через событие в классе посредника.

Поэтому, говоря простыми словами, шаблон проектирования посредника используется для управления взаимодействием между группами объектов (похожих или нет), сохраняя объекты изолированными друг от друга.

Чтобы понять это, позвольте мне объяснить больше на простом примере.

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

Таким образом, при реализации этого у вас будет класс User с методом SendMessage.

Однако вам по-прежнему необходимо иметь доступ к другим пользователям, чтобы вы могли направить им свое сообщение. Кроме того, всякий раз, когда пользователь покидает чат, вам также необходимо удалить этого пользователя из всех других пользователей, чтобы они не отправляли ему сообщения.

У вас есть несколько способов добиться этого:

  1. Определение списка Пользователей внутри каждого Пользователя и обновление этих списков всякий раз, когда Пользователь входит или выходит из комнаты чата.
  2. Использование другого класса или модуля в качестве посредника для обработки списка пользователей и способа отправки и получения сообщений между разными пользователями.

Первый вариант подойдет, но он не годится, потому что:

  1. Класс User будет делать слишком много вещей, которые на самом деле не связаны с его основной работой.
  2. Каждый User теперь знает о других пользователях, что нелогично.
  3. Если мы хотим применить некоторые бизнес-правила, по которым пользователю разрешено общаться с каким пользователем, это будет очень сложно.
  4. Слишком много обработки для ведения всех списков пользователей.

Теперь, если мы рассмотрим второй вариант:

  1. Класс User выполняет свою работу и только работу.
  2. Вся логика ведения списка пользователей централизована в одном классе или модуле.
  3. Это позволяет нам легко применять бизнес-правила и агрегации, если это необходимо.

Как видите, лучше пойти по второму пути. Теперь позвольте мне сказать вам, что во втором варианте вы собираетесь реализовать шаблон проектирования посредника.



Диаграмма классов шаблона проектирования посредника

Что я заметил в шаблоне проектирования посредника, так это то, что его можно реализовать более чем в одном варианте.

Иногда можно использовать События, Делегаты или Прямые методы. Иногда вы можете использовать Посредник во многих местах и ​​внедрить его как зависимость, или вы можете использовать его только в централизованном менеджере.

Таким образом, это заставило меня рассматривать Шаблон проектирования посредника как концепцию, а не шаблон.

Однако, чтобы помочь вам представить себе, каким может быть шаблон проектирования посредника, вот диаграмма классов из Википедии.

Пример кода

Теперь пришло время углубиться в код, чтобы понять шаблон проектирования посредника. Сейчас мы собираемся реализовать пример Chat Room.

Начнем с класса User.

Что мы можем здесь заметить:

  1. Мы определили делегат MessageBroadcastedEventHandler для использования в качестве события, запускаемого каждым пользователем при отправке сообщения.
  2. Тогда очевидно, что мы определили event MessageBroadcastedEventHandler MessageBroadcasted в классе User.
  3. Мы также определили protected void OnMessageBroadcasted(string message) как лучший способ обработки запуска события.
  4. Каждый User имеет свойство Name.
  5. Кроме того, у каждого User есть метод SendMessage, который вызывается основным модулем, когда определенный User отправляет сообщение. Здесь мы просто запускаем событие через OnMessageBroadcasted.
  6. Метод ReceiveMessage используется для информирования User о передаче сообщения. Здесь мы просто записываем некоторую информацию для тестирования.

Теперь давайте перейдем к классу ChatRoom, который является нашим классом Mediator.

Что мы можем здесь заметить:

  1. Мы определили частный список Пользователей.
  2. Мы определили метод AddUser, который будет вызываться при добавлении нового User в ChatRoom. Здесь мы добавляем пользователя в приватный список и подписываемся на его событие MessageBroadcasted.
  3. Мы определили метод RemoveUser, который будет вызываться при удалении User из ChatRoom. Здесь мы удаляем пользователя из личного списка и отписываемся от его события MessageBroadcasted.
  4. Мы определили метод OnMessageBroadcasted для обработки события MessageBroadcasted каждого User. Что мы делаем здесь, так это зацикливаемся на всех Пользователях, кроме того, кто транслирует сообщение, и запускаем их метод ReceiveMessage.

Переходим к основному модулю.

Что мы можем здесь заметить:

  1. Мы определяем 3 пользователей.
  2. Мы создали комнату чата и добавили трех пользователей одного за другим.
  3. Затем Ахмед начал со слов «Привет всем». Вот что мы получили в консоли:
    «Тарек» получил сообщение от «Ахмеда»: «Привет всем».
    «Хасан» получил сообщение от «Ахмеда»: «Привет всем».
  4. Затем Тарек ответил: «Привет, Ахмед». Мы получили это в консоли:
    «Ахмед» получил сообщение от «Тарека»: «Привет, Ахмед».
    «Хасан» получил сообщение от «Тарека»: «Привет, Ахмед».
  5. Затем Тарек удаляется из чат-рома.
  6. Затем Тарек отправил сообщение «Кто-нибудь меня слышит?». Однако в консоли мы ничего не получили, потому что Тарека уже удалили из чата.
  7. Затем Хасан отправил сообщение: «Я думаю, что Тарек отключен». Вот что мы получили в консоли:
    «Ахмед» получил сообщение от «Хасана»: «Я думаю, что Тарек отключен».

Итак, теперь все работает как надо, и важно то, что наши объекты (в нашем случае — Пользователи) слабо связаны.

Кроме того, если мы хотим применить некоторые бизнес-правила, мы можем легко это сделать, потому что у нас есть отношения и взаимодействия, централизованные в одном месте. Это также упрощает применение агрегатов.

Заблуждение

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

Шаблон проектирования посредника может выполнять одну и ту же работу с различными типами объектов. Одним из известных примеров является реализация шаблона Request-Handler.

Проще говоря, если у вас есть входные данные или запрос, которые будут направляться через ряд различных обработчиков (могут быть предопределены или загружены во время выполнения), лучше всего использовать шаблон проектирования посредника.

Основное преимущество, которое вы получите, используя Шаблон проектирования посредника, заключается в том, что вы получите слабосвязанный дизайн, так что запрос и обработчики не будут знать друг о друге.

Давайте посмотрим пример.

Пример обработчика запроса

Допустим, у нас есть система управления сотрудниками, и у нас есть разные действия, которые нужно выполнять всякий раз, когда нанимается новый сотрудник.

В этом случае вы не хотите запускать или инициировать все эти действия в одном месте, как метод CreateEmployee на бизнес-уровне. Это сработает, но вы получите раздутый бизнес-уровень. Кроме того, каждый раз, когда вы хотите добавить новое действие, вы будете изменять метод CreateEmployee. Это нехорошо, так как вы можете что-то сломать, когда не хотели.

Поэтому здесь хорошо использовать шаблон Request-Handler.

Примечание. Для простоты здесь будут опущены некоторые рекомендации, чтобы сосредоточить внимание на основной идее шаблона Request-Handler.

Что мы можем здесь заметить:

  1. Мы определили простой класс Employee.
  2. Мы определили простой интерфейс IAddEmployeeRequest. У него есть только одно свойство Employee, которое нужно добавить.
  3. Мы определили простой класс AddEmployeeRequest, реализующий интерфейс IAddEmployeeRequest.
  4. Мы определили интерфейс IAddEmployeeRequestHandler для представления любого обработчика запроса IAddEmployeeRequest.
  5. У нас есть две простые реализации интерфейса IAddEmployeeRequestHandler; AddEmployeeRequestHandler1 и AddEmployeeRequestHandler2.

Что мы можем здесь заметить:

  1. Мы определили класс AddEmployeeMediator.
  2. Он поддерживает список обработчиков IAddEmployeeRequestHandler.
  3. Когда запрос поступает через метод Handle, он зацикливается на всех зарегистрированных обработчиках и запускает их метод Handle.
  4. RegisterHandler и UnregisterHandler — это два метода регистрации и отмены регистрации обработчиков.

Запустив это, мы получим следующий результат:

AddEmployeeRequestHandler1 обрабатывает Ахмеда
AddEmployeeRequestHandler2 обрабатывает Ахмеда
AddEmployeeRequestHandler1 обрабатывает Тарека
AddEmployeeRequestHandler2 обрабатывает Тарека

Отлично. Теперь наши запросы обрабатываются разными обработчиками без жесткой связи запросов и обработчиков.

Конечно, пример кода не в лучшем виде, и его можно было бы улучшить, предоставив больше абстракции и выполнив некоторый рефакторинг, но это то, что вы можете легко сделать, верно?

Прежде чем оставить этот шаг в нашем путешествии, позвольте мне сказать вам, что одной из известных библиотек, реализующих эту концепцию, является MediatR.

Что такое МедиатР?

MediatR, по определению команды, и я цитирую, это:

Простая, не амбициозная реализация посредника в .NET

Внутрипроцессный обмен сообщениями без зависимостей.

Поддерживает запросы/ответы, команды, запросы, уведомления и события, синхронные и асинхронные с интеллектуальной диспетчеризацией через общую дисперсию C#.

Если хотите, можете посмотреть несколько примеров использования MediatR на странице эта вики.

В настоящее время он активно используется с .NetCore, и известно, что он используется при реализации разделения ответственности команд и запросов (CQRS).

Я действительно рекомендую вам попробовать.

Последние мысли

Шаблон проектирования посредника — один из лучших шаблонов. В какой-то момент я понял, что использовал его, даже не зная об этом.

Иногда слишком очевидно, когда использовать этот шаблон, а иногда это становится немного сложнее.

Чтобы узнать больше о шаблоне проектирования посредника, я рекомендую не просто довольствоваться тем, что вы узнали из этой статьи, а продолжать проводить исследования и испытания. Это поможет вам лучше понять его.

Надеюсь, вы нашли этот контент полезным. Если вы хотите поддержать:

▶ Если вы еще не являетесь участником Medium, вы можете использовать мою реферальную ссылку, чтобы я мог получать часть ваших сборов от Medium. > вы ничего не платите.
▶ Подпишитесь на мою рассылку новостей, чтобы получать рекомендации, руководства, подсказки, подсказки и многое другое прямо на ваш почтовый ящик.

Другие источники

Это другие ресурсы, которые могут оказаться полезными.