Справочная информация: я разрабатываю систему обмена сообщениями. Эта структура позволит:
- отправка сообщений через сервисную шину
- подписка на очереди в шине сообщений
- подписка на темы в шине сообщений
В настоящее время мы используем RabbitMQ, но я знаю, что мы перейдем на Microsoft Service Bus (в Premise) в самое ближайшее время.
Я планирую создать набор интерфейсов и реализаций, чтобы при переходе на ServiceBus мне просто нужно было предоставить новую реализацию без внесения каких-либо изменений в код клиента (т. Е. Издателей или подписчиков).
Проблема в том, что RabbitMQ и ServiceBus не могут быть переведены напрямую. Например, RabbitMQ опирается на биржи и имена тем, а ServiceBus - это пространства имен и очереди. Также нет общих интерфейсов между клиентом ServiceBus и клиентом RabbitMQ (например, оба могут иметь IConnection, но интерфейс отличается - не от общего пространства имен).
Итак, на мой взгляд, я могу создать интерфейс следующим образом:
public interface IMessageReceiver{
void AddSubscription(ISubscription subscriptionDetails)
}
Из-за непереводимых свойств двух технологий реализации ServiceBus и RabbitMQ вышеприведенного интерфейса предъявляют различные требования. Так что моя реализация RabessMq для IMessageReceiver может выглядеть так:
public void AddSubscription(ISubscription subscriptionDetails){
if(!subscriptionDetails is RabbitMqSubscriptionDetails){
// I have a problem!
}
}
Для меня вышеприведенная линия нарушает правило замещаемости Лискова.
Я подумал об этом, чтобы подписка приняла IMessageConnection, но опять-таки для подписки RabbitMq потребовались бы определенные свойства RabbitMQMessageConnection.
Итак, мои вопросы:
- Я прав, что это нарушает LSP?
- Согласны ли мы с тем, что в некоторых случаях это неизбежно или я что-то упускаю?
Надеюсь, это понятно и по теме!
источник
interface TestInterface<T extends ISubscription>
бы ясно показывает, какие типы принимаются, и что есть различия между реализациями.interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }
. Реализация может выглядеть такpublic class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }
(в Java).Ответы:
Да, это нарушает LSP, потому что вы сужаете область действия подкласса, ограничивая количество принятых значений, вы усиливаете предварительные условия. Родитель указывает, что принимает,
ISubscription
а ребенок - нет.То, неизбежно ли это, является предметом обсуждения. Не могли бы вы, возможно, полностью изменить свой дизайн, чтобы избежать этого сценария, возможно, перевернуть отношения, подтолкнув вещи к своим продюсерам? Таким образом вы заменяете сервисы, объявленные как интерфейсы, принимающие структуры данных, и реализации решают, что они хотят с ними делать.
Другой вариант - сообщить пользователю API о том, что может возникнуть ситуация недопустимого подтипа, аннотируя интерфейс исключением, которое может быть выдано, например
UnsupportedSubscriptionException
. При этом вы определяете строгое предварительное условие прямо во время моделирования начального интерфейса и можете ослабить его, не выбрасывая исключение, если тип будет правильным, не затрагивая остальную часть приложения, которое объясняет ошибку.источник
Да, ваш код нарушает LSP здесь. В таких случаях я бы использовал Антикоррупционный слой из DDD Design Patterns. Вы можете увидеть один пример там: http://www.markhneedham.com/blog/2009/07/07/domain-driven-design-anti-corruption-layer/
Идея состоит в том, чтобы максимально уменьшить зависимость от подсистемы путем ее явной изоляции, чтобы минимизировать рефакторинг при его изменении.
Надеюсь, это поможет !
источник