现实世界-Liskov替代原理


14

背景:我正在开发一个消息传递框架。该框架将允许:

  • 通过服务总线发送消息
  • 订阅消息总线上的队列
  • 订阅消息总线上的主题

我们目前正在使用RabbitMQ,但我知道我们将在不久的将来迁移到Microsoft Service Bus(在Premise上)。

我计划创建一组接口和实现,以便当我们转向ServiceBus时,我只需要提供一个新的实现而无需修改任何客户端代码(即发布者或订阅者)。

这里的问题是RabbitMQ和ServiceBus不能直接翻译。例如,RabbitMQ依赖于交换和主题名称,而ServiceBus全部与命名空间和队列有关。同样,在ServiceBus客户端和RabbitMQ客户端之间没有公共接口(例如,两者都可以具有IConnection,但是接口是不同的-不同于公共名称空间)。

因此,就我而言,我可以创建如下界面:

public interface IMessageReceiver{
  void AddSubscription(ISubscription subscriptionDetails)
}

由于这两种技术的不可翻译特性,上述接口的ServiceBus和RabbitMQ实现具有不同的要求。因此,我的IMessageReceiver的RabbitMq实现可能如下所示:

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

对我来说,上面的线打破了李斯科夫的可替代性规则。

我考虑过将其翻转,以便订阅可以接受IMessageConnection,但是RabbitMq订阅再次需要RabbitMQMessageConnection的特定属性。

因此,我的问题是:

  • 我是否认为这会破坏LSP?
  • 我们是否同意在某些情况下这是不可避免的,还是我错过了一些东西?

希望这很清楚,而且是话题!


向接口添加类型参数是您的一个选择吗?就Java语法而言,类似这样的方法interface TestInterface<T extends ISubscription>可以清楚地传达接受哪些类型,以及实现之间的差异。
绿巨人

@Hulk,我不这么认为,因为每个实现都需要ISubscription的不同实现。
GinjaNinja

1
抱歉,我想提出的是interface IMessageReceiver<T extends ISubscription>{void AddSubscription(T subscriptionDetails); }。然后,实现可能看起来像public class RabbitMqMessageReceiver implements IMessageReceiver<RabbitMqSubscriptionDetails> { public void AddSubscription(RabbitMqSubscriptionDetails subscriptionDetails){} }(在Java中)。
绿巨人

Answers:


11

是的,它确实破坏了LSP,因为您通过限制可接受值的数量来缩小子类的范围,因此您正在加强前提条件。父母指定接受,ISubscription但孩子不接受。

是否不可避免,需要讨论。您是否可以完全更改您的设计来避免这种情况,或者通过将内容推入生产者来扭转这种关系?这样,您就可以替换声明为接受数据结构的接口的服务,而实现则决定它们要如何处理它们。

另一种选择是通过用可能引发的异常(例如)来注释接口,以使API的用户明确地知道可能发生子类型不可接受的情况UnsupportedSubscriptionException。这样做是为了在初始接口建模期间定义严格的前提条件权限,并允许通过不抛出异常来弱化它(如果类型正确),而又不影响导致错误的应用程序的其余部分。


谢谢。我想不出解决问题的办法就可以“翻转”它。例如,订阅将需要知道RabbitMQ的IModel和ServiceBus的其他信息,以便接收消息。我认为带注释的例外是前进的唯一途径。
GinjaNinja

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.