Go struct exported and unexported

"Go, struct"

Posted by Keal on September 9, 2020

基本原则

  1. struct名称小写开头, 则此struct不会被导出,即不会被外部包使用
  2. struct名称大写开头,则struct会被导出,但是内部只有是大写字母开头的字段名称被导出, 即小写字母开头的内部字段不会被导出

例外情况: struct的嵌套

1
2
3
4
5
6
7
8
9
// Horse能被导出, 虽然animal字段是小写,但是依然能够访问到其中的Speak属性
type animal struct{
    name string
    Speak string
}
type Horse struct {
    animal
    sound string
}

实践

最好不要直接暴露struct,而是通过提供func来间接暴露struct

​ 为了避免类似struct嵌套情况下违背小写字母开头名称不被导出的原意(例如上文例子中的animal的Speak属性被访问),我们最好不直接暴露struct, 而是建立一个func来给外部访问.

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
//abc.go文件内容:
package abc

type animal struct{
    name string
    Speak string
}

// 通过NewAnimal向外界暴露animal
func NewAnimal() *animal{
    a := new(animal)
    return a
}


// test.go内容:
package main

import (
    "fmt"
    "./abc"
)

func main() {
    t1 := abc.NewAnimal()
//  t1.name = "haha"    // 无法访问name属性
    t1.Speak = "hhhh"
    fmt.Println(t1.Speak)
}

​ 上述代码虽然通过NewAnimal()使外部包能够构建animal, 但是其中的name字段因为是小写字母开头,依然无法被访问, 如果需要name可以被外界操作又不希望能通过访问name属性直接修改, 可以提供两个func来完成这项工作, 例如:

1
2
3
4
5
6
7
// 通过暴露GetName和SetName使外部包可以访问和修改animal的name属性值,并且你可以增加一些逻辑,例如对name字段的合法化校验等
func (a *animal) GetName() string {
    return a.name
}
func (a *animal) SetName(name string){
    a.name = name
}

小心结构体的嵌入

规范

​ 通常在结构体中,嵌入的结构体应该放在字段顶部, 并且通过一个空行将其与常规字段分开.

1
2
3
4
5
6
7
8
9
10
11
12
// Good
type Client struct {
  http.Client

  version int
}

// Bad
type Client struct {
  version int
  http.Client
}

​ 避免在公共结构中嵌入类型,因为这些嵌入的类型泄漏实现细节, 禁止类型演化, 使文档模糊。通过委托的方式调用可以方便在未来更改具体的Add行为和Remove行为, 否则你就要直接更改AbstractList里的方法, 这可能并不是你的原意.

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
type AbstractList struct {}

// Add adds an entity to the list.
func (l *AbstractList) Add(e Entity) {
  // ...
}

// Remove removes an entity from the list.
func (l *AbstractList) Remove(e Entity) {
  // ...
}

// Bad
// ConcreteList is a list of entities.
type ConcreteList struct {
  *AbstractList
}


// Good
// ConcreteList is a list of entities.
type ConcreteList struct {
  list *AbstractList
}

// Add adds an entity to the list.
func (l *ConcreteList) Add(e Entity) {
  return l.list.Add(e)
}

// Remove removes an entity from the list.
func (l *ConcreteList) Remove(e Entity) {
  return l.list.Remove(e)
}