Реальний світ - Принцип заміни Ліскова


14

Передумови: я розробляю рамки для обміну повідомленнями. Ця рамка дозволить:

  • відправлення повідомлень через службову шину
  • підписавшись на черги на шині повідомлень
  • передплатити теми на шині повідомлень

Наразі ми використовуємо RabbitMQ, але я знаю, що ми найближчим часом перейдемо до службової шини Microsoft (у приміщенні).

Я планую створити набір інтерфейсів та реалізацій, щоб, коли ми переходимо до ServiceBus, мені просто потрібно було б надати нову реалізацію, не змінюючи жодного клієнтського коду (тобто видавців чи підписників).

Проблема тут полягає в тому, що RabbitMQ та ServiceBus безпосередньо не перекладаються. Наприклад, RabbitMQ покладається на обміни та імена тем, тоді як ServiceBus - це все про простори імен та черги. Також між клієнтом ServiceBus та клієнтом RabbitMQ немає спільних інтерфейсів (наприклад, обидва можуть мати IConnection, але інтерфейс відрізняється - не від загального простору імен).

Отже, я можу створити інтерфейс наступним чином:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

Завдяки неперекладним властивостям двох технологій реалізація ServiceBus та RabbitMQ вищевказаного інтерфейсу мають різні вимоги. Тож моя реалізація RabbitMq IMessageReceiver може виглядати так:

public void AddSubscription(ISubscription subscriptionDetails){
  if(!subscriptionDetails is RabbitMqSubscriptionDetails){
    // I have a problem!
  }
}

Для мене рядок вище порушує правило Ліскова замінюваності.

Я розглядав можливість перегортати це, щоб підписка приймала IMessageConnection, але знову ж таки, підписка RabbitMq потребує конкретних властивостей RabbitMQMessageConnection.

Отже, мої запитання:

  • Чи я правда, що це порушує LSP?
  • Чи згодні ми, що в деяких випадках це неминуче, чи я щось пропускаю?

Сподіваємось, це зрозуміло і по темі!


Чи додавання параметра інтерфейсу до інтерфейсу є варіантом для вас? Щодо Java-синтаксису, щось на кшталт interface TestInterface<T extends ISubscription>чітко повідомляє, які типи прийнято, і що існують відмінності між реалізаціями.
Халк

@Hulk, я не вірю, тому що кожна реалізація потребує різної реалізації ISubscriptions.
GinjaNinja

1
Вибачте, що я хотів запропонувати interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Тоді реалізація може виглядати так public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(у Java).
Халк

Відповіді:


11

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

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

Інший варіант - повідомити користувачеві API чітко знати, що може виникнути ситуація неприйнятного підтипу, анотувавши інтерфейс за винятком, який може бути викинутий, наприклад UnsupportedSubscriptionException. При цьому ви визначаєте суворі умови попереднього умови під час моделювання початкового інтерфейсу і дозволено ослабити його, не кидаючи виняток, якщо тип буде правильним, не впливаючи на решту програми, яка пояснює помилку.


Спасибі. Я не можу подумати, як "перевернути" це, не пересунувши питання. Наприклад, для отримання підписки потрібно знати IModel для RabbitMQ та ще щось для ServiceBus. Я думаю, що вилучений анотація - єдиний шлях вперед.
GinjaNinja

2

Так, ваш код тут порушує LSP. У таких випадках я б використовував антикорупційний шар від шаблонів дизайну DDD. Ви можете побачити один приклад там: http://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corrup-layer/

Ідея полягає в тому, щоб максимально зменшити залежність від підсистеми шляхом чіткого виділення її, щоб мінімізувати рефакторинг при зміні її

Сподіваюся, це допомагає!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.