前言

这是Go常见错误系列的第14篇:过度使用getter和setter方法。

素材来源于Go布道者,现Docker公司资深工程师Teiva Harsanyi

本文涉及的源代码全部开源在:Go常见错误源代码,欢迎大家关注公众号,及时获取本系列最新更新。

常见错误和最佳实践

现状

写Java或者C++的人,可能会习惯下面的编程模式:

  • 将不希望外部直接访问的类成员变量设置为private私有成员。
  • 在类里定义public的get和set方法,用于外部获取和修改这个成员变量的值。get方法我们叫做getter,set方法叫做setter。

这是一种数据封装模式,在Java和C++里被广泛使用。

但是在Go语言里,官方从来没有建议使用getter和setter,我们可以直接访问结构体里的成员变量。

成员变量的可见性通过结构体标识符首字母大小写以及成员变量首字母大小写来控制到package这个层面。

  • 如果结构体要被其它package使用,那结构体的标识符或者说结构体的名称首字母要大写。
  • 如果结构体的成员要被其它package使用,那结构体和结构体的成员标识符首字母都要大写,否则只能在当前包里使用。

举个Go标准库里的time.Timer结构体的例子:

1
2
3
4
5
6
7
8
// The Timer type represents a single event.
// When the Timer expires, the current time will be sent on C,
// unless the Timer was created by AfterFunc.
// A Timer must be created with NewTimer or AfterFunc.
type Timer struct {
	C <-chan Time
	r runtimeTimer
}

Timer结构体定义如上所示,里面有一个成员变量C用于接收Timer到点后的当前时间。

Timer和C都是大写,所以我们可以直接在下面的代码里访问Timer里的成员变量C拿到当前时间。

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

import (
	"fmt"
	"time"
)

func main() {
	// print current time
	fmt.Println(time.Now())

	// NewTimer creates a new Timer that will send
	// the current time on its channel after at least duration d.
	timer := time.NewTimer(5 * time.Second)

	// print current time
	fmt.Println(<-timer.C)
}

上面程序执行结果是:

1
2
2022-11-06 13:07:07.706011 +0800 CST m=+0.000174256
2022-11-06 13:07:12.709128 +0800 CST m=+5.003141645

这种写法当然不是Go官方所预期的,因为成员变量C一般来说是不直接对外访问。

如果C暴露了可以对外访问,那我们甚至修改C的值,导致程序出错。

尽管不推荐这种写法,但是通过这个例子,我们可以知道如下事实:

Go标准库里对于结构体里不应该修改的字段,也没有使用getter和setter方法

辩证来看

尽管Go官方没有使用getter和setter,但是从另一方面来说,在一些特定场景下使用getter和setter是有好处的。

  • getter和setter隐藏了内部实现,我们可以自己灵活控制该暴露哪些东西。
  • 如果成员变量的值发生了预期之外的变化,那通过getter和setter,我们可以方便做一些调试,更快发现问题。

Go语言里如果要使用getter和setter方法,有一些命名规范需要遵循。

假设我们要对结构体里的成员变量balance增加getter和setter方法,那么规范如下:

  • getter方法应该被命名为Balance(而不是GetBalance)。
  • setter方法应该被命名为SetBalance。
  • 首字母大写是因为要被外部package使用,要大写来保证可见性。

示例如下:

1
2
3
4
currentBalance := customer.Balance()
if currentBalance < 0 {
    customer.SetBalance(0)
}

总结

  • Java/C++等语言里常用的getter和setter,在Go语言里并不是惯例和规范。
  • 但是如果发现有上面讲到的需要使用到getter和setter的场景,那还是应该使用的,而不是完全不用。
  • getter和setter方法命名参考上面提到的命名规范。

推荐阅读

开源地址

文章和示例代码开源在GitHub: Go语言初级、中级和高级教程

公众号:coding进阶。关注公众号可以获取最新Go面试题和技术栈。

个人网站:Jincheng’s Blog

知乎:无忌

福利

我为大家整理了一份后端开发学习资料礼包,包含编程语言入门到进阶知识(Go、C++、Python)、后端开发技术栈、面试题等。

关注公众号「coding进阶」,发送消息 backend 领取资料礼包,这份资料会不定期更新,加入我觉得有价值的资料。

发送消息「进群」,和同行一起交流学习,答疑解惑。

References