Go 语言方法与接口的值与指针

值接收者与指针接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import "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 main

import (
"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
//工厂函数, 返回一个error接口, 其实具体实现是*errorString

func New(text string) error {

return &errorString{text}

}

//结构体, 内部一个字段s, 存储错误信息

type errorString struct {

s string

}

//用于实现error接口

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 main

import (
"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 接口

pic

同样, 结构体也能组合

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 main

import "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 main

import (
"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

All articles in this blog adopt the CC BY-SA 4.0 agreement unless otherwise stated. Please indicate the source for reprinting!