Real World - Principio di sostituzione di Liskov

14

Background: sto sviluppando un framework di messaggistica. Questo framework consentirà:

  • invio di messaggi su un bus di servizio
  • abbonarsi alle code sul bus dei messaggi
  • abbonarsi agli argomenti su un bus dei messaggi

Attualmente stiamo usando RabbitMQ, ma so che ci sposteremo su Microsoft Service Bus (in Premessa) nel prossimo futuro.

Ho intenzione di creare un insieme di interfacce e implementazioni in modo tale che quando ci spostiamo su ServiceBus, ho semplicemente bisogno di 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 si basa su Namespace e Code. Inoltre, non ci sono interfacce comuni tra il client ServiceBus e il client RabbitMQ (ad esempio, entrambi possono avere un IConnection, ma l'interfaccia è diversa - non da uno spazio dei nomi comune).

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

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

A causa delle proprietà non traducibili delle due tecnologie, le implementazioni di ServiceBus e RabbitMQ dell'interfaccia precedente hanno requisiti diversi. Quindi la mia implementazione di IMessageReceiver su RabbitMq potrebbe essere simile a questa:

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

Per me, la riga precedente infrange la regola di sostituibilità di Liskov.

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

Quindi, le mie domande sono:

  • Ho corretto che ciò interrompe LSP?
  • Siamo d'accordo che in alcuni casi è inevitabile, o, mi manca qualcosa?

Si spera che questo sia chiaro e in tema!

    
posta GinjaNinja 24.11.2016 - 12:40
fonte

2 risposte

11

Sì, interrompe il LSP, perché limitando l'ambito della sottoclasse limitando il numero di valori accettati, si stanno rafforzando le pre-condizioni. Il genitore specifica accetta ISubscription ma il bambino no.

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

Un'altra opzione è lasciare che l'utente dell'API sappia esplicitamente che potrebbe verificarsi una situazione di un sottotipo inaccettabile annotando l'interfaccia con un'eccezione che potrebbe essere generata, come UnsupportedSubscriptionException . In questo modo, durante la modellazione dell'interfaccia iniziale definisci la condizione di pre-condizione rigorosa e puoi indebolirla evitando di lanciare l'eccezione se il tipo è corretto senza influire sul resto dell'applicazione che tiene conto dell'errore.

    
risposta data 24.11.2016 - 16:05
fonte
2

Sì, il tuo codice interrompe LSP qui. In questi casi, utilizzerei il livello anti-corruzione dai modelli di progettazione DDD. Puoi vedere un esempio qui: link

L'idea è di ridurre il più possibile la dipendenza dal sottosistema isolandolo esplicitamente, per minimizzare il refactoring quando lo si cambia

Spero che ti aiuti!

    
risposta data 25.11.2016 - 08:27
fonte

Leggi altre domande sui tag