Mondo reale - Principio di sostituzione di Liskov


14

Contesto: sto sviluppando un framework di messaggistica. Questo quadro consentirà:

  • invio di messaggi tramite un bus di servizio
  • iscriversi alle code sul bus dei messaggi
  • sottoscrivendo argomenti su un bus messaggi

Attualmente stiamo usando RabbitMQ, ma so che passeremo al Microsoft Service Bus (on Premise) nel prossimo futuro.

Ho intenzione di creare una serie di interfacce e implementazioni in modo che quando passiamo a ServiceBus, devo semplicemente fornire una nuova implementazione senza modificare alcun codice client (ad esempio editori o abbonati).

Il problema qui è che RabbitMQ e ServiceBus non sono direttamente traducibili. Ad esempio, RabbitMQ si basa su scambi e nomi di argomenti, mentre ServiceBus è interamente dedicato agli spazi dei nomi e alle code. Inoltre, non ci sono interfacce comuni tra il client ServiceBus e il client RabbitMQ (ad esempio, entrambi possono avere una connessione IC, ma l'interfaccia è diversa, non da uno spazio dei nomi comune).

Quindi a mio punto, posso creare un'interfaccia come segue:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

A causa delle proprietà non traducibili delle due tecnologie, le implementazioni ServiceBus e RabbitMQ dell'interfaccia di cui sopra hanno requisiti diversi. Quindi la mia implementazione RabbitMq di IMessageReceiver potrebbe apparire così:

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

Per me, la linea sopra infrange la regola della sostituibilità di Liskov.

Ho pensato di capovolgerlo, in modo che una sottoscrizione accetti un IMessageConnection, ma ancora una volta la sottoscrizione RabbitMq richiederebbe proprietà specifiche di un RabbitMQMessageConnection.

Quindi, le mie domande sono:

  • Sono corretto che questo rompe LSP?
  • Siamo d'accordo sul fatto che in alcuni casi sia inevitabile o mi sto perdendo qualcosa?

Speriamo che sia chiaro e in tema!


L'aggiunta di un parametro di tipo all'interfaccia è un'opzione per te? In termini di sintassi Java, qualcosa del genere interface TestInterface<T extends ISubscription>comunicarebbe chiaramente quali tipi sono accettati e che ci sono differenze tra le implementazioni.
Hulk,

@Hulk, non ci credo, poiché ogni implementazione richiederebbe una diversa implementazione di ISubscription.
GinjaNinja,

1
Scusa, quello che volevo proporre era interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Un'implementazione potrebbe quindi apparire public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(in Java).
Hulk,

Risposte:


11

Sì, interrompe l'LSP, poiché stai restringendo l'ambito della sottoclasse limitando il numero di valori accettati, stai rafforzando le pre-condizioni. Il genitore specifica che accetta ISubscriptionma il figlio no.

Se è inevitabile è una cosa da discutere. Potresti forse cambiare completamente il tuo design per evitare questo scenario, forse capovolgere la relazione spingendo cose nei tuoi produttori? In questo modo si sostituiscono i servizi dichiarati come interfacce che accettano le strutture di dati e le implementazioni decidono cosa vogliono fare con loro.

Un'altra opzione è far sapere esplicitamente all'utente dell'API che potrebbe verificarsi una situazione di un sottotipo inaccettabile, annotando l'interfaccia con un'eccezione che potrebbe essere generata, ad esempio UnsupportedSubscriptionException. In questo modo si definisce il diritto preliminare rigoroso durante la modellazione dell'interfaccia iniziale e si può indebolirlo non generando l'eccezione nel caso in cui il tipo sia corretto senza influire sul resto dell'applicazione che rappresenta l'errore.


Grazie. Non riesco a pensare a come "capovolgerlo" senza spostare il problema. Ad esempio, per ricevere il messaggio è necessario conoscere l'IModel per RabbitMQ e qualcos'altro per ServiceBus. Penso che l'eccezione annotata sia l'unica via da seguire.
GinjaNinja,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.