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. app
Go沒有像其它面嚮對象語言中的類繼承概念 可是 它能夠經過在結構體或者接口中嵌入其它的類型 來使用被嵌入類型的功能 dom
Interface embedding is very simple. We've mentioned the io.Reader and io.Writer interfaces before; here are their definitions. ide
嵌入接口很是簡單 咱們以前提到過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: this
io包導出了其它的接口 舉例來講 io.ReadWriter 這個接口包含了Read和Write 固然 咱們能夠直接定義io.ReadWriter接口 把相應的Write和Read函數直接定義其中 可是 嵌入接口的作法更好 能夠利用已有的東西嘛 何須啥都親力親爲: idea
// 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的功能 只有接口才能被嵌入到接口 code
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. orm
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