kon*_*ung 2 ruby oop liskov-substitution-principle solid-principles
考虑这个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
Run Code Online (Sandbox Code Playgroud)
方法是否sneer_majesticly违反LSP,仅在Cat上定义,因为Animal上没有实现这个接口也不需要它?
Jör*_*tag 11
利斯科夫替代原则与阶级无关.这是关于类型.Ruby没有类型作为语言功能,因此在语言功能方面谈论它们并没有多大意义.
在Ruby(以及一般的OO)中,类型基本上是协议.协议描述了对象响应哪些消息,以及它如何响应它们.例如,Ruby中一个众所周知的协议是迭代协议,它由单个消息组成,each它接受一个块,但没有位置或关键字参数和yields元素顺序到块.请注意,没有与此协议对应的类或mixin.符合此协议的对象无法声明.
有一个mixin 依赖于这个协议,即Enumerable.同样,由于没有与"协议"概念相对应的Ruby构造,因此无法Enumerable声明此依赖关系.它只在文件的介绍段落中提到(大胆强调我的):
该
Enumerable混入提供了集合类与几个遍历和搜索方法,以及排序的功能.该类必须提供一个方法each,该方法生成集合的连续成员.
而已.
Ruby中不存在协议和类型.它们确实存在于Ruby文档,Ruby社区,Ruby程序员的头脑中以及Ruby代码中的隐式假设中,但它们从未在代码中体现出来.
所以,谈论Ruby类的LSP是没有意义的(因为类不是类型),但是根据Ruby类型谈论LSP也没什么意义(因为没有类型).您只能根据脑中的类型来讨论LSP(因为代码中没有任何类型).
好吧,咆哮.但是,这是真的,真的,真的,真的很重要.LSP是关于类型的.类不是类型.有些语言如C++,Java或C♯,其中所有类也是自动类型,但即使在这些语言中,将类型(规则和约束的规范)的概念与概念分开也很重要. class(它是对象的状态和行为的模板),如果只是因为除了那些类型的类之外还有其他东西(例如Java和C中的接口以及Java中的原语).事实上,interfaceJava是protocolObjective-C 的直接端口,后者又来自Smalltalk社区.
唷.所以,不幸的是,没有一个能回答你的问题:-D
LSP究竟是什么意思?LSP谈论子类型.更准确地说,它定义了一个(在它被发明的时候)新的基于行为可替代性的子类型概念.很简单,LSP说:
我可以用类型为S <:T的对象替换类型为T的对象,而无需更改程序的所需属性.
例如,"程序没有崩溃"是一个理想的属性,所以我不应该通过用超类型的对象替换超类型的对象来使程序崩溃.或者您也可以从另一个方向查看它:如果我可以通过用类型S的对象替换类型T的对象来违反程序的期望属性(例如,使程序崩溃),那么S不是T的子类型.
我们可以遵循一些规则来确保我们不违反LSP:
这两个规则只是函数的标准子类型规则,它们早在Liskov之前就知道了.
这三个规则是限制方法签名的静态规则.Liskov的关键创新是四个行为规则,特别是第四个规则("历史规则"):
前Liskov之前已经知道了前三个规则,但它们是以证明理论的方式制定的,没有考虑到混叠.规则的行为表述以及历史规则的添加使得LSP适用于现代OO语言.
这是查看LSP的另一种方式:如果我有一个只知道和关心的检查员T,并且我给他一个类型的对象S,他是否能够发现它是"伪造的"或者我能欺骗他吗?
好的,最后你的问题:添加sneer_majesticly方法是否违反了LSP?答案是:否.添加新方法可能违反LSP 的唯一方法是,如果这种新方法以仅使用旧方法不可能发生的方式操纵旧状态.由于不操纵任何状态,添加它不可能违反LSP.记住:我们的检查员只知道,即他只知道和.他不知道或不关心.sneer_majesticlyAnimalwalkrunsneer_majesticly
如果,OTOH,你正在添加一个方法,bite_off_foot之后猫不再行走,那么你违反LSP,因为通过调用bite_off_foot,检查员只能使用他所知道的方法(walk和run)观察一个无法观察的情况用动物:动物总能走路,但我们的猫突然不能!
但是!理论上run 可以违反LSP.请记住:子类型的对象不能更改超类型的理想属性.现在,问题是:什么是理想的属性Animal?问题是你没有提供任何文档Animal,所以我们不知道它的理想属性是什么.我们唯一可以看到的是代码,它总是raisesa NotImplementedError(BTW实际上是raise一个NameError,因为NotImplementedError在Ruby核心库中没有命名常量).所以,问题是:是否raise需要属性的异常部分?没有文档,我们无法分辨.
如果Animal定义如下:
class Animal
# …
# Makes the animal run.
#
# @return [void]
# @raise [NotImplementedError] if the animal can't run
def run
raise NotImplementedError
end
end
Run Code Online (Sandbox Code Playgroud)
然后它不会是LSP违规.
但是,如果Animal定义如下:
class Animal
# …
# Animals can't run.
#
# @return [never]
# @raise [NotImplementedError] because animals never run
def run
raise NotImplementedError
end
end
Run Code Online (Sandbox Code Playgroud)
那么这将是一个LSP违规.
换句话说:如果规范run是"总是引发异常",那么我们的检查员可以通过调用run并观察它没有引发异常来发现猫.但是,如果规范run是"使动物运行或者引发异常",那么我们的检查员就无法将猫与动物区分开来.
您将注意到,Cat在此示例中是否违反LSP实际上完全独立于Cat!它实际上也完全独立于内部代码Animal!这仅取决于上的文档.这是因为我在一开始就试图弄清楚:LSP是关于类型的.Ruby没有类型,因此类型只存在于程序员的头脑中.或者在此示例中:在文档注释中.