影响

1
2
func (t T) M1() <=> F1(t T)
func (t *T) M2() <=> F2(t *T)
Method Desc
M1 基于值拷贝,传递的是 T 类型实例的副本
M2 基于值拷贝,传递的是 T 类型实例的指针
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
type T struct {
a int
}

func (t T) M1() {
t.a = -1
}

func (t *T) M2() {
t.a += 1
}

func main() {
var t T
println(t.a) // 0

t.M1()
println(t.a) // 0

t.M2()
println(t.a) // 1

p := &t

p.M1()
println(t.a) // 1

p.M2()
println(t.a) // 2
}

方法集合

方法集合决定了接口实现:用来判断一个类型是否实现了接口类型的唯一手段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Interface interface {
M1()
M2()
}

type T struct{}

func (t T) M1() {}
func (t *T) M2() {}

func main() {
var i Interface

var p *T
var t T

i = p
i = t
// cannot use t (type T) as type Interface in assignment:
// T does not implement Interface (M2 method has pointer receiver)
}
  1. 任何一个类型都有属于该类型的方法集合,对于没有定义方法的类型(如 int),对应的是空方法集合
  2. 接口类型只会列出代表接口的方法列表,而不会具体定义某个方法
    • 接口类型的方法集合就是它方法列表中的所有方法

*T 类型的方法集合,包含所有以 *T 或者 T 为 receiver 参数类型的方法

T 类型的方法集合,仅包含所有以 T 为 receiver 参数类型的方法

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
func main() {

var n int
dumpMethodSet(n)
dumpMethodSet(&n)

var t T
dumpMethodSet(t)
dumpMethodSet(&t)
}

type T struct{}

func (T) M1() {}
func (T) M2() {}

func (*T) M3() {}
func (*T) M4() {}

func dumpMethodSet(i interface{}) {
dumpType := reflect.TypeOf(i)

if dumpType == nil {
fmt.Printf("there is no type info for %v\n", i)
return
}

n := dumpType.NumMethod()
if n == 0 {
fmt.Printf("no methods found for '%T'\n", i)
return
}

fmt.Printf("Method set for '%T' (type %v):\n", i, dumpType)
for i := 0; i < n; i++ {
fmt.Printf("- %v\n", dumpType.Method(i).Name)
}
fmt.Println()
}

// Output:
// no methods found for 'int'
// no methods found for '*int'
// Method set for 'main.T' (type main.T):
// - M1
// - M2
//
// Method set for '*main.T' (type *main.T):
// - M1
// - M2
// - M3
// - M4

T 类型的方法集合仅有 M1,而 Interface 的方法集合有 M1 和 M2,所以不匹配

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
53
54
55
type Interface interface {
M1()
M2()
}

type T struct{}

func (t T) M1() {}
func (t *T) M2() {}

func dumpMethodSet(i interface{}) {
dumpType := reflect.TypeOf(i)

if dumpType == nil {
fmt.Printf("there is no type info for %v\n", i)
return
}

n := dumpType.NumMethod()
if n == 0 {
fmt.Printf("no methods found for '%T'\n", i)
return
}

fmt.Printf("Method set for '%T' (type %v):\n", i, dumpType)
for i := 0; i < n; i++ {
fmt.Printf("- %v\n", dumpType.Method(i).Name)
}
fmt.Println()
}

func main() {
var i Interface

var p *T
var t T

dumpMethodSet(i)
dumpMethodSet(p)
dumpMethodSet(t)

i = p
// i = t
// cannot use t (type T) as type Interface in assignment:
// T does not implement Interface (M2 method has pointer receiver)
}

// Output:
// there is no type info for <nil>
// Method set for '*main.T' (type *main.T):
// - M1
// - M2
//
// Method set for 'main.T' (type main.T):
// - M1

如果类型 T 的方法集合与接口类型 I 的方法集合相同或者是其超集,那么类型 T 实现了接口 I

原则

第一原则

如果 Method 需要对 receiver 参数代表的类型实例进行修改,并反映原类型实例上,选择 *T

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
type T struct {
a int
}

func (t T) M1() {
t.a = 11
}

func (t *T) M2() {
t.a = 12
}

func main() {
var t T
println(t.a) // 0
t.M1()
println(t.a) // 0
t.M2() // 编译器提供的语法糖,自动转换为(&t).M2()
println(t.a) // 12

var p = &T{}
println(p.a) // 0
p.M1() // 编译器提供的语法糖,自动转换为(*p).M1()
println(p.a) // 0
p.M2()
println(p.a) // 12
}

T 类型实例或者 *T 类型实例,都可以调用所有 receiver 为 T 或者 *T 的 Method - 语法糖

第二原则

  1. 不需要在 Method 中对类型实例进行修改时,一般会选择 T 类型 - 缩窄接触面
  2. 如果 receiver 参数类型的 size 较大,基于值拷贝传递,会导致较大的性能开销,可以选择 *T 类型

第三原则

往往是首要考虑的原则 - 全局设计

  1. *T 的方法集合包含T 的方法集合
  2. 如果 T 类型需要实现某个接口,那么就需要使用 T 作为 receiver 参数的类型