# 10.2 使用工厂方法创建结构体实例 ## 10.2.1 结构体工厂 Go语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在Go中实现“构造子工厂“方法。为了方便通常会为类型定义一个工厂,俺惯例,工厂的名字以new或New开头。假设定义了如下的File结构体类型: ```go type File struct { fd int // 文件描述符 name string // 文件名 } ``` 下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针: ```go func NewFile(fd int, name string) *File { if fd < 0 { return nil } return &File(id, name) } ``` 然后这样调用它:`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函数,强制用户使用工厂方法,从而使类型变成私有的,就像在OO语言中那样。 ```go type matrix struct { ... } func NewMatrix(params) *matrix { m := new(matrix) // 初始化m return m } ``` 在其他包里使用工厂方法: ```go package main import "matrix" ... wrong := new(matrix.matrix) // 编译失败(matrix是私有的) right := matrix.NewMatrix(...) // 实例化matrix的唯一方式 ``` ## 10.2.2 map和struct vs new()和make() new和make这两个内置函数已经在[7.2.4](7.2.md)节通过切片的例子说明过一次。 现在为止我们已经见到了可以使用make()的三种类型中的其中两个: slices / maps / channels(见第14章) 下面的例子来说明了在映射上使用new和make的区别,以及可能的发生的错误: Listing 10.4—new_make.go(不能编译) ```go package main type Foo map[string]string type Bar struct { thingOne string thingTwo int } func main() { // OK y := new(Bar) (*y).thingOne = "hello" (*y).thingTwo = 1 // NOT OK z := make(Bar) // 编译错误:cannot make type Bar (*y).thingOne = "hello" (*y).thingTwo = 1 // OK x := make(Foo) x["x"] = "goodbye" x["y"] = "world" // NOT OK u := new(Foo) (*u)["x"] = "goodbye" // 运行时错误!! panic: assignment to entry in nil map (*u)["y"] = "world" } ``` 试图make()一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是new()一个映射并试图使用数据填充它,将会引发运行时错误! 因为new(Foo)返回的是一个指向nil的指针,它尚未被分配内存。所以在使用map时要特别谨慎。 ## 链接 - [目录](directory.md) - 上一节:[10 结构(struct)与方法(method)](10.0.md) - 下一节:[10.3 使用结构体定制包](10.3.md)