省略 super() and/or *弱化* 前提条件是否违反里氏替换原则?
Does omitting super() and/or *weakening* preconditions violate the Liskov Substitution Principle?
我最近一直在研究一些 SOLID 设计原则,我从一个来源获得的一些信息最初对我来说很有意义,但基于我能够为 thr LSP 找到的严格定义,似乎该信息可能不正确。信息具体是:
1) 不在重写的方法上回调 super() 违反了 LSP(或者至少让你容易违反)因为基础 class 的行为可能会在某些时候改变,你的子class 可能会以导致子 class 不再可替代 parent 的方式缺少该行为。这对我来说似乎很有意义,但如果有人可以详细说明 that/give 一些关于什么时候不回调 super 是合适的信息,那就太好了。
2) Subclasses 的限制不应低于 parent。示例是:如果您有一个只接受正整数的 parent class,那么您创建一个接受正整数和负整数的 subclass。所以 child 应该可以很好地代替 parent,但是 child 在这种情况下不能委托回 super。
我认为这是有道理的,但 LSP 上的信息似乎恰恰相反:child 不能 加强 先决条件。两者对我来说似乎都有意义,但 Liskov 只声明前置条件不能加强,后置条件不能削弱。有人可以帮我解释一下吗?
1) 案例,什么时候不回调 super
通常(但不总是)这种情况意味着 class 层次结构设计有问题。
如果子方法接受正确的输入类型和 returns 正确的输出类型,则不调用超级 class 实现不会违反 LSP。
仅表示问题的可能性。
这是一个当你不调用 super 方法时绝对有效的例子:
class Animal
void eat(Food food)
// Eat the food
class Cat extends Animal
void meow()
// Say meow
class AnimalOwner
Animal findAPet()
return new Animal()
class CatOwner
// we can return the subclass of Animal here
Cat findAPet()
return new Cat() // We don't need to use the parent implementation
这里是 CatOwner::findAPet()
returns a Cat
(an Animal
subclass),这在 LSP 方面是有效的,我们不调用父实现。
请注意,调用父实现并不能保证您不会陷入与不调用时相同的问题。
考虑这个例子:
class Child
Output doSomething()
parent = super::doSomething()
if parent->isNotGood():
return new OutputSubclass() // We called super, but we return
// something different, might be not safe
else:
return parent // We called super and return
// the same value, safe
2) "Preconditions cannot be strengthened in a subtype"。这也意味着先决条件(与输入参数相关的期望)可以保持不变或被削弱。
在你说的例子中,前置条件实际上被弱化了,所以不存在冲突:
Parent::doSomething(PositiveInteger value) // Only positive integers
Child::doSomething(Integer value) // Positive or negative integers,
// pre-condition is weaker
// (covers wider area of values)
不完全正确的是第一句:"Subclasses should not be less restrictive than the parent"。
当我们谈论前置条件(输入参数)时,Subclasses 可以减少限制,这就是示例中显示的内容。
Liskov 替换原则要求您可以在需要基类型时使用子类型。为此,您必须遵守基本类型的约定。对于具有方法 f、前置条件 Pre、后置条件 Post 和不变量 I 的基础 class B,这意味着
- 调用者代码仅保证 Pre 在可能使用您的对象时保持不变。如果 Pre 成立,您的先决条件也必须成立。逻辑上 Pre 表示派生方法的前提条件。就是说可以放宽前置条件因为Pre -> (Pre and condition),只要不矛盾
- 调用方代码期望 Post 在方法完成时保持不变。这意味着你可以增加更多的保证,但你不能削弱Post条件。
- 你的子class的不变量必须与基class的不变量兼容。
我会考虑显式调用 base-class 实现代码味道(构造函数除外)。使用 te,plate method pattern(or non-virtual interface in C++) 来执行 base-class 合约要好得多。在 Python 中,这看起来像:
class Base:
def publicMethod(self, x):
// do something
self.templateMethod(x)
// do something else
def templateMethod(self, x):
// has to be overriden by sub-classes
// it provides extension hooks, but
// publicMethod ensures that the overall
// functionality is correct
我最近一直在研究一些 SOLID 设计原则,我从一个来源获得的一些信息最初对我来说很有意义,但基于我能够为 thr LSP 找到的严格定义,似乎该信息可能不正确。信息具体是:
1) 不在重写的方法上回调 super() 违反了 LSP(或者至少让你容易违反)因为基础 class 的行为可能会在某些时候改变,你的子class 可能会以导致子 class 不再可替代 parent 的方式缺少该行为。这对我来说似乎很有意义,但如果有人可以详细说明 that/give 一些关于什么时候不回调 super 是合适的信息,那就太好了。
2) Subclasses 的限制不应低于 parent。示例是:如果您有一个只接受正整数的 parent class,那么您创建一个接受正整数和负整数的 subclass。所以 child 应该可以很好地代替 parent,但是 child 在这种情况下不能委托回 super。
我认为这是有道理的,但 LSP 上的信息似乎恰恰相反:child 不能 加强 先决条件。两者对我来说似乎都有意义,但 Liskov 只声明前置条件不能加强,后置条件不能削弱。有人可以帮我解释一下吗?
1) 案例,什么时候不回调 super
通常(但不总是)这种情况意味着 class 层次结构设计有问题。
如果子方法接受正确的输入类型和 returns 正确的输出类型,则不调用超级 class 实现不会违反 LSP。 仅表示问题的可能性。
这是一个当你不调用 super 方法时绝对有效的例子:
class Animal
void eat(Food food)
// Eat the food
class Cat extends Animal
void meow()
// Say meow
class AnimalOwner
Animal findAPet()
return new Animal()
class CatOwner
// we can return the subclass of Animal here
Cat findAPet()
return new Cat() // We don't need to use the parent implementation
这里是 CatOwner::findAPet()
returns a Cat
(an Animal
subclass),这在 LSP 方面是有效的,我们不调用父实现。
请注意,调用父实现并不能保证您不会陷入与不调用时相同的问题。
考虑这个例子:
class Child
Output doSomething()
parent = super::doSomething()
if parent->isNotGood():
return new OutputSubclass() // We called super, but we return
// something different, might be not safe
else:
return parent // We called super and return
// the same value, safe
2) "Preconditions cannot be strengthened in a subtype"。这也意味着先决条件(与输入参数相关的期望)可以保持不变或被削弱。 在你说的例子中,前置条件实际上被弱化了,所以不存在冲突:
Parent::doSomething(PositiveInteger value) // Only positive integers
Child::doSomething(Integer value) // Positive or negative integers,
// pre-condition is weaker
// (covers wider area of values)
不完全正确的是第一句:"Subclasses should not be less restrictive than the parent"。 当我们谈论前置条件(输入参数)时,Subclasses 可以减少限制,这就是示例中显示的内容。
Liskov 替换原则要求您可以在需要基类型时使用子类型。为此,您必须遵守基本类型的约定。对于具有方法 f、前置条件 Pre、后置条件 Post 和不变量 I 的基础 class B,这意味着
- 调用者代码仅保证 Pre 在可能使用您的对象时保持不变。如果 Pre 成立,您的先决条件也必须成立。逻辑上 Pre 表示派生方法的前提条件。就是说可以放宽前置条件因为Pre -> (Pre and condition),只要不矛盾
- 调用方代码期望 Post 在方法完成时保持不变。这意味着你可以增加更多的保证,但你不能削弱Post条件。
- 你的子class的不变量必须与基class的不变量兼容。
我会考虑显式调用 base-class 实现代码味道(构造函数除外)。使用 te,plate method pattern(or non-virtual interface in C++) 来执行 base-class 合约要好得多。在 Python 中,这看起来像:
class Base:
def publicMethod(self, x):
// do something
self.templateMethod(x)
// do something else
def templateMethod(self, x):
// has to be overriden by sub-classes
// it provides extension hooks, but
// publicMethod ensures that the overall
// functionality is correct