Mundo Real - Princípio da Substituição de Liskov

14

Antecedentes: estou desenvolvendo uma estrutura de mensagens. Essa estrutura permitirá:

  • envio de mensagens através de um barramento de serviço
  • assinando filas no barramento de mensagens
  • assinando tópicos em um barramento de mensagens

Atualmente, estamos usando o RabbitMQ, mas sei que iremos mudar para o Microsoft Service Bus (no local) em um futuro muito próximo.

Eu pretendo criar um conjunto de interfaces e implementações para que, quando mudarmos para o ServiceBus, eu simplesmente precise fornecer uma nova implementação sem alterar nenhum código do cliente (ou seja, editores ou assinantes).

O problema aqui é que o RabbitMQ e o ServiceBus não são diretamente traduzíveis. Por exemplo, o RabbitMQ depende de trocas e nomes de tópicos, enquanto o ServiceBus é sobre espaços para nome e filas. Além disso, não há interfaces comuns entre o cliente ServiceBus e o cliente RabbitMQ (por exemplo, ambos podem ter um IConnection, mas a interface é diferente - não de um espaço para nome comum).

Então, ao meu ponto de vista, posso criar uma interface da seguinte maneira:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

Devido às propriedades não traduzíveis das duas tecnologias, as implementações ServiceBus e RabbitMQ da interface acima têm requisitos diferentes. Portanto, minha implementação do IMessageReceiver RabbitMq pode ficar assim:

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

Para mim, a linha acima quebra a regra de substituibilidade de Liskov.

Eu considerei inverter isso, para que uma Assinatura aceite um IMessageConnection, mas novamente a Assinatura RabbitMq exigiria propriedades específicas de um RabbitMQMessageConnection.

Então, minhas perguntas são:

  • Estou correto que isso quebra o LSP?
  • Concordamos que, em alguns casos, é inevitável ou estou perdendo alguma coisa?

Felizmente, isso é claro e sobre o assunto!

GinjaNinja
fonte
A adição de um parâmetro de tipo à interface é uma opção para você? Em termos de sintaxe Java, algo como interface TestInterface<T extends ISubscription>comunicaria claramente quais tipos são aceitos e que existem diferenças entre implementações.
Hulk #
@ Hulk, não acredito, pois cada implementação exigiria uma implementação diferente da ISubscription.
GinjaNinja
1
Desculpe, o que eu queria propor era interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }. Uma implementação pode parecer public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(em java).
Hulk

Respostas:

11

Sim, ele quebra o LSP, porque você está restringindo o escopo da subclasse limitando o número de valores aceitos, e fortalecendo as pré-condições. O pai especifica que aceita, ISubscriptionmas o filho não.

Se é inevitável, isso é motivo de discussão. Você poderia mudar completamente seu design para evitar esse cenário, talvez mudar o relacionamento empurrando coisas para seus produtores? Dessa forma, você substitui os serviços declarados como interfaces que aceitam estruturas de dados e as implementações decidem o que elas querem fazer com elas.

Outra opção é deixar explicitamente ao usuário da API que uma situação de um subtipo inaceitável pode ocorrer, anotando a interface com uma exceção que pode ser lançada, como UnsupportedSubscriptionException. Fazendo isso, você define o direito estrito de pré-condição durante a modelagem da interface inicial e pode enfraquecê-la ao não lançar a exceção, caso o tipo esteja correto sem afetar o restante do aplicativo responsável pelo erro.

Andy
fonte
Obrigado. Não consigo pensar em 'virar' sem mexer no assunto. Por exemplo, a assinatura precisaria conhecer o IModel para RabbitMQ e outra coisa para o ServiceBus para receber a mensagem. Eu acho que a exceção anotada é o único caminho a seguir.
GinjaNinja