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.
Interface embedding is very simple. We've mentioned the io.Reader and io.Writer interfaces before; here are their definitions.
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:
// 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.
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.
// 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
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:
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.
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.
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.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,
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.
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.
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.
这段文字解释了 Go 语言中的“嵌入”(Embedding)特性。Go 语言不支持传统的、基于类型的子类化,但它允许通过在结构体或接口中嵌入其他类型来“借用”它们的实现。以下是对该段落的逐点解释:
1、接口的嵌入:你可以在一个接口中嵌入其他接口。例如,你可以有一个 Reader 接口和一个 Writer 接口,然后创建一个 ReadWriter 接口,该接口同时嵌入了 Reader 和 Writer。这意味着任何实现了 ReadWriter 的类型都必须实现 Reader 和 Writer 中的方法。
2、结构体的嵌入:与接口嵌入类似,你也可以在一个结构体中嵌入其他结构体。例如,bufio 包中的 ReadWriter 结构体嵌入了 Reader 和 Writer 结构体。这意味着 ReadWriter 结构体拥有 Reader 和 Writer 的所有方法,并且它也满足了 io.Reader、io.Writer 和 io.ReadWriter 这三个接口的要求。
3、方法的接收者:当你调用一个嵌入类型的方法时,这个方法的接收者是嵌入的类型,而不是包含它的类型。所以,在前面的例子中,当你调用 bufio.ReadWriter 的 Read 方法时,它实际上是调用嵌入的 Reader 的 Read 方法。
4、简单的便利性:嵌入可以是一种简化代码的方法。例如,Job 结构体嵌入了 *log.Logger,这意味着你可以直接在 Job 上调用 Logger 的方法,如 Println,而无需额外的字段名。
5、访问嵌入的字段:如果你需要直接引用嵌入的字段,可以使用该字段的类型名(去掉包名)作为字段名。例如,要访问 Job 变量的 *log.Logger,你可以写 job.Logger。
6、名称冲突:嵌入类型可能会导致名称冲突,但解决这些冲突的规则很简单。首先,一个字段或方法 X 会隐藏在类型中更深层次的同名项。其次,如果同一层级上有相同的名称,通常会报错。但是,如果这个重复的名称从未在类型定义之外的程序中提到,则不会有问题。