考虑这个ruby示例
class Animal
def walk
# In our universe all animals walk, even whales
puts "walking"
end
def run
# Implementing to conform to LSP, even though only some animals run
raise NotImplementedError
end
end
class Cat < Animal
def run
# Dogs run differently, and Whales, can't run at all
puts "running like a cat"
end
def sneer_majesticly
# Only cats can do this.
puts "meh"
end
end
方法sneer_majesticly
是否违反LSP,仅在Cat上定义,因为Animal上没有实现这个接口也不需要?
答案 0 :(得分:9)
Liskov替换原则与班级无关。它是关于类型。 Ruby没有类型作为语言功能,所以在语言功能方面谈论它们并没有多大意义。
在Ruby(以及一般的OO)中,类型基本上是协议。协议描述了对象响应哪些消息,以及它如何响应它们。例如,Ruby中一个众所周知的协议是迭代协议,它由单个消息each
组成,它接受一个块,但没有位置或关键字参数和yield
s元素顺序到块。请注意,没有与此协议对应的类或mixin。符合此协议的对象无法声明。
在此协议上有一个依赖的mixin,即Enumerable
。同样,由于没有与#34; protocol&#34;的概念相对应的Ruby构造,因此Enumerable
无法声明此依赖关系。它只在the documentation(粗略强调我的)的介绍段落中提到:
Enumerable
mixin为集合类提供了多种遍历和搜索方法,并具有排序功能。 该类必须提供方法each
,该方法会生成集合的连续成员。
那就是它。
Ruby中不存在协议和类型。他们做存在于Ruby文档,Ruby社区,Ruby程序员的头脑中,以及Ruby代码中的隐含假设中,但它们从未在代码中体现过。
所以,谈论Ruby类的LSP是没有意义的(因为类不是类型),但是根据Ruby类型谈论LSP也没什么意义(因为没有类型)。你只能根据头脑中的类型来谈论LSP(因为你的代码中没有任何内容)。
好的,咆哮。但事实上,真的,真的,真的很重要。 LSP是关于类型的。类不是类型。有些语言如C ++,Java或C♯,其中所有类也是自动类型,但即使在这些语言中,将类型(规则和约束的规范)的概念与概念分开也很重要。 class(它是对象的状态和行为的模板),如果只是因为除了那些类型的类之外还有其他的东西(例如Java和C♯中的接口以及使用Java)。 In fact, the interface
in Java is a direct port of the protocol
from Objective-C,反过来来自Smalltalk社区。 p>
呼。所以,遗憾的是,没有一个能回答你的问题:-D
LSP究竟是什么意思? LSP谈论子类型。更准确地说,它定义了一个(在它被发明时)新的子类型概念,它基于行为可替代性。很简单,LSP说:
我可以使用 S&lt;:T 类型的对象替换 T 类型的对象,而无需更改程序所需的属性。
例如,&#34;程序不会崩溃&#34;是一个理想的属性,所以我不应该通过用超类型的对象替换超类型的对象来使程序崩溃。或者您也可以从另一个方向查看它:如果我可以通过用类型的对象替换 T 类型的对象来违反程序的理想属性(例如,使程序崩溃) S ,然后 S 不是 T 的子类型。
我们可以遵循一些规则来确保我们不违反LSP:
这两个规则只是函数的标准子类型规则,它们早在Liskov之前就已为人所知。
这三条规则是限制方法签名的静态规则。利斯科夫的关键创新是四个行为规则,特别是第四条规则(&#34;历史规则&#34;):
前三条规则在Liskov之前已知,但它们是以证明理论的方式制定的,并未将别名考虑在内。规则的行为表述以及历史规则的添加使LSP适用于现代OO语言。
这是查看LSP的另一种方式:如果我有一个只知道并关心T
的检查员,并且我给他一个S
类型的对象,他是否能够发现这是一个&#34;假冒&#34;或者我可以欺骗他吗?
好的,最后你的问题是:添加sneer_majesticly
方法是否违反了LSP?答案是:否。添加 new 方法的唯一方法是违反LSP,如果此 new 方法在这样的情况下操纵旧状态仅使用旧方法无法实现的方式。由于sneer_majesticly
不会操纵任何状态,因此添加它不可能违反LSP。请记住:我们的检查员只知道Animal
,即他只知道walk
和run
。他并不了解或关心sneer_majesticly
。
如果,OTOH,你正在添加一个方法bite_off_foot
,之后猫不再行走,然后你违反了LSP,因为通过调用bite_off_foot
,检查员可以通过仅使用他所知道的方法(walk
和run
)观察动物无法观察到的情况:动物总能走路,但我们的猫突然无法行动! / p>
然而的! run
可能理论上违反了LSP。请记住:子类型的对象不能更改超类型的理想属性。现在,问题是:是 Animal
的理想属性?问题是你没有提供Animal
的任何文档,所以我们不知道它的理想属性是什么。我们唯一可以看到的是代码,它总是raise
sa NotImplementedError
(BTW实际上raise
是NameError
,因为没有名为{{的常量1)}在Ruby核心库中)。所以,问题是:是否需要属性的异常部分的NotImplementedError
?没有文件,我们无法分辨。
如果raise
定义如下:
Animal
然后它不是LSP违规。
但是,如果class Animal
# …
# Makes the animal run.
#
# @return [void]
# @raise [NotImplementedError] if the animal can't run
def run
raise NotImplementedError
end
end
定义如下:
Animal
然后它将成为LSP违规。
换句话说:如果class Animal
# …
# Animals can't run.
#
# @return [never]
# @raise [NotImplementedError] because animals never run
def run
raise NotImplementedError
end
end
的规范是&#34;总是引发异常&#34;,那么我们的检查员可以通过调用run
来发现一只猫,并观察它没有&#39; ;提出异常。但是,如果run
的规范是&#34;使动物运行或者引发异常&#34;,那么我们的检查员可以不区分猫与动物。
您将注意到run
在此示例中是否违反LSP实际上完全独立于Cat
!它实际上也完全独立于Cat
内的代码! 仅取决于文档。这是因为我在一开始就试图弄清楚:LSP是关于类型。 Ruby没有类型,因此类型只存在于程序员的头脑中。或者在此示例中:在文档注释中。
答案 1 :(得分:1)
LSP说你可以放入基本类型/接口的任何实现,它应该继续工作。所以它没有理由违反它,尽管它提出了一个有趣的问题,为什么你需要在一个实现中实现该附加接口而不是其他实现。您是否遵循单一责任原则?