Mundo real - Principio de sustitución de Liskov

14

Antecedentes: estoy desarrollando un marco de mensajería. Este marco permitirá:

  • envío de mensajes a través de un bus de servicio
  • suscribirse a colas en el bus de mensajes
  • suscribirse a temas en un bus de mensajes

Actualmente estamos utilizando RabbitMQ, pero sé que nos trasladaremos a Microsoft Service Bus (en las instalaciones) en un futuro muy cercano.

Planeo crear un conjunto de interfaces e implementaciones para que cuando pasemos a ServiceBus, simplemente necesite proporcionar una nueva implementación sin modificar el código del cliente (es decir, editores o suscriptores).

El problema aquí es que RabbitMQ y ServiceBus no son directamente traducibles. Por ejemplo, RabbitMQ se basa en intercambios y nombres de temas, mientras que ServiceBus se trata de espacios de nombres y colas. Además, no hay interfaces comunes entre el cliente ServiceBus y el cliente RabbitMQ (por ejemplo, ambos pueden tener una conexión IC, pero la interfaz es diferente, no de un espacio de nombres común).

Entonces, en mi opinión, puedo crear una interfaz de la siguiente manera:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

Debido a las propiedades no traducibles de las dos tecnologías, las implementaciones de ServiceBus y RabbitMQ de la interfaz anterior tienen requisitos diferentes. Entonces mi implementación de RabbitMq de IMessageReceiver puede verse así:

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

Para mí, la línea de arriba rompe la regla de sustituibilidad de Liskov.

Pensé en cambiar esto, para que una Suscripción acepte una IMessageConnection, pero nuevamente la Suscripción RabbitMq requeriría propiedades específicas de una RabbitMQMessageConnection.

Entonces, mis preguntas son:

  • ¿Estoy en lo cierto de que esto rompe LSP?
  • ¿Estamos de acuerdo en que en algunos casos es inevitable o me estoy perdiendo algo?

¡Con suerte, esto está claro y sobre el tema!

GinjaNinja
fuente
¿Agregar un parámetro de tipo a la interfaz es una opción para usted? En términos de sintaxis Java, algo así interface TestInterface<T extends ISubscription>comunicaría claramente qué tipos son aceptados, y que hay diferencias entre las implementaciones.
Hulk
@Hulk, no lo creo, ya que cada implementación requeriría una implementación diferente de ISubscription.
GinjaNinja
1
Lo siento, lo que quería proponer era interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Una implementación podría verse así public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(en Java).
Hulk

Respuestas:

11

Sí, rompe el LSP, porque al reducir el alcance de la subclase al limitar el número de valores aceptados, se refuerzan las condiciones previas. El padre especifica que acepta ISubscriptionpero el niño no.

Si es inevitable es algo para debatir. ¿Podrías cambiar completamente tu diseño para evitar este escenario, tal vez cambiar la relación introduciendo cosas en tus productores? De esta forma, reemplaza los servicios declarados como interfaces que aceptan estructuras de datos y las implementaciones deciden qué quieren hacer con ellos.

Otra opción es dejar que el usuario de la API sepa explícitamente que puede ocurrir una situación de un subtipo inaceptable, al anotar la interfaz con una excepción que podría producirse, como UnsupportedSubscriptionException. Hacer que defina la condición previa estricta durante el modelado de la interfaz inicial y se le permita debilitarla al no lanzar la excepción en caso de que el tipo sea correcto sin afectar el resto de la aplicación que explica el error.

Andy
fuente
Gracias. No puedo pensar cómo 'voltearlo' sin solo mover el problema. Por ejemplo, la suscripción necesitaría conocer el IModel para RabbitMQ y algo más para ServiceBus para recibir el mensaje. Creo que la excepción anotada es el único camino a seguir.
GinjaNinja