值接收者与指针接收者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport "fmt" type Age uint func (a Age) Modify() { a = Age(30 ) }func main () { age := Age(25 ) (&age).Modify() age.Modify() fmt.Println(age) }
在调用方法的时候, 传递的接收者本质上都是副本, 只不过一个是这个值副本, 一是指向这个值指针的副本。指针具有指向原有值的特性, 所以修改了指针指向的值, 也就修改了原有的值。我们可以简单地理解为值接收者使用的是值的副本来调用方法, 而指针接收者使用实际的值来调用方法。
二者都可以调用方法, 但对于接口来说
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport ( "fmt" "strconv" )type Age uint type Female bool type Stringer interface { String() string }func printString (s Stringer) { fmt.Println(s.String()) }func (a Age) String() string { return strconv.Itoa(int (a) + 1 ) }func (f *Female) String() string { return strconv.FormatBool(!bool (*f)) }func main () { age := Age(25 ) printString(&age) female := Female(true ) printString(female) }
编译不通过
1 2 3 # command-line-arguments .\main.go:32:13: cannot use female (type Female) as type Stringer in argument to printString: Female does not implement Stringer (String method has pointer receiver)
以指针类型实现接口的时候, 只有该指针类型实现了接口, 而以值接收者实现接口的时候, 则指针和值都实现了接口
方法接收者
实现接口的类型
(p person)
person 和 *person
(p *person)
*person
工厂函数 工厂函数一般用于创建自定义的结构体, 便于使用者调用, 通过工厂函数创建自定义结构体的方式, 可以让调用者不用太关注结构体内部的字段, 只需要给工厂函数传参就可以了
工厂函数也可以用来创建一个接口, 它的好处就是可以隐藏内部具体类型的实现, 让调用者只需关注接口的使用即可
以 errors.New 这个 Go 语言自带的工厂函数为例, 演示如何通过工厂函数创建一个接口, 并隐藏其内部实现, 如下代码所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func New (text string ) error { return &errorString{text} }type errorString struct { s string }func (e *errorString) Error() string { return e.s }
其中, errorString 是一个结构体类型, 它实现了 error 接口, 所以可以通过 New 工厂函数, 创建一个 *errorString 类型, 通过接口 error 返回。
这就是面向接口的编程, 假设重构代码, 哪怕换一个其他结构体实现 error 接口, 对调用者也没有影响, 因为接口没变
组合 Go 没有继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package mainimport ( "fmt" "time" )type Anna struct { }type James struct { }func (Anna) Egg() bool { return time.Now().Unix()%2 != 0 }func (James) Sperm() bool { return time.Now().Unix()%2 != 0 }type Human struct { }func (h Human) Egg() bool { return time.Now().Unix()%2 != 0 }func (h Human) Sperm() bool { return time.Now().Unix()%2 != 0 }type men interface { Sperm() bool }type women interface { Egg() bool }type human interface { men women }func main () { a := Anna{} j := James{} h := Human{} fmt.Printf("Anna and James combine into a fertilized egg is %v\n" , a.Egg() && j.Sperm()) fmt.Printf("Combine into a fertilized egg is %v\n" , human(h).Sperm() && human(h).Egg()) }
即实现 io.Reader
接口和 io.Writer
的接口 即实现了 ReaderWriter
接口
同样, 结构体也能组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport "fmt" type Person struct { Name string Age uint Address }type Address struct { Province string City string }func (Address) print () { fmt.Println("Address" ) }func (Person) print () { fmt.Println("Person" ) }func main () { p := Person{ Name: "Test" , Age: 0 , Address: Address{ Province: "Shanghai" , City: "Shanghai" , }, } fmt.Println(p.City) p.print () }
组合后, 被组合的 address 称为内部类型, person 称为外部类型
类型组合后, 外部类型不仅可以使用内部类型的字段, 也可以使用内部类型的方法, 就像使用自己的方法一样。如果外部类型定义了和内部类型同样的方法, 那么外部类型的会覆盖内部类型, 这就是方法的覆写
方法覆写不会影响内部类型的方法实现
类型断言 类型断言用来判断一个接口的值是否是实现该接口的某个具体类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package mainimport ( "fmt" "strconv" )type Age uint type Female bool type Stringer interface { String() string }func printString (s Stringer) { fmt.Println(s.String()) }func (a Age) String() string { return strconv.Itoa(int (a) + 1 ) }func (f *Female) String() string { return strconv.FormatBool(!bool (*f)) }func main () { age := Age(25 ) printString(&age) var s Stringer s = age p2 := s.(*Female) fmt.Println(p2) }
运行则报错
1 panic: interface conversion: main.Stringer is main.Age, not *main.Female