Golang设计模式(二) | 工厂模式

工厂模式(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()
}

NewCollegeStudentNewMiddleStudent都返回了同一个接口类型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,因为NewMiddleStudentNewCollegeStudent都返回的是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")

本博文到此结束,祝你愉快~

comments powered by Disqus