当遵守里氏替换原则(LSP)时,子类可以实现额外的接口吗?

2024-03-01

考虑这个红宝石示例

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 上未实现也不需要?


里氏替换原则与类别无关。这是关于types。 Ruby 没有类型作为一种语言特征,所以从语言特性的角度来谈论它们并没有什么意义。

在 Ruby(以及一般的 OO)中,类型基本上是协议。协议描述对象响应哪些消息以及如何响应这些消息。例如,Ruby 中的一个著名协议是迭代协议,它由一条消息组成each它需要一个块,但没有位置或关键字参数并且yields 元素按顺序添加到块中。请注意,没有与此协议对应的类或 mixin。符合此协议的对象无法如此声明。

有一个 mixindepends在此协议上,即Enumerable。同样,由于没有与“协议”概念相对应的 Ruby 构造,因此无法Enumerable来声明这个依赖关系。仅在介绍性段落中提到文档 http://ruby-doc.org/core/Enumerable.html (bold强调我的):

The Enumerablemixin 为集合类提供了多种遍历和搜索方法,并且具有排序的能力。该类必须提供一个方法each,它产生集合的连续成员。

就是这样。

Ruby 中不存在协议和类型。他们do存在于 Ruby 文档中、Ruby 社区中、Ruby 程序员的头脑中以及 Ruby 代码中的隐含假设中,但它们从未在代码中显现出来。

因此,用 Ruby 类来谈论 LSP 没有任何意义(因为类不是类型),但用 Ruby 类型来谈论 LSP 也没有任何意义(因为没有类型)。你只能用你头脑中的类型来谈论LSP(因为你的代码中没有任何类型)。

好了,吐槽完毕。但这确实是,really, really, REALLY重要的。 LSP 与类型有关。类不是类型。在 C++、Java 或 C♯ 等语言中,所有类也自动成为类型,但即使在这些语言中,将类型的概念(规则和约束的规范)与对象的概念分开也很重要。类(它是对象状态和行为的模板),如果只是因为有other除了类之外,这些语言中的类型也是如此(例如 Java 和 C♯ 中的接口以及 Java 中的原语)。事实上,interface在Java中是直接移植protocol来自 Objective-C https://cs.gmu.edu/~sean/stuff/java-objc.html,这又来自 Smalltalk 社区。

唷。所以,不幸的是,这些都不能回答你的问题:-D

LSP 到底是什么意思? LSP 讨论了子类型。更准确地说,它定义了(在发明时)新的子类型概念,该概念基于行为可替代性。很简单,LSP 说:

我可以替换类型的对象T与类型的对象S <: t>而不改变程序所需的属性。

例如,“程序不会崩溃”是一个理想的属性,因此我不应该通过用子类型的对象替换超类型的对象来使程序崩溃。或者您也可以从另一个方向查看它:如果我可以通过替换类型的对象来违反程序的理想属性(例如使程序崩溃)T与类型的对象S, then S不是 的子类型T.

我们可以遵循一些规则来确保我们不违反 LSP:

  • 方法参数类型是逆变的,即,如果重写方法,子类型中的重写方法必须接受与重写方法相同类型或更通用类型的参数。
  • 方法返回类型是协变的,即子类型中的重写方法必须返回与被重写方法相同的类型或更具体的类型。

这两条规则只是函数的标准子类型规则,它们早在 Liskov 之前就已为人所知。

  • 子类型中的方法不得引发任何不仅由超类型中的重写方法引发的新异常,但其类型本身是重写方法引发的异常的子类型的异常除外。

这三个规则是限制方法签名的静态规则。里斯科夫的关键创新是四个行为规则,特别是第四个规则(“历史规则”):

  • 子类型中的先决条件无法得到加强,即,如果用子类型替换对象,则不能对调用者施加额外的限制,因为调用者不知道它们。
  • 子类型中的后置条件不能被削弱,即您不能放松超类型所做的保证,因为调用者可能依赖它们。
  • 必须保留不变量,即如果超类型保证某些内容始终为真,那么它在子类型中也必须始终为真。
  • 历史规则:操作子类型的对象不得创建无法从超类型的对象观察到的历史记录。 (这个有点棘手,它的意思如下:如果我观察到一个类型的对象S只能通过类型方法T,我不应该能够将对象置于一种状态,使得观察者看到的状态是该类型的对象不可能看到的状态T,即使我使用的方法S来操纵它。)

前三个规则在里斯科夫之前就已为人所知,但它们是以证明理论的方式制定的,无需考虑aliasing考虑到。规则的行为表述以及历史规则的添加使得LSP适用于现代OO语言。

这是看待 LSP 的另一种方式:如果我有一个只知道并关心的检查员T,然后我递给他一个类型的对象S,他能看出这是“假货”吗?或者我能骗他吗?

好的,最后回答你的问题:添加sneer_majesticly方法违反了LSP?答案是:不。唯一的方法是添加一个new方法可以违反 LSP 是如果这样new方法操纵old以这样的方式陈述仅使用不可能发生old方法。自从sneer_majesticly不操纵任何状态,添加它不可能违反 LSP。请记住:我们的检查员只知道Animal,即他只知道walk and run。他不知道也不关心sneer_majesticly.

如果,OTOH,您要添加一个方法bite_off_foot之后猫就不能再走路了,then你违反了 LSP,因为通过调用bite_off_foot,检查员可以仅使用他所知道的方法(walk and run)观察一个用动物无法观察到的情况:动物总是可以走路,但我们的猫突然不能了!

However! run could理论上违反LSP。请记住:子类型的对象无法更改超类型的所需属性。现在的问题是:什么are的理想特性Animal?问题是你没有提供任何文档Animal,所以我们不知道它的理想特性是什么。我们唯一能看的就是代码,它总是raises a NotImplementedError(顺便说一句,实际上raise a NameError,因为没有名为常量NotImplementedError在 Ruby 核心库中)。所以,问题是:是raise是否具有所需属性的异常部分?没有文档,我们无法判断。

If Animal定义如下:

class Animal
  # …

  # Makes the animal run.
  #
  # @return [void]
  # @raise [NotImplementedError] if the animal can't run
  def run
    raise NotImplementedError
  end
end

那么就会not违反 LSP。

然而,如果Animal定义如下:

class Animal
  # …

  # Animals can't run.
  #
  # @return [never]
  # @raise [NotImplementedError] because animals never run
  def run
    raise NotImplementedError
  end
end

Then it would违反 LSP。

换句话说:如果规范run是“总是引发异常”,那么我们的检查员可以通过调用来发现一只猫run并观察它不会引发异常。但是,如果规范为run是“让动物跑,否则引发异常”,那么我们的检查员可以not区分猫和动物。

您会注意到,无论是否Cat在这个例子中违反LSP实际上是完全独立的Cat!而且它其实也是完全独立于里面的代码的Animal! It only取决于文档。这是因为我一开始就试图阐明这一点:LSP 是关于types。 Ruby 没有类型,因此类型只存在于程序员的头脑中。或者在这个例子中:在文档注释中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当遵守里氏替换原则(LSP)时,子类可以实现额外的接口吗? 的相关文章

随机推荐

  • SyncStateNotFound 错误:如何修复或避免?

    我使用 Microsoft Graph API 增量查询定期下载一些信息 消息 联系人 事件 但有时我会收到此错误 error code SyncStateNotFound innerError date 2018 06 01T06 31
  • 如何在 Watin 中通过标签名称查找元素?

    如何使用 Watin 使用 TagName 来查找特定元素或元素列表 从 WatiN 2 0 beta 1 开始 这已更改为 ie ElementWithTag h1 constraint 使用 Find XXX 方法时会创建约束 这是一个
  • Facebook ShareDialog 在完成时始终返回 .canceled

    共享对话框打开 Facebook 应用程序 尽管内容已成功共享 但我总是收到 取消打回来 在这两种情况下 当我取消共享时和共享成功时 知道出了什么问题吗 Pod 版本 Using Bolts 1 8 4 Using FBSDKCoreKit
  • 矩形内最大的空矩形

    我的数学不太好 所以我很难将公式转换为代码 而且我在谷歌上找不到任何现成的东西 我有一个包含很多小矩形的大矩形 我需要做的就是计算最大的空矩形 任何人都可以帮助我吗 这就是我想出的 没什么可说的 这是一个很大的失败 Rect result
  • 使用 MPI 和 C++ 从不同节点收集数据

    我正在开发一个包含多个从节点和一个主节点的项目 在某些时候 我需要将来自不同从节点 主节点也可以视为从节点 的数据收集到主节点 数据可以是任何类型 但我们假设它是 unsigned int 这就是数据在从节点上的样子 节点0 块01 块02
  • 在 [code] 和 [php] 标签内查找 URL [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我的字符串是这样的 string Link 1 code This is a textual line www google com
  • Jackson:忽略父类属性

    有什么方法可以告诉杰克逊在序列化子类时忽略父类的属性吗 class Parent private String parentProperty1 private String parentProperty2 getter setter Ign
  • 如何使用 MVVM 和 MVVM 工具包将属性绑定到文本框?

    我是 MVVM 新手 为了学习 我创建了一个示例应用程序 以便在单击按钮时在文本框中显示消息 在我的代码中 按钮命令工作正常 但该属性未绑定到文本框 如何使用 MVVM 将属性绑定到文本框 我的代码类似于下面给出的 View
  • ASP.NET vNext - 本地化 (.resx)

    如何使用 resx 文件通过 ASP NET vNext 云优化模式 进行本地化以及如何为当前线程设置区域性 System Threading Thread CurrentThread CurrentCulture System Threa
  • 为什么这个函数将RAX压入堆栈作为第一个操作?

    在下面的 C 源代码汇编中 为什么RAX被压入堆栈 据我从 ABI 的了解 RAX 可以包含调用函数中的任何内容 但我们将其保存在这里 然后将堆栈向后移动 8 个字节 所以我认为堆栈上的 RAX 只与std throw bad functi
  • 如何解决“允许的内存大小已耗尽”错误?

    我正在使用 phpspreadsheet 我想修改一个包含 4 张纸的 xlsx 文件 我只想在 2 张表中插入数据 但我想将所有 4 张表复制到新的 xlsx 文件中 当我这样做时 我收到此错误 致命错误 允许的内存大小 53687091
  • 是否有高级 Ag-Grid 事件来侦听列状态的任何更改?

    我将 Ag Grid 与 Angular 一起使用 我想听听any修改列状态的事件 截至目前 我必须列出所有事件 columnVisible onCol event columnMoved onCol event etc 是否有我可以依赖的
  • 在 python 中查询返回本地范围的对象

    下面是返回的程序function函数中定义的类型对象f其堆栈帧 f1 在程序退出之前仍然存在 下面是返回的程序int类型对象 值为1024 但返回后栈帧不存在int类型对象 根据上面两个图 为什么返回类型机制存在这种差异 当您返回时 框架不
  • 如何使用 XSLT 合并两个 xml 文件

    我有两个 xml 文件 需要使用 XSLT 将它们合并为一个 第一个 XML 是 原始 XML
  • 如何计算php中2个unix时间戳之间的间隔而不除以86400(60 * 60 * 24)

    我有 2 个 unix 时间戳 我位于亚太地区 奥克兰时区 GMT 12 夏令时 GMT 13 我想计算两个时间戳之间的天数间隔 其中一个在夏令时内 一个不在夏令时内 我的示例日期是 7 Feb 2009 1233925200 to 21
  • 什么是 Git?我为什么需要它?

    我对编程还很陌生 但我觉得我已经掌握了它的窍门 并且我正在尽力学习 我一直读到 Git 对于编程项目来说绝对至关重要 但我似乎无法弄清楚它实际上是什么does 谷歌也不想告诉我 该网站称其为 分布式版本控制系统 呃 嗯 有人可以向新手解释一
  • 后台工作者 while 循环

    我想要创建的是一个后台工作者 每 30 秒执行几个进程 但我希望只要程序启动就执行这个 while 循环 这是我正在使用的 private void watcherprocess1 backgroundWorker1 RunWorkerAs
  • python 中的 crontab

    我正在用 python 为某种守护进程编写代码 该守护进程必须在由 crontab 字符串定义的特定时间实例执行特定操作 有我可以使用的模块吗 如果没有 有人可以粘贴 链接一个算法 我可以用它来检查 crontab 定义的时间实例是否在上次
  • 相对于 DBUnit 数据集中当前的日期

    我想知道是否有任何方法可以指定例如明天作为 DBUnit XML 数据集中的日期 有时 未来日期和过去日期的代码逻辑是不同的 我想测试这两种情况 当然 我可以指定 2239 年 11 月 5 日之类的日期 并确保测试在此日期之前有效 但是否
  • 当遵守里氏替换原则(LSP)时,子类可以实现额外的接口吗?

    考虑这个红宝石示例 class Animal def walk In our universe all animals walk even whales puts walking end def run Implementing to co