Monde réel - Principe de substitution de Liskov


14

Contexte: Je développe un cadre de messagerie. Ce cadre permettra:

  • envoi de messages via un bus de service
  • abonnement aux files d'attente sur le bus de messages
  • abonnement à des sujets sur un bus de messages

Nous utilisons actuellement RabbitMQ, mais je sais que nous allons passer à Microsoft Service Bus (sur site) dans un avenir très proche.

Je prévois de créer un ensemble d'interfaces et d'implémentations de sorte que lorsque nous passerons à ServiceBus, je n'ai qu'à fournir une nouvelle implémentation sans modifier le code client (c'est-à-dire les éditeurs ou les abonnés).

Le problème ici est que RabbitMQ et ServiceBus ne sont pas directement traduisibles. Par exemple, RabbitMQ s'appuie sur les échanges et les noms de rubrique, tandis que ServiceBus concerne les espaces de noms et les files d'attente. De plus, il n'y a pas d'interfaces communes entre le client ServiceBus et le client RabbitMQ (par exemple, les deux peuvent avoir une connexion IC, mais l'interface est différente - pas d'un espace de noms commun).

Donc à mon point, je peux créer une interface comme suit:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

En raison des propriétés non traduisibles des deux technologies, les implémentations ServiceBus et RabbitMQ de l'interface ci-dessus ont des exigences différentes. Donc, mon implémentation RabbitMq de IMessageReceiver peut ressembler à ceci:

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

Pour moi, la ligne ci-dessus rompt la règle de substituabilité de Liskov.

J'ai envisagé de retourner cela, de sorte qu'un abonnement accepte un IMessageConnection, mais encore une fois, l'abonnement RabbitMq nécessiterait des propriétés spécifiques d'un RabbitMQMessageConnection.

Donc, mes questions sont:

  • Suis-je correct que cela casse LSP?
  • Sommes-nous d'accord pour dire que dans certains cas, c'est inévitable, ou est-ce que je manque quelque chose?

J'espère que c'est clair et sur le sujet!


L'ajout d'un paramètre de type à l'interface est-il une option pour vous? En termes de syntaxe Java, quelque chose comme interface TestInterface<T extends ISubscription>cela communiquerait clairement quels types sont acceptés et qu'il existe des différences entre les implémentations.
Hulk

@Hulk, je ne le crois pas, car chaque implémentation nécessiterait une implémentation différente de ISubscription.
GinjaNinja

1
Désolé, ce que je voulais proposer était interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Une implémentation pourrait alors ressembler public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(en java).
Hulk

Réponses:


11

Oui, cela casse le LSP, car vous réduisez la portée de la sous-classe en limitant le nombre de valeurs acceptées, vous renforcez les conditions préalables. Le parent spécifie qu'il accepte, ISubscriptionmais pas l'enfant.

Que ce soit inévitable est une chose à discuter. Pourriez-vous peut-être changer complètement votre conception pour éviter ce scénario, peut-être inverser la relation en insérant des éléments dans vos producteurs? De cette façon, vous remplacez les services déclarés en tant qu'interfaces acceptant les structures de données et les implémentations décident de ce qu'ils veulent en faire.

Une autre option consiste à informer explicitement l'utilisateur de l'API, qu'une situation d'un sous-type inacceptable peut se produire, en annotant l'interface avec une exception qui pourrait être levée, telle que UnsupportedSubscriptionException. En faisant cela, vous définissez le droit de pré-condition strict lors de la modélisation de l'interface initiale et vous êtes autorisé à l'affaiblir en ne levant pas l'exception si le type est correct sans affecter le reste de l'application qui explique l'erreur.


Merci. Je ne peux pas penser comment le «retourner» sans simplement déplacer le problème. Par exemple, l'abonnement devra connaître l'IModel pour RabbitMQ et autre chose pour ServiceBus afin de recevoir le message. Je pense que l'exception annotée est la seule voie à suivre.
GinjaNinja

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.