当前位置: 首页 > article >正文

go-基础之嵌入

嵌入

Go 语言没有提供典型的、基于类型驱动的子类化概念,但它能够通过在结构体或接口中嵌入类型来“借用”部分实现。

接口嵌入非常简单。我们之前提到过 io.Readerio.Writer 接口,下面是它们的定义:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

io 包还导出了其他几个接口,这些接口定义的对象可以实现多个这样的方法。例如,有 io.ReadWriter 接口,它包含 ReadWrite 方法。我们可以通过显式列出这两个方法来定义 io.ReadWriter,但更简单且更具表现力的方式是嵌入这两个接口来形成新的接口,如下所示:

// ReadWriter 是一个组合了 Reader 和 Writer 接口的接口。
type ReadWriter interface {
    Reader
    Writer
}

这正如它看起来的那样:ReadWriter 既可以执行 Reader 的操作,也可以执行 Writer 的操作;它是所嵌入接口的集合。只有接口才能嵌入到接口中。

同样的基本概念也适用于结构体,但会有更深远的影响。bufio 包有两个结构体类型,bufio.Readerbufio.Writer,当然,它们各自实现了 io 包中对应的接口。bufio 包还实现了一个带缓冲的读写器,它通过嵌入将一个读取器和一个写入器组合到一个结构体中来实现这一点:在结构体中列出类型但不指定字段名。

// ReadWriter 存储了指向 Reader 和 Writer 的指针。
// 它实现了 io.ReadWriter 接口。
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}

嵌入的元素是指向结构体的指针,当然,在使用它们之前必须将其初始化为指向有效的结构体。ReadWriter 结构体也可以写成这样:

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

但这样的话,为了提升字段的方法并满足 io 接口,我们还需要提供转发方法,如下所示:

func (rw *ReadWriter) Read(p []byte) (n int, err error) {
    return rw.reader.Read(p)
}

通过直接嵌入结构体,我们避免了这种额外的处理。嵌入类型的方法会自动成为外部类型的方法,这意味着 bufio.ReadWriter 不仅拥有 bufio.Readerbufio.Writer 的方法,还满足所有三个接口:io.Readerio.Writerio.ReadWriter

嵌入与子类化有一个重要的区别。当我们嵌入一个类型时,该类型的方法会成为外部类型的方法,但在调用这些方法时,方法的接收者是内部类型,而不是外部类型。在我们的示例中,当调用 bufio.ReadWriterRead 方法时,其效果与上面写出的转发方法完全相同;接收者是 ReadWriterreader 字段,而不是 ReadWriter 本身。

嵌入也可以是一种简单的便利方式。下面这个示例展示了一个嵌入字段和一个常规的命名字段。

type Job struct {
    Command string
    *log.Logger
}

Job 类型现在拥有 *log.LoggerPrintPrintfPrintln 等方法。当然,我们也可以给 Logger 一个字段名,但这不是必需的。现在,一旦初始化完成,我们就可以对 Job 进行日志记录:

job.Println("starting now...")

LoggerJob 结构体的一个常规字段,所以我们可以在 Job 的构造函数中以通常的方式对其进行初始化,如下所示:

func NewJob(command string, logger *log.Logger) *Job {
    return &Job{command, logger}
}

或者使用复合字面量:

job := &Job{command, log.New(os.Stderr, "Job: ", log.Ldate)}

如果我们需要直接引用嵌入字段,忽略包限定符的字段类型名可以作为字段名,就像在 ReadWriter 结构体的 Read 方法中那样。在这里,如果我们需要访问 Job 变量 job*log.Logger,我们可以写成 job.Logger,如果我们想改进 Logger 的方法,这会很有用。

func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

嵌入类型会引入名称冲突的问题,但解决这些问题的规则很简单。首先,字段或方法 X 会隐藏类型中更深嵌套部分的任何其他 X 项。如果 log.Logger 包含一个名为 Command 的字段或方法,JobCommand 字段会覆盖它。

其次,如果相同的名称出现在同一嵌套级别,通常这是一个错误;如果 Job 结构体包含另一个名为 Logger 的字段或方法,嵌入 log.Logger 就是错误的。然而,如果重复的名称在类型定义之外的程序中从未被提及,那么这是可以的。这种限定为从外部嵌入的类型的更改提供了一定的保护;如果添加了一个与另一个子类型中的字段冲突的字段,但两个字段都从未被使用,那么就没有问题。


http://www.kler.cn/a/523766.html

相关文章:

  • 2501,20个窗口常用操作
  • 实验七 带函数查询和综合查询(2)
  • 2025美赛数学建模MCM/ICM选题建议与分析,思路+模型+代码
  • 【go语言】结构体
  • 登录授权流程
  • Vue 3 30天精进之旅:Day 07 - Vue Router
  • 10JavaWeb——SpringBootWeb案例01
  • 计算机网络__基础知识问答
  • 低代码岗位就业前景分析
  • STM32 对射式红外传感器配置
  • Excel - Binary和Text两种Compare方法
  • 高效学习方法分享
  • 9.8 实战:使用 GPT Builder 开发定制化 ChatGPT 应用
  • 使用 Go 和 gqlgen 实现 GraphQL API:实战指南
  • NodeJs / Bun 分析文件编码 并将 各种编码格式 转为 另一个编码格式 ( 比如: GB2312→UTF-8, UTF-8→GB2312)
  • 【论文推荐|深度学习,滑坡检测,多光谱影像,自然灾害,遥感】2022年Landslide4Sense竞赛成果:基于多源卫星影像的先进滑坡检测算法研究(五)
  • 【某大厂一面】数组和链表区别
  • MATLAB绘图:动态波浪图
  • lwIP——4 网络接口
  • [MySQL]事务的隔离级别原理与底层实现
  • 2.策略模式(Strategy)
  • 如何使用Git进行版本控制?
  • 单细胞分析基础-第一节 数据质控、降维聚类
  • NLP自然语言处理通识
  • 前端25.1.26学习记录
  • IDM-VTON本地部署教程:双重编码 + 文字提示,解锁真实野外试穿