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
中显示地列出这两个方法,但是通过嵌入两个接口来组成新接口的方式则更加便宜,而且顺理成章:
// 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
同时拥有 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.
以此类推,我们可以联想到结构体的内嵌。不同的是,结构体内嵌具有更为深远的影响。bufio
包有两个结构体,分别是 bufio.Reader
和 bufio.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.Reader
和 bufio.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.ReadWriter
的 Read
方法被调用时,其效果跟我们之前编写的转发方法异曲同工;方法接收器是字段 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
方法,那么 Job
的 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 的。 此限定提供一些保护,防止从外部对嵌入的类型进行更改。 因此,就算添加的字段与另一个子类型中的字段相冲突,只要这两个相同的字段永远不会被使用就没问题。