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.Reader和io.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 这个接口包含了Read和Write 当然 我们可以直接定义io.ReadWriter接口 把相应的Write和Read函数直接定义其中 但是 嵌入接口的做法更好 可以利用已有的东西嘛 何必啥都亲力亲为:
// 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 (which must be disjoint sets of methods). Only interfaces can be embedded within interfaces.
ReadWriter同时提供Reader和Writer的功能 只有接口才能被嵌入到接口
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.
struct可以嵌入其它类型 bufio包有两个结构体类型 bufio.Reader 和 bufio.Writer 它们分别实现了和io包中Reader Writer类似功能的接口 bufio通过把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:
但是 如果使用这样给了字段名的方式的话 在实现的时候就需要做一次转换 把相应的操作传递给ReadWrtier.reader 或者ReadWriter.writer
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.Reader bufio.Writer的方法 它同时也满足了io.Reader io.Writer 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.ReadWriter的Read方法被调用时 它产生的效果 和我们之前定义的 有字段名的方式(reader *Reader)一样 把调用传递给了内部的类型
Embedding can also be a simple convenience. This example shows an embedded field alongside a regular, named field.
类型嵌入其实可以很简单 看下面这段代码 Job有一个直接嵌入的类型 *log.Logger和有字段名的类型 Command string:
type Job struct { Command string *log.Logger }
The Job type now has the Log, Logf 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现在有了*log.Logger的方法 例如Log Logf 现在 当它被初始化后 我们可以直接通过Job来使用Log:
job.Log("starting now...")
The Logger is a regular field of the struct and we can initialize it in the usual way with a constructor,
Logger是结构体的一个字段 我们可以使用正常的初始化方式:
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. If we needed to access the *log.Loggerof a Job variable job, we would write job.Logger. This would be useful if we wanted to refine the methods of Logger.
如果我们需要显式地引用被嵌入的字段 可以利用不带包前缀的字段类型来达到目的 看下面这段代码 我们想访问Job变量job的*log.Loggerof字段 我们可以写成job.Logger 在需要重新定义Logger的方法很有用
func (job *Job) Logf(format string, args ...interface{}) { job.Logger.Logf("%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字段或者方法 那么Job的最外层Command就会隐藏掉log.Logger里的Command
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的 这种限制可以保护类型在其它地方被嵌入的更改 如果一个字段被添加后会和其它子类型中的字段冲突 但是这两个冲突的字段都没有使用 那么一切都OK
有疑问加站长微信联系(非本文作者)