golang没有继承的机制,但有嵌入的机制,将其他interface和struct嵌入当前类型,以起到复用的作用,达到继承的效果。
interface嵌入interface,如下常见的例子
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
Reader
Writer
}
struct也可以嵌入struct,使用了匿名字段机制,只嵌入stuct类型名称,而没有字段名称。
// ReadWriter stores pointers to a Reader and a Writer.
// It implements io.ReadWriter.
type ReadWriter struct {
*Reader // *bufio.Reader
*Writer // *bufio.Writer
}
struct ReadWriter可以直接用.引用嵌入类型的字段和方法,不用写上类型名称。当然,在引用字段和类型时,一定要对这个类型进行实例初始化。
以下列出了嵌入式类型使用的注意事项
外层类型调用方法
外层类型调用方法会传到到内嵌的类型
package main
import (
"fmt"
)
type Dog struct {
sound string
}
func (d *Dog) Sound() string {
return d.sound
}
type Animal struct {
Dog
sound string
}
func main() {
a := Animal{
sound: "...",
Dog: Dog{sound: "woof"},
}
fmt.Println(a.Sound()) // <- woof
}
上面的例子,调用Animal的Sound方法,最终调用的时Dog的Sound方法。
字段名称冲突
外层类型的字段或方法会覆盖内部类型的字段和方法。
package main
import (
"fmt"
)
type C struct {
Hello string
}
type B struct {
C
}
type A struct {
B
Hello string // <- added
}
func main() {
A := A{Hello: "world", B: B{C: C{Hello: "universe"}}}
fmt.Println(A.Hello) // <- world
}
A的Hello字段覆盖了C的Hello。
对于同级的字段名称冲突,可以直接用A.B.Hello明确访问那个类型的字段,而直接用A.Hello访问就会编译错误。
package main
import (
"fmt"
)
type B struct {
Hello string
}
type C struct {
Hello string
}
type A struct {
B
C
}
func main() {
A := A{B: B{Hello: "world 1"}, C: C{Hello: "world 2"}}
fmt.Println(A.Hello) // <- not allowed, will break
fmt.Println(A.B.Hello) // <- world 1
fmt.Println(A.C.Hello) // <- world 2
}
Marshalling / Unmarshalling
嵌入式类型,将json字符串Unmarshall成struct会有意向不到的错误,特别是遇到同级字段名称冲突的情况。如下面的例子,Color就不会展示出来。代码本身时可以编译成功的。
dogpackage main
import (
"encoding/json"
"fmt"
)
type Dog struct {
Color string `json:"color"`
}
type Cat struct {
Color string `json:"color"`
}
type Animal struct {
Dog
Cat
Kind string `json:"kind"`
}
func main() {
var a Animal
err := json.Unmarshal([]byte(`{"kind":"dog","color":"golden"}`), &a)
if err != nil {
panic(err)
}
fmt.Println(a.Kind) // dog
fmt.Println(a.Dog.Color) // not print
fmt.Println(a.Cat.Color) // not print
}
json的marshall和unmarshall也是依赖golang对字段的可见原则,同时也考虑了json的tag机制。会找到最匹配的字段进行编码。如果同层次的字段名称,tag都一样,会忽略编码,也不会报错。
要解决这个问题,可以重写类型的UnmarshallJson方法
package main
import (
"encoding/json"
"fmt"
)
type Dog struct {
Color string `json:"color"`
}
type Cat struct {
Color string `json:"color"`
}
type AnimalBase struct {
Kind string `json:"kind"`
}
type Animal struct {
AnimalBase
Dog
Cat
}
func (a *Animal) UnmarshalJSON(raw []byte) error {
var base AnimalBase
err := json.Unmarshal(raw, &base)
if err != nil {
return err
}
a.AnimalBase = base
switch base.Kind {
case "dog":
var dog Dog
err = json.Unmarshal(raw, &dog)
if err != nil {
return err
}
a.Dog = dog
case "cat":
var cat Cat
err = json.Unmarshal(raw, &cat)
if err != nil {
return err
}
a.Cat = cat
}
return nil
}
func main() {
var a Animal
err := json.Unmarshal([]byte(`{"kind": "dog", "color":"golden"}`), &a)
if err != nil {
panic(err)
}
fmt.Println(a.Kind)
fmt.Println(a.Dog.Color)
fmt.Println(a.Cat.Color)
}
以下内容为interface in struct的一些注意点。文章内容来自Embedding in Go: Part 3 - interfaces in structs
简单的例子
type Fooer interface {
Foo() string
}
type Container struct {
Fooer
}
func (cont Container) Foo() string {
return cont.Fooer.Foo()
}
// sink takes a value implementing the Fooer interface.
func sink(f Fooer) {
fmt.Println("sink:", f.Foo())
}
// TheRealFoo is a type that implements the Fooer interface.
type TheRealFoo struct {
}
func (trf TheRealFoo) Foo() string {
return "TheRealFoo Foo"
}
co := Container{Fooer: TheRealFoo{}}
sink(co)
如果Container初始化为co := Container() 则执行sink的时候会报错。
interface in struct使用模式的举例
interface包裹
实现StatsConn struct, 对读取的数据进行字节统计
type StatsConn struct {
net.Conn
BytesRead uint64
}
func (sc *StatsConn) Read(p []byte) (int, error) {
n, err := sc.Conn.Read(p)
sc.BytesRead += uint64(n)
return n, err
}
conn, err := net.Dial("tcp", u.Host+":80")
if err != nil {
log.Fatal(err)
}
sconn := &StatsConn{conn, 0}
resp, err := ioutil.ReadAll(sconn)
if err != nil {
log.Fatal(err)
}
对类型进行了扩充。
当然也可用用其他方式去实现这个功能,但需要重写接口的所有方法,比较繁琐。如下的普通实现举例
type StatsConn struct {
conn net.Conn
BytesRead uint64
}
func (sc *StatsConn) Read(p []byte) (int, error) {
n, err := sc.conn.Read(p)
sc.BytesRead += uint64(n)
return n, err
}
//there are 8 methods that should be rewrite
func (sc *StatsConn) Close() error {
return sc.conn.Close()
}
func (sc *StatsConn) Write() error {
return sc.conn.Write()
}
例子2: Sort.Reverse
继承了Sort.Interface,修改了Swap的方法,方便实现逆序排序。
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
type reverse struct {
sort.Interface
}
func (r reverse) Less(i, j int) bool {
return r.Interface.Less(j, i)
}
func Reverse(data sort.Interface) sort.Interface {
return reverse{data}
}
sort.Sort(sort.Reverse(sort.IntSlice(lst)))
fmt.Println(lst)
例子3: context.WithValue
valueCtx扩充了Context
func WithValue(parent Context, key, val interface{}) Context {
return &valueCtx{parent, key, val}
}
type valueCtx struct {
Context
key, val interface{}
}
func (c *valueCtx) Value(key interface{}) interface{} {
if c.key == key {
return c.val
}
return c.Context.Value(key)
}
例子4:将一个类型降低接口实现级别
这个是一个高级的功能,降低了一个类型的接口实现级别,为了解决嵌入循环依赖的问题。
首先看下io.ReaderFrom接口定义
type ReaderFrom interface {
ReadFrom(r Reader) (n int64, err error)
}
os.File类型实现了ReaderFrom这个接口
func (f *File) ReadFrom(r io.Reader) (n int64, err error) {
if err := f.checkValid("write"); err != nil {
return 0, err
}
n, handled, e := f.readFrom(r)
if !handled {
return genericReadFrom(f, r)
}
return n, f.wrapErr("write", e)
}
其中关键是调用了genericReadFrom,在这个函数中会调用io.Copy函数
func genericReadFrom(f *File, r io.Reader) (int64, error) {
return io.Copy(onlyWriter{f}, r)
}
onlyWriter是怎么定义的呢?onlyWriter是一个struct,嵌入了io.Writer interface
type onlyWriter struct {
io.Writer
}
奇怪的是代码中并没有看到onlyWriter的定义。那这个有什么用处呢。这个要看看io.Copy函数中的逻辑了,io.Copy函数是调用f.ReadFrom,同时我们就是为了实现ReadFrom这个method,会形成循环依赖。所以要单独定义onlyWriter打破这个依赖。