Skip to content

Latest commit

 

History

History
132 lines (89 loc) · 8.99 KB

13.Embedding.md

File metadata and controls

132 lines (89 loc) · 8.99 KB

Embedding

Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to "borrow" pieces of an implementation by embedding types within a struct or interface.

Go 并没有提供典型的,类型驱动的子类概念,但它确实能够通过在结构体或接口中嵌入类型来"借用"其实现的某些部分。

Interface embedding is very simple. We've mentioned the io.Reader and io.Writer interfaces before; here are their definitions.

内嵌接口非常简单。我们曾提到过接口 io.Readerio.Writer;它们的定义如下:

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

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

The io package also exports several other interfaces that specify objects that can implement several such methods. For instance, there is io.ReadWriter, an interface containing both Read and Write. We could specify io.ReadWriter by listing the two methods explicitly, but it's easier and more evocative to embed the two interfaces to form the new one, like this:

io 包还导出了一些其他接口,这些接口包含了数个类似的方法。例如 io.ReadWriter 接口同时包含了 ReadWrite。我们当然可以在 io.ReadWriter 中显示地列出这两个方法,但是通过嵌入两个接口来组成新接口的方式则更加便宜,而且顺理成章:

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}

This says just what it looks like: A ReadWriter can do what a Reader does and what a Writer does; it is a union of the embedded interfaces. Only interfaces can be embedded within interfaces.

如你所见:ReadWriter 同时拥有 ReaderWriter 的能力;它是这两个内嵌接口的组合。只有接口才可以内嵌接口。

The same basic idea applies to structs, but with more far-reaching implications. The bufio package has two struct types, bufio.Reader and bufio.Writer, each of which of course implements the analogous interfaces from package io. And bufio also implements a buffered reader/writer, which it does by combining a reader and a writer into one struct using embedding: it lists the types within the struct but does not give them field names.

以此类推,我们可以联想到结构体的内嵌。不同的是,结构体内嵌具有更为深远的影响。bufio 包有两个结构体,分别是 bufio.Readerbufio.Writer,当然,每个都实现了来自包 io 包中的相似接口。除此之外,bufio 还实现了带缓存的 reader/writer,这都仰赖使用内嵌来组合 reader 和 writer 结构体:在结构体定义中列出类型,但不给予名字。

// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}

The embedded elements are pointers to structs and of course must be initialized to point to valid structs before they can be used. The ReadWriter struct could be written as

被嵌入的元素都是指向结构体的指针,当然,其在使用前需要初始化,且指向有效的结构体。当然可以像下面这样定义 ReadWriter

type ReadWriter struct {
    reader *Reader
    writer *Writer
}

but then to promote the methods of the fields and to satisfy the io interfaces, we would also need to provide forwarding methods, like this:

然而,为了促进结构体字段的方法以满足 io 的接口,我们还需要提供转发方法,像这样:

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

By embedding the structs directly, we avoid this bookkeeping. The methods of embedded types come along for free, which means that bufio.ReadWriter not only has the methods of bufio.Reader and bufio.Writer, it also satisfies all three interfaces: io.Reader, io.Writer, and io.ReadWriter.

直接使用内嵌结构体,我们可以避免这种流水账。被嵌入类型的方法会随着内嵌而自动被 bufio.ReadWriter 获得,这意味着 bufio.ReadWriter 不仅仅是拥有了 bufio.Readerbufio.Writer 的方法,同时它还满足所有这三个接口:io.Reader, io.Writer, and io.ReadWriter

There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one. In our example, when the Read method of a bufio.ReadWriter is invoked, it has exactly the same effect as the forwarding method written out above; the receiver is the reader field of the ReadWriter, not the ReadWriter itself.

内嵌和子类有一点很重要的不同。当我们嵌入一个类型时,该类型的方法变为外层类型的方法,但当它们被调用时方法的接收器是内层类型,而非外层类型。在我们的例子中,当 bufio.ReadWriterRead 方法被调用时,其效果跟我们之前编写的转发方法异曲同工;方法接收器是字段 reader 而不是 ReadWriter 自身。

Embedding can also be a simple convenience. This example shows an embedded field alongside a regular, named field.

内嵌也可以单纯为了方便。这个例子展示了一个内嵌字段和一个常规的命名字段。

type Job struct {
    Command string
    *log.Logger
}

The Job type now has the Print, Printf, Println and other methods of *log.Logger. We could have given the Logger a field name, of course, but it's not necessary to do so. And now, once initialized, we can log to the Job:

类型 Job 现在拥有 Print, Printf, Println 以及 *log.Logger 的其它方法。当然,我们本可以给字段 Logger 一个名字,但大可不必。且看现在,一旦初始化完毕,我们可以像下面一样记录日志:

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

The Logger is a regular field of the Job struct, so we can initialize it in the usual way inside the constructor for Job, like this,

Logger 就是 Job 的一个普通字段,因此我们可以在构造函数内使用一般方式初始化,像这样:

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

or with a composite literal,

或者以复合字面量的方式,

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

If we need to refer to an embedded field directly, the type name of the field, ignoring the package qualifier, serves as a field name, as it did in the Read method of our ReadWriter struct. Here, if we needed to access the *log.Logger of a Job variable job, we would write job.Logger, which would be useful if we wanted to refine the methods of Logger.

如果我们需要直接引用一个内嵌的字段,可以使用不包含包名的类型名,就像我们在 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...))
}

Embedding types introduces the problem of name conflicts but the rules to resolve them are simple. First, a field or method X hides any other item X in a more deeply nested part of the type. If log.Logger contained a field or method called Command, the Command field of Job would dominate it.

内嵌类型会带来命名冲突的问题,然而解决冲突的规则却很简单。首先,一个名为 X 的字段或方法会遮蔽任何更深嵌套层次的 X 项。如果 log.Logger 有一个 Command 方法,那么 JobCommand 字段将会覆盖它。

Second, if the same name appears at the same nesting level, it is usually an error; it would be erroneous to embed log.Logger if the Job struct contained another field or method called Logger. However, if the duplicate name is never mentioned in the program outside the type definition, it is OK. This qualification provides some protection against changes made to types embedded from outside; there is no problem if a field is added that conflicts with another field in another subtype if neither field is ever used.

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