实现具有更广泛方法签名的接口

2024-05-22

在Go中,是否有一种方法可以使用方法来实现接口,其中实现中相应方法的返回类型“比”预期返回类型“更宽”?

这很难解释,所以这里有一个例子。在 Go Playground 中运行以下示例代码时出现此错误:

./prog.go:36:14: cannot use BirdImpl{} (type BirdImpl) as type Animal in argument to foo:
    BirdImpl does not implement Animal (wrong type for Move method)
        have Move() BirdMoveResult
        want Move() AnimalMoveResult

(where BirdMoveResult是“比”更宽AnimalMoveResult因为任何实施BirdMoveResult也是一个实现AnimalMoveResult)

package main

type (
    Animal interface {
        Move() AnimalMoveResult
    }
    Bird interface {
        Move() BirdMoveResult
    }
    AnimalMoveResult interface {
        GetDistance() int
    }
    BirdMoveResult interface {
        GetDistance() int
        GetHeight() int
    }
    BirdImpl struct{}
    BirdMoveResultImpl struct{}
)

// Some implementation of BirdImpl.Move
// Some implementation of BirdMoveResultImpl.GetDistance
// Some implementation of BirdMoveResultImpl.GetHeight

func foo(animal Animal) int {
    return animal.Move().GetDistance()
}

func main() {
    foo(BirdImpl{})  // This fails because BirdImpl doesn't implement Animal. My question is why not?
}

据我了解,Move()由于返回类型不同,方法签名不完全匹配,因此 Go 不考虑BirdImpl作为一个实施Animal。但是,如果 Go 比较返回类型,则任何实现BirdMoveResult还将实施AnimalMoveResult。所以,不应该Move() BirdMoveResult是一个可接受的实现Move() AnimalMoveResult(如果没有,为什么不呢)?

编辑:在实际场景中,Animal, AnimalMoveResult, and foo是外部包的一部分。在我自己的代码中,我希望能够扩展AnimalMoveResult使用我自己的接口方法(正如示例中所做的那样)BirdMoveResult),同时仍然能够利用foo使用扩展接口。


此类问题通常是由以下原因造成的:

  • 过早的接口
  • 假接口,或“气泡包装”(参见我的咆哮here https://stackoverflow.com/a/67133656/11424673)
  • 并行接口

您遇到的错误与第三个问题最相关,但每个错误都存在于此处的示例设计中。

好的接口应该(以它们的名称和方法签名)传达一组行为。接口的名称应该由它包含的方法隐含,因为名称只是应该捕获该行为的本质。

为此目的似乎需要的唯一接口是:

type Mover interface {
    Move() MoveResult
}

对于 MoveResult,您可以这样做(如果您愿意,可以将 float 替换为 int):

type MoveResult struct {
    Distance, Height float64
}

考虑到以下假设,这种类型可以作为结构体正常工作:

  • 移动结果只是数据点(它们不需要是动态的;您可以设置值并保留它们)
  • 当未指定时,“额外”数据(例如高度)可以正常使用零值。

现在,实现鸟类型:

type Bird struct {
    // some internal data
}

func (b *Bird) Move() MoveResult {
    // some movement calculations
    return MoveResult{Distance: howFar, Height: howHigh}
}

现在实现 foo:

func foo(m Mover) float64 {
    return m.Move().Distance
}

现在在 main 中使用它:

func main() {
    foo(&Bird{})
}

现在我们可以添加其他种类Movers 到程序并使用它们foo没有问题。


针对您的评论,在哪里Animal, AnimalMoveResult and foo都是外部的,你不能修改它们:

我如何用我的理解提出问题:有这个功能foo,旨在接受一个名为Animal,其特征在于它如何返回专有类型。你想使用你的类型Bird as an Animal,但是您无法将所需的所有行为都放入该专有返回类型中。

那么如何解决这个问题呢?

实现您自己的、更广泛的返回类型

type birdMoveResult struct {
    // some internal data
}

func (r birdMoveResult) GetDistance() int {
    // get the distance
}

func (r birdMoveResult) GetHeight() int {
    // get the height
}

现在,实现 Bird 类型

type Bird struct {
    // some internal data
}

func (b *Bird) Move() AnimalMoveResult {
    var result birdMoveResult
    // calculate the move result
    return birdMoveResult
}

请注意Move返回AnimalMoveResult类型,所以现在Bird将满足Animal界面。我们能够做到这一点是因为birdMoveResult满足AnimalMoveresult类型,所以在这里返回它是合法的。

剩下的问题是Bird's Move方法缩小了宽接口,我们实际上已经失去了您添加的新功能。为了在仍然满足的情况下取回它Animal接口,我们无法更改Move方法签名根本不存在。这里有 2 种可能的解决方法。

  1. 当使用你的鸟类型时,你可以类型断言 https://tour.golang.org/methods/15结果访问额外的方法
var b Bird
// ...
height := b.Move().(birdMoveResult).GetHeight()
  1. 添加一个新方法Bird返回更宽的类型,保留现有的Move具有相同的签名
func (b *Bird) MoveFull() birdMoveResult {
    var result birdMoveResult
    // calculate the move result
    return birdMoveResult
}

func (b *Bird) Move() AnimalMoveResult {
    return b.MoveFull()
}

height := b.MoveFull().GetHeight()

其中任何一个都可以,这主要是一个偏好问题。

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

实现具有更广泛方法签名的接口 的相关文章

  • 在 OSX 上交叉编译 Go?

    我正在尝试在 OSX 上交叉编译 go 应用程序以构建适用于 Windows 和 Linux 的二进制文件 我已经阅读了网上能找到的所有内容 我发现的最接近的例子已经发布在 除了疯狂邮件列表上许多未完成的讨论之外 http solovyov
  • golang中默认的HTTP拨号超时值

    我正在运行 golang http 客户端来对服务器进行压力测试 有时我会收到错误 拨号 tcp 161 170 xx xxx 80 操作超时 错误 我认为这是 HTTP 客户端超时 我正在考虑增加超时值https stackoverflo
  • go build 不断抱怨:go.mod 有 post-v0 模块路径

    Go 1 11 发布后 我一直在尝试将我的存储库移动到 Go 模块 方法是添加go mod文件在其根目录下 我的根库之一my host root其版本为17 0 1 所以我在其中写道go mod file module my host ro
  • 带有导出字段的私有类型

    在 Go 教程的第二天有这样的练习 为什么拥有带有导出字段的私有类型会很有用 例如 package geometry type point struct X Y int name string 请注意point是小写的 因此不会导出 而字段
  • 修改现有的yaml文件并添加新的数据和注释

    我最近看到了yaml https github com go yaml yaml tree v3lib 有新版本 V3 与nodes https github com go yaml yaml blob v3 yaml go L348功能
  • Golang:使用像 Node.js 中那样的可读流从 PostgreSQL 数据库中选择几百万行

    我有大约 5000 万行的 PostgreSQL 表 我想编写 Go 代码来从该表中选择大约 100 万行 并以有效的方式处理它们 上次我使用了nodejs和这个NPM模块pg 查询流 https www npmjs com package
  • GO中的优先级队列

    谁能向我解释一下 我想在GO中实现一个优先级队列 接口实现来自link https golang org pkg container heap example priorityQueue 但优先级最低 我的代码 pq make Priori
  • 是否可以在 Golang 中 pickle 结构实例

    我正在 Golang 中做一些机器学习 我现在碰壁了 我训练有素的分类器需要将近半分钟的时间来训练 并且想要保存分类器的该实例 这样我就不必每次都从头开始训练 在 Golang 中应该如何去做呢 仅供参考 我的分类器是一个结构 当我用 py
  • 在 Go 中解析 RFC-3339 / ISO-8601 日期时间字符串

    我尝试解析日期字符串 2014 09 12T11 45 26 371Z 在围棋中 该时间格式定义为 RFC 3339 日期时间 https datatracker ietf org doc html rfc3339 section 5 6
  • 将 []string 传递给需要可变参数的函数

    为了不一遍又一遍地重复我的自我 我想创建一个处理运行一些命令的函数 func runCommand name string arg string error cmd exec Command name arg if err cmd Run
  • 如何自定义解析错误的 HTTP 400 响应?

    我编写了一个 REST API 服务 要求所有响应均为 JSON 但是 当 Go HTTP 请求解析器遇到错误时 它会返回 400 作为纯文本响应 而不会调用我的处理程序 例子 gt curl i H Authorization Basic
  • vscode 中的调试不会在断点处停止,调试器启动时显示“无法找到文件...”

    乌班图 vscode 1 62 1 去1 17 3 vscode go 扩展 v0 29 0 深入研究 v1 7 1 我是 vscode 和 Go 的新手 我有多年在 Eclipse 中调试 Java 应用程序的经验 我构建了一个小型多模块
  • 如何在 Visual Studio Code 中为 Golang 启用竞争检测器?

    我搜索了很多网页来找到我应该放入哪个短语settings json在 VS Code Golang 扩展 由 Microsoft 发布 中添加构建标志 在我的例子中是竞赛检测器 I added go buildFlags race 在扩展名
  • GO并发编程测试

    我试图确保我的并发程序不存在以下情况 僵局 livelock 饥饿 我找到了以下工具http blog golang org race detector http blog golang org race detector 我尝试编译并运行
  • 使用 crypto/ssh 的 golang scp 文件

    我正在尝试通过 ssh 下载远程文件 以下方法在 shell 上运行良好 ssh hostname tar cz opt local folder gt folder tar gz 然而 golang 上的相同方法在输出工件大小方面存在一些
  • 取消用户特定的 goroutine [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一个应用程序 网络应用程序 允许用户使用 twitter oauth 登录并提供自动推文删除功能 用户登录到 Web 应用程序后
  • 按引用或按值扫描功能

    我有以下代码 statement SELECT id from source where mgmt 1 var exists string errUnique dr db QueryRow statement mgmt Scan exist
  • 在函数中将通道作为参数传递的不同方法

    我正在阅读一些Go代码 并说了几种传递Go通道的不同方法 也许它们是相同的 但我想知道是否有任何区别 因为我无法在线找到文档 1 func serve ch lt chan interface do stuff 2 func serve c
  • Golang中如何删除字符串的最后一个字符?

    我想删除字符串的最后一个字符 但在此之前我想检查最后一个字符是否是 如何才能做到这一点 以下是删除尾随加号的几种方法 package main import fmt strings func TrimSuffix s suffix stri
  • 使用 OpenTelemetry 统一不同服务的范围

    我刚刚开始使用 OpenTelemetry 并为此创建了两个 微 服务 Standard and GeoMap 最终用户将请求发送到Standard服务 该服务又将请求发送到GeoMap在将结果返回给最终用户之前获取信息 我使用 gRPC

随机推荐

  • 不幸的是应用程序已在 Android 模拟器中停止

    我是 Android 新手 正在尝试一些小应用程序 例如 Compass 当我在模拟器中运行应用程序时 它会给出消息Unfortunately Compass has Stopped 我没有编译时错误 我该如何解决这个问题 是什么原因造成的
  • golang中通道缓冲容量0和1的区别

    我已将通道缓冲区大小设置为零 例如var intChannelZero make chan int 当从intChannelZero将被阻止 直到intChannelZero有价值 另外 我将通道缓冲区大小设置为 1 例如var intCh
  • Android smoothScrollTo 不调用 onScrollStateChanged

    我在用smoothScrollBy 滚动到 a 中的特定位置ListView 我希望在以下情况时得到通知ListView完成滚动以将其与当前集成onScrollStateChanged 当用户用手指滚动时触发的事件 目前我正在使用Timer
  • DOM 中不再存在缓存元素

    就像在类似的问题中一样 我使用appium java 尝试选择元素 在移动应用程序中 我要转到页面 之后有许多元素 android widget ImageView 0 我需要选择 6 个 例如 这样的元素并执行其他步骤 Byt 只能选择一
  • 运行pyspark时没有这样的文件或目录错误

    我安装了 Spark 但是当我运行时pyspark在终端上 我得到 usr local Cellar apache spark 2 4 5 1 libexec bin pyspark line 24 Users miguel spark 2
  • 如何恢复丢失的aws服务器实例的私钥?

    我丢失了 AWS 实例的私钥 我在控制台面板中搜索了该选项 恐怕你可能不走运 当您启动实例时 您应该指定密钥的名称 您计划用于连接到实例的对 如果你不指定 启动实例时现有密钥对的名称 您 将无法连接到实例 当您连接到 例如 您必须指定与密钥
  • Mongodb 以不区分大小写的方式排序

    我在 Nodejs express 中以 mongodb 作为数据库的一个项目中非常努力地构建 当我使用 sort 获取所有数据时 它以错误的方式返回数据 那么有没有办法按照我的预期得到正确的格式 如下所示 如果我们在数据库中有三个记录 i
  • <%: 和 <%= 与嵌入代码(表达式)块相同吗

    刚开始使用 MVC 2 我注意到他们在入门模板中使用 我确信在 MVC 1 中它是 它们是一样的吗 如果是这样 为什么从等号改为冒号 冒号语法意味着您将自动进行 html 编码 http haacked com archive 2009 0
  • 在keras自定义损失中使用层输出

    我正在 Keras 中开发自定义损失函数 我需要第一层输出 我怎样才能取回它 def custom loss y true y pred cross K mean K binary crossentropy y true y pred ax
  • jQuery Mobile 数据过滤器,以防没有结果

    我目前正在探索 jQuery Mobile 以开发带有订单跟踪信息的移动版仪表板 计划是使用一个包含所有订单的简单无序列表 人们可以单击他们想了解更多信息的链接 由于此列表可能会变得相当大 因此拥有过滤功能非常好 使用 jQuery Mob
  • startDrag 方法 已弃用且无法编译程序

    startDrag android content ClipData android view View DragShadowBuilder java lang Object int 已弃用 如何解决这个问题而又不失去对旧版本的兼容性 还有
  • 如何让 IPython 按类别组织制表符补全的可能性?

    当一个对象有数百个方法时 制表符补全很难使用 通常 有趣的方法是由被检查对象的类而不是其基类定义或重写的方法 如何让 IPython 对其制表符完成可能性进行分组 以便首先检查对象的类中定义的方法和属性 然后是基类中的方法和属性 看起来像是
  • Rails“where”方法通过子属性查找父级

    我有一个 Rails 应用程序 我试图根据子类的日期创建父类的列表 现在我有 orders Order where order reminders date lt 1 month from now 但我收到一个错误 没有这样的列 order
  • 恢复默认的CSS属性

    我正在编写一个可在多个网站上使用的组件 每个网站都有自己的样式表 并且以不同的方式显示某些内容 我的所有 html 都包含在一个带有 id 的 div 中 div div 然而 我的组件是在所有网站上看起来一致 这很好 因为我将样式应用于组
  • 什么是 Kubernetes 清单?

    我在网上搜索过 大多数链接似乎都提到了清单 但没有实际解释它们是什么 什么是清单 它基本上是 Kubernetes API 对象描述 配置文件可以包含其中的一个或多个 即 Deployment ConfigMap Secret Daemon
  • C++ Primer 5th Edition 错误 bool 值没有指定最小大小?

    bool 的最小大小不应该是 1 个字节吗 这有点学术性的东西 尽管它们会转换为数字 并且 与其他所有事物一样 它们最终将基本上由计算机内存中的数字表示 但布尔值不是数字 你的bool可以取值true 或值false 即使您确实需要至少 1
  • 检测浏览器是否支持 contentEditable?

    There s 这个问题 https stackoverflow com questions 3497942 browser detect contenteditable features 但发布的解决方案是浏览器嗅探 我试图避免这种情况
  • 为什么Python安装程序不断弹出?

    每当我尝试运行 Python 文件时 都会自动弹出此窗口 虽然 我可以关闭它 但有时它会连续打开 7 10 个窗口 这令人恼火 谁能告诉我为什么会发生这种情况 None
  • git-lfs 中的多个文件版本

    我正在尝试估计 GitHub 上我的项目的存储要求 并对 git lfs 如何存储多个版本的文件有一些疑问 git lfs 是否存储多个版本的文件 如果是这样 对文件的每次更改都会导致复制整个文件 还是仅存储差异 所有版本都会计入 gith
  • 实现具有更广泛方法签名的接口

    在Go中 是否有一种方法可以使用方法来实现接口 其中实现中相应方法的返回类型 比 预期返回类型 更宽 这很难解释 所以这里有一个例子 在 Go Playground 中运行以下示例代码时出现此错误 prog go 36 14 cannot