Real World - Liskov Substitution Principle


14

Tło: opracowuję strukturę przesyłania wiadomości. Ramy te umożliwią:

  • wysyłanie wiadomości za pośrednictwem magistrali usług
  • subskrybowanie kolejek na szynie komunikatów
  • subskrybowanie tematów na szynie wiadomości

Obecnie używamy RabbitMQ, ale wiem, że w najbliższej przyszłości przejdziemy do Microsoft Service Bus (w lokalu).

Planuję utworzyć zestaw interfejsów i implementacji, aby po przejściu na ServiceBus po prostu musiałem dostarczyć nową implementację bez zmiany żadnego kodu klienta (tj. Wydawców lub subskrybentów).

Problem polega na tym, że RabbitMQ i ServiceBus nie podlegają bezpośredniemu tłumaczeniu. Na przykład RabbitMQ opiera się na wymianach i nazwach tematów, podczas gdy ServiceBus dotyczy przestrzeni nazw i kolejek. Ponadto nie ma wspólnych interfejsów między klientem ServiceBus a klientem RabbitMQ (np. Oba mogą mieć połączenie IConnection, ale interfejs jest inny - nie od wspólnej przestrzeni nazw).

Moim zdaniem mogę utworzyć interfejs w następujący sposób:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

Ze względu na nieprzekształcalne właściwości obu technologii, implementacje ServiceBus i RabbitMQ powyższego interfejsu mają różne wymagania. Więc moja implementacja IMessageReceiver w RabbitMq może wyglądać następująco:

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

Dla mnie powyższa linia łamie zasadę substytucyjności Liskowa.

Rozważyłem odwrócenie tego, aby Subskrypcja akceptowała IMessageConnection, ale znowu Subskrypcja RabbitMq wymagałaby określonych właściwości RabbitMQMessageConnection.

Tak więc moje pytania to:

  • Czy mam rację, że to łamie LSP?
  • Czy zgadzamy się, że w niektórych przypadkach jest to nieuniknione, czy coś mi brakuje?

Mam nadzieję, że jest to jasne i na temat!


Czy dodanie parametru typu do interfejsu jest dla Ciebie opcją? Jeśli chodzi o składnię Java, coś podobnego interface TestInterface<T extends ISubscription>jasno komunikuje, które typy są akceptowane i że istnieją różnice między implementacjami.
Hulk

@Hulk, nie sądzę, ponieważ każda implementacja wymagałaby innej implementacji ISubscription.
GinjaNinja

1
Przepraszam, chciałem to zaproponować interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Implementacja może wtedy wyglądać następująco public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(w java).
Hulk

Odpowiedzi:


11

Tak, psuje LSP, ponieważ zawężasz zakres podklasy ograniczając liczbę akceptowanych wartości, wzmacniasz warunki wstępne. Rodzic określa, że ​​akceptuje, ISubscriptionale dziecko nie.

To, czy jest to nieuniknione, należy do dyskusji. Czy mógłbyś całkowicie zmienić swój projekt, aby uniknąć tego scenariusza, może odwrócić relacje, popychając rzeczy do swoich producentów? W ten sposób zastępujesz usługi zadeklarowane jako interfejsy akceptujące struktury danych, a implementacje decydują, co chcą z nimi zrobić.

Inną opcją jest wyraźne poinformowanie użytkownika interfejsu API, że może wystąpić sytuacja o niedopuszczalnym podtypie, poprzez opatrzenie opisu interfejsu wyjątkiem, który może zostać zgłoszony, np UnsupportedSubscriptionException. W ten sposób zdefiniujesz ścisłe warunki wstępne podczas modelowania początkowego interfejsu i możesz go osłabić, nie rzucając wyjątku, jeśli typ jest poprawny bez wpływu na resztę aplikacji, która odpowiada za błąd.


Dzięki. Nie mogę wymyślić, jak to „przerzucić”, nie przenosząc problemu. Np. Abonament musiałby znać IModel dla RabbitMQ i coś innego dla ServiceBus, aby otrzymać wiadomość. Myślę, że wyjątek z adnotacjami jest jedyną drogą do przodu.
GinjaNinja

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.