工厂模式(Factory Pattern)是面向对象编程中的常用模式。在 Golang 项目开发中,你可以通过使用简单工厂模式、抽象工厂模式、工厂方法模式,来使代码更简洁明了。 简单工厂模式返回结构体类型;而抽象工厂模式返回接口类型;工厂方法返回一个闭包,下面具体的看看每一种工厂模式的使用。
Golang 中的结构体,可以理解为面向对象编程中的类,例如 Student 结构体(类)实现了 SayHello 方法。
1
2
3
4
5
6
7
8
9
|
type Student struct {
name string
// 可以使用uint8类型表示例sex别,这里为了仅仅只是为了直观可视
sex string
}
func (s Student) SayHello() {
fmt.Printf("Hi! My name is %s, %s", s.name, s.sex)
}
|
有了 Student 结构体(类),就可以创建 Student 实例。我们可以通过简单工厂模式、抽象工厂模式、工厂方法模式这三种方式,来创建一个 Student 实例。
简单工厂模式
这三种工厂模式中,简单工厂模式是最常用、最简单的。它是一个接受一些参数,然后返回 Student 实例的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
type Student struct {
name string
// 可以使用uint8类型表示例sex别,这里为了仅仅只是为了直观可视
sex string
}
func (s Student) SayHello() {
fmt.Printf("Hi! My name is %s, %s", s.name, s.sex)
}
func NewStudent(name, sex string) *Student {
return &Student{
name: name,
sex: sex,
}
}
|
相比 s := &Student{}
这种创建实例的方式而言,简单工厂模式可以确保创建的实例具有需要的参数,进而保证实例的方法可以按预期执行。例如,通过 NewStudent 创建 Student 实例时,可以确保实例的 name 和 sex 字段(属性)被设置。
抽象工厂模式
下面来看抽象工厂模式,抽象工厂模式和简单工厂模式相比唯一的区别,就是它返回的是接口(interface{})而不是结构体。通过返回接口,可以在你不公开内部实现的情况下让调用者使用你提供的各种功能,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
type Student interface {
SayHello()
}
type student struct {
name string
sex string
}
func (s student) SayHello() {
fmt.Printf("Hi! My name is %s, %s", s.name, s.sex)
}
// NewStudent returns an interface
func NewStudent(name, sex string) Student {
return student{
name: name,
sex: sex,
}
}
|
上面代码,定义了一个不可导出的结构体 student,在通过 NewStudent 创建实例的时返回的是接口,而不是结构体。 通过返回接口的好处是,我们还可以实现多个不同的工厂函数,来返回接口的不同实现,例如:
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
|
package factory
import "fmt"
type Student interface {
SayHello()
}
// 中学生
type middle struct {
name string
sex string
}
func (s middle) SayHello() {
fmt.Printf("Hi! My name is %s, %s, middle student\n", s.name, s.sex)
}
// NewMiddleStudent 返回一个Student接口类型的中学生实现。
func NewMiddleStudent(name, sex string) Student {
return middle{
name: name,
sex: sex,
}
}
// 大学生
type college struct {
name string
sex string
}
func (s college) SayHello() {
fmt.Printf("Hi! My name is %s, %s, college student\n", s.name, s.sex)
}
// NewCollegeStudent 返回一个Student接口类型的大学生实现。
func NewCollegeStudent(name, sex string) Student {
return college{
name: name,
sex: sex,
}
}
// SayHello 接受一个 Student 接口类型,这其实一种动态类型,DuckTyping
func SayHello(student Student) {
student.SayHello()
}
|
NewCollegeStudent和NewMiddleStudent都返回了同一个接口类型Student,这使得二者可以互换使用。在上面代码中我们还定义了一个SayHello函数,该函数接受一个Student接口类型实现,这样我们仅仅只需要调用Student接口的SayHello方法即可,我们并不关心你是 “中学生” 还是 “大学生”,只要你实现了SayHello方法,你也就实现了这个接口。
下面这段代码是上面这段代码的测试用例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func Test_Student(t *testing.T) {
var student Student
// 中学生
student = NewMiddleStudent("tom", "male")
// Hi! My name is tom, male, middle student
student.SayHello()
// 大学生
student = NewCollegeStudent("kitty", "female")
// Hi! My name is kitty, female, college student
student.SayHello()
}
func Test_SayHello(t *testing.T) {
// 中学生 Hi! My name is tom, male, middle student
SayHello(NewMiddleStudent("tom", "male"))
// 大学生 Hi! My name is kitty, female, college student
SayHello(NewCollegeStudent("kitty", "female"))
}
|
第一个测试用例Test_Student,因为NewMiddleStudent和NewCollegeStudent都返回的是Student同一个接口,所有它们可以赋值给student变量。
第二个测试用例Test_SayHello我想说明的是DuckTyping问题,这也是面向接口开发的一个好处,我们不需要关心是 “小学生” 还是 “中学生” 异或是 “大学生”,只要你能 “问好(实现了SayHello方法)” 我们就让你 “上车”,否则不好意思。
工厂方法模式
在简单工厂模式中,依赖于唯一的工厂对象,如果我们需要实例化一个产品,就要向工厂中传入一个参数,获取对应的对象;如果要增加一种产品,就要在工厂中修改创建产品的函数。这会导致耦合性过高,这时我们就可以使用工厂方法模式。
在工厂方法模式中,依赖工厂接口,我们可以通过实现工厂接口来创建多种工厂,将对象创建从由一个对象负责所有具体类的实例化,变成由一群子类来负责对具体类的实例化,从而将过程解耦。
下面是工厂方法模式的一个代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
type Student struct {
name string
sex string
}
func NewStudentFactory(sex string) func(name string) Student {
return func(name string) Student {
return Student{
name: name,
sex: sex,
}
}
}
|
然后,我们可以使用此功能来创建具有默认性别的工厂:
1
2
3
4
5
6
7
8
|
// 默认性别(男)
maleStudent := NewStudentFactory("male")
tom := maleStudent("tom")
david := maleStudent("david")
// 默认性别(女)
femaleStudent := NewStudentFactory("female")
kitty := femaleStudent("kitty")
|
本博文到此结束,祝你愉快~