高效GO语言编程(10)——内嵌

欢迎加入GolangRoadmap,一个年轻的GO开发者社区https://www.golangroadmap.com/,目前是邀请制注册,注册码:Gopher-1035-0722,已开放GO内推,GO面试,GO宝典,GO返利等栏目

Go并不提供典型的,类型驱动的子类化概念,但通过将类型内嵌到结构体或接口中, 它就能“借鉴”部分实现。接口内嵌非常简单。我们之前提到过 io.Reader 和 io.Writer 接口,这里是它们的定义。

type Reader interface {                       //定义读取的接口类型
    Read(p []byte) (n int, err error)       //定义函数   传入[]byte类型  返回一个整型和err
}

type Writer interface {                         //定义写入的接口类型
    Write(p []byte) (n int, err error)       //定义函数   传入[]byte类型  返回一个整型和err
}

io 包也导出了一些其它接口,以此来阐明对象所需实现的方法。 例如 io.ReadWriter 就是个包含 Read 和 Write 的接口。我们可以通过显示地列出这两个方法来指明 io.ReadWriter, 但通过将这两个接口内嵌到新的接口中显然更容易且更具启发性,就像这样:

// ReadWriter 接口结合了 Reader 接口 和 Writer 接口
type ReadWriter interface {
    Reader
    Writer
}

正如它看起来那样:ReadWriter 能够做任何 Reader 和 Writer 可以做到的事情,它是内嵌接口的联合体 (它们必须是不相交的方法集)。只有接口能被嵌入到接口中。
同样的基本想法可以应用在结构体中,但其意义更加深远。bufio 包中有 bufio.Reader 和 bufio.Writer 这两个结构体类型, 它们每一个都实现了与 io 包中相同意义的接口。此外,bufio 还通过结合 reader/writer 并将其内嵌到结构体中,实现了带缓冲的 reader/writer:它列出了结构体中的类型,但并未给予它们字段名。

// 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.Reader 和 bufio.Writer 的方法,它还同时满足下列三个接口: io.Reader、io.Writer 以及 io.ReadWriter。

还有种区分内嵌与子类的重要手段。当内嵌一个类型时,该类型的方法会成为外部类型的方法, 但当它们被调用时,该方法的接收者是内部类型,而非外部的。在我们的例子中,当 bufio.ReadWriter 的 Read 方法被调用时, 它与之前写的转发方法具有同样的效果;接收者是 ReadWriter 的 reader 字段,而非 ReadWriter 本身。

内嵌同样可以提供便利。这个例子展示了一个内嵌字段和一个常规的命名字段。

type Job struct {
    Command string
    *log.Logger
}

Job 类型现在有了 Log、Logf 和 *log.Logger 的其它方法。我们当然可以为 Logger 提供一个字段名,但完全不必这么做。现在,一旦初始化后,我们就能记录 Job 了:

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

Logger 是 Job 结构体的常规字段, 因此我们可在 Job 的构造函数中,通过一般的方式来初始化它,就像这样:

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

或通过复合字面:

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

若我们需要直接引用内嵌字段,可以忽略包限定名,直接将该字段的类型名作为字段名, 就像我们在 ReaderWriter 结构体的 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 的字段或方法,Job 的 Command 字段会覆盖它。

其次,若相同的嵌套层级上出现同名冲突,通常会产生一个错误。若 Job 结构体中包含名为 Logger 的字段或方法,再将 log.Logger 内嵌到其中的话就会产生错误。然而,若重名永远不会在该类型定义之外的程序中使用,那就不会出错。 这种限定能够在外部嵌套类型发生修改时提供某种保护。 因此,就算添加的字段与另一个子类型中的字段相冲突,只要这两个相同的字段永远不会被使用就没问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值