10.2 使用工厂方法创建结构体实例

10.2.1 结构体工厂

Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂”方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new...New... 开头。假设定义了如下的 File 结构体类型:

  1. type File struct {
  2. fd int // 文件描述符
  3. name string // 文件名
  4. }

下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:

  1. func NewFile(fd int, name string) *File {
  2. if fd < 0 {
  3. return nil
  4. }
  5. return &File{fd, name}
  6. }

然后这样调用它:

  1. f := NewFile(10, "./test.txt")

在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造函数。

如果 File 是一个结构体类型,那么表达式 new(File)&File{} 是等价的。

这可以和大多数面向对象编程语言中笨拙的初始化方式做个比较:File f = new File(...)

我们可以说是工厂实例化了类型的一个对象,就像在基于类的 OO 语言中那样。

如果想知道结构体类型 T 的一个实例占用了多少内存,可以使用:size := unsafe.Sizeof(T{})

如何强制使用工厂方法

通过应用可见性规则参考 4.2.1节9.5 节 就可以禁止使用 new() 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。

  1. type matrix struct {
  2. ...
  3. }
  4. func NewMatrix(params) *matrix {
  5. m := new(matrix) // 初始化 m
  6. return m
  7. }

在其他包里使用工厂方法:

  1. package main
  2. import "matrix"
  3. ...
  4. wrong := new(matrix.matrix) // 编译失败(matrix 是私有的)
  5. right := matrix.NewMatrix(...) // 实例化 matrix 的唯一方式

10.2.2 map 和 struct vs new() 和 make()

new()make() 这两个内置函数已经在第 7.2.4 节通过切片的例子说明过一次。

现在为止我们已经见到了可以使用 make() 的三种类型中的其中两个:

  1. slices / maps / channels(见第 14 章)

下面的例子说明了在映射上使用 new()make() 的区别以及可能发生的错误:

示例 10.4 new_make.go(不能编译)

  1. package main
  2. type Foo map[string]string
  3. type Bar struct {
  4. thingOne string
  5. thingTwo int
  6. }
  7. func main() {
  8. // OK
  9. y := new(Bar)
  10. (*y).thingOne = "hello"
  11. (*y).thingTwo = 1
  12. // NOT OK
  13. z := make(Bar) // 编译错误:cannot make type Bar
  14. (*z).thingOne = "hello"
  15. (*z).thingTwo = 1
  16. // OK
  17. x := make(Foo)
  18. x["x"] = "goodbye"
  19. x["y"] = "world"
  20. // NOT OK
  21. u := new(Foo)
  22. (*u)["x"] = "goodbye" // 运行时错误!! panic: assignment to entry in nil map
  23. (*u)["y"] = "world"
  24. }

试图 make() 一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是 new() 一个 map 并试图向其填充数据,将会引发运行时错误! 因为 new(Foo) 返回的是一个指向 nil 的指针,它尚未被分配内存。所以在使用 map 时要特别谨慎。

链接