1

我最近开始阅读有关 Liskov 替换原则 ( LSP ) 的内容,并且我正在努力完全理解“不能在子类型中加强先决条件”这一限制的含义。在我看来,这种限制与建议应该最小化或完全避免从基类向下转换为派生类的需要的设计原则相冲突。

也就是说,我从一个Animal类开始,并派生出动物DogBirdHuman。LSP 对先决条件的限制显然符合自然,因为没有狗、鸟或人应该比一般动物类别更受限制。Bird.fly()坚持 LSP,派生类Human.makeTool()将添加特殊功能,例如Animal.

基类对每个可能的动物子类型的每个可能的特征都有虚拟方法感觉有点荒谬Animal,但如果没有,那么我需要向下转换Animal对其底层子类型的引用以访问这些独特的特征。然而,这种低调的需求通常被认为是糟糕设计的危险信号。Wikipedia 甚至暗示,由于 LSP,向下转换被认为是不好的做法

那么我错过了什么?

额外的问题:再次考虑上面描述的类层次结构Animals。显然,如果Animal.setWeight(weight)只需要一个非负数,这将违反 LSP,但Human.setWeight(weight)加强了这个前提条件并要求一个小于 1000 的非负数。但是对于 的构造函数呢Human,它可能看起来像Human(weight, height, gender)?如果构造函数对重量施加限制,是否会违反 LSP?如果是这样,应该如何重新设计这种层次结构以尊重衍生动物物理特性的明确界限?

4

2 回答 2

1

编程的许多方面都涉及权衡,而 SOLID 原则就是其中之一。如果有某种动作可以以相同的方式对类的几乎所有派生或接口的实现进行,并且不是接口的主要目的的一部分,但一些特定的派生或实现可能如果有更好的方法,“接口隔离原则”会建议此类操作不包含在通用接口(*)中。在这种情况下,接收对非特定类型的引用的代码检查实际对象是否具有某些“特殊”功能并在有时使用它们可能会有所帮助。例如,IEnumerable<Animal>ICollection<Animal>ICollection[注意List<Cat>实现后者而不是前者] 并且——如果是这样——向下转型并使用该Count方法]。在这种情况下向下转换没有任何问题,因为该方法不需要传入的实例实现这些接口——它只是在它们实现时效果更好。

(*) 恕我直言,IEnumerable应该包含一种描述序列属性的方法,例如计数是否已知,是否将永远包含相同的项目等,但事实并非如此。

向下转换的另一种用途发生在一个人将拥有一组对象的集合,并且知道每个组中的特定对象实例彼此“兼容”的情况下,即使一个组中的对象可能与另一组中的对象不兼容. 例如,该MaleCat.MateWith()方法可能只接受 的一个实例FemaleCat,而FemaleKangaroo.MateWith()with 可能只接受 的一个实例MaleKangaroo(),但对于 Noah 来说,拥有一组交配的动物最实用的方法是让每种动物都有一个MateWith()接受的方法anAnimal和向下转换为正确的类型(并且可能还有一个CanMateWith()属性)。如果 aMatingPair包含 aFemaleHamsterMaleWolf,则尝试调用Breed()该配对上的方法将在运行时失败,但如果代码避免构造不兼容的配对配对,则永远不会发生此类故障。请注意,泛型可以大大减少对这种向下转换的需求,但不能完全消除它。

在确定向下转换是否违反 LSP 时,50,000 美元的问题是一个方法是否会为任何可能传入的东西维护它的契约。如果MateWith()方法的契约指定它只保证在返回 true的特定实例Animal上有效,CanMateWith()当给定某些子类型时它会失败的事实Animal不会违反 LSP。一般来说,让方法在编译时拒绝其类型不能保证可用的对象是很有用的,但在某些情况下,代码可能知道某些对象实例的类型之间的关系,而这些关系不能用语法来表达[例如,事实上一个MatingPair将持有两个Animal可以成功繁殖的实例]。虽然向下转换通常是一种代码味道,但当它以符合对象契约的方式使用时并没有错。

于 2014-04-22T16:00:17.317 回答
1

LSP 是关于行为子类型的。粗略地说,BAif 的一个子类型,它总是可以在A预期的地方使用。此外,这种用法不应改变预期的行为。

因此,考虑到 LSP 应用,重点是“预期行为A”是什么。在您的示例中,它是Animal. Animal设计所有动物通用的有用界面并不是那么简单。

坚持 LSP,派生类将添加特殊功能,例如AnimalBird.fly()Human.makeTool()常见的功能。

不完全的。LSP 假设您只处理Animals。好像不能低头似的。因此,您的HumanBird其他动物可以有任何方法、构造函数或其他任何东西。它根本与LSP无关。当用作Animals 时,它们的行为应该符合预期。

问题是这样的接口非常有限。在实践中,我们经常不得不使用类型切换来让鸟儿飞翔,让人们制作有用的工具。

主流 OOP 语言中的两种常见方法是:

  1. 垂头丧气
  2. 访客模式

在这种情况下向下转换没有任何问题,因为这是您通常在不支持本机变体类型的语言中进行类型切换的方式。您可以花大量时间引入接口层次结构以避免显式向下转换,但通常它只会降低代码的可读性和难以维护。

于 2014-04-24T12:38:51.183 回答