内嵌

未匹配的标注

Go并不提供典型的,类型驱动的子类化概念,但通过将类型内嵌到结构体或接口中, 它就能“借鉴”部分实现。

接口内嵌非常简单。我们之前提到过 io.Readerio.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 就是个包含 ReadWrite 的接口。我们可以通过显示地列出这两个方法来指明 io.ReadWriter, 但通过将这两个接口内嵌到新的接口中显然更容易且更具启发性,就像这样:

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

正如它看起来那样:ReadWriter 能够做任何 Reader Writer 可以做到的事情,它是内嵌接口的联合体 (它们必须是不相交的方法集)。只有接口能被嵌入到接口中。

同样的基本想法可以应用在结构体中,但其意义更加深远。bufio 包中有 bufio.Readerbufio.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.Readerbufio.Writer 的方法,它还同时满足下列三个接口: io.Readerio.Writer 以及 io.ReadWriter

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

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

type Job struct {
    Command string
    *log.Logger
}

Job 类型现在有了 LogLogf*log.Logger 的其它方法。我们当然可以为 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)}

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

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

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/effective-go/20...

译文地址:https://learnku.com/docs/effective-go/20...

上一篇 下一篇
贡献者:8
讨论数量: 0
发起讨论 只看当前版本


暂无话题~