简单工厂方法

问题

刚开始设计程序的时候,可能只需要程序提供一个苹果,每次有需求就new 一个Apple,随着需求的增加或者变化,后续可能需要new一个Apple,也可能需要new一个Banana,或者Orange等,如果每次都直接new一个对应的水果的话,则可能每次来一个新的水果,都得重写自己的代码。

解决方案

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

工厂模式将直接调用构造函数的方法切换为通过调用特殊的工厂方法来实现。

394bf6ec-f02a-43cc-8744-6a8bd9925a02.png

我们先定义所有水果的接口类Fruit以及需要的水果类

 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
type Fruit interface {
	Name() string
}
type Apple struct {
	name string
}

func (a *Apple) Name() string {
	return a.name
}

type Banana struct {
	name string
}

func (a *Banana) Name() string {
	return a.name
}

type Orange struct {
	name string
}

func (a *Orange) Name() string {
	return a.name
}

然后我们可以使用一个简单工厂来决定创建哪一种水果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func NewSimpleFruitFactory(name string) Fruit {
	switch name {
	case "apple":
		return &Apple{name: name}
	case "banana":
		return &Banana{name: name}
	case "orange":
		return &Orange{name: name}
	}
	return nil
}

新建一个单元测试

 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
func TestNewSimpleFruitFactory(t *testing.T) {
	type args struct {
		name string
	}
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			name: "apple",
			args: args{
				name: "apple",
			},
			want: "apple",
		},
		{
			name: "banana",
			args: args{
				name: "banana",
			},
			want: "banana",
		},
		{
			name: "orange",
			args: args{
				name: "orange",
			},
			want: "orange",
		},
	}
	for _, tes := range tests {
		t.Run(tes.name, func(ts *testing.T) {
			if got := NewSimpleFruitFactory(tes.args.name); !(got.Name() == tes.want) {
				ts.Errorf("NewSimpleFruitFactory Name=%s, want %s", got.Name(), tes.want)
			}
		})
	}
}

使用简单工厂模式的优势:让对象的调用者和对象创建过程分离,当对象调用者需要对象时,直接向工厂请求即可。从而避免了对象的调用者与对象的实现类以硬编码方式耦合,以提高系统的可维护性、可扩展性。工厂模式也有一个小小的缺陷:当产品修改时,工厂类也要做相应的修改,违反了开闭原则。如上例,需要增加一种新的水果时,需要修改工厂类NewSimpleFruitFactory

在面向对象编程领域中,开闭原则 (The Open/Closed Principle, OCP) 规定“软件中的对象(类,模块,函数等等)应该对于扩展是开放的,但是对于修改是封闭的,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。

简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。

工厂方法

现在需要对系统进行修改,不使用统一的FruitFactory来统一生成水果,而专门交给对应的工厂子类来完成。首先定义一个抽象的水果工厂,然后定义具体的水果工厂来生成Apple、Banana、Orange等,她们实现了抽象工厂定义的接口,这种抽象化的结果可以不修改具体工厂类的前提下引进新的水果,就符合了开闭原则。

定义

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成。

factory-method-uml-1

代码

工厂:

 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
package factory_method

import "errors"

type FruitFactory interface {
	CreateFruit() Fruit
}

type AppleFruitFactory struct {
	name string
}

func (f *AppleFruitFactory) CreateFruit() Fruit {
	return &Apple{name: f.name, type_: "apple"}
}

type BananaFruitFactory struct {
	name string
}

func (f *BananaFruitFactory) CreateFruit() Fruit {
	return &Banana{name: f.name, type_: "banana"}
}

type OrangeFruitFactory struct {
	name string
}

func (f *OrangeFruitFactory) CreateFruit() Fruit {
	return &Orange{name: f.name, type_: "orange"}
}

func NewFruitFactory(name string) (FruitFactory, error) {
	switch name {
	case "apple":
		return &AppleFruitFactory{name: name}, nil
	case "banana":
		return &BananaFruitFactory{name: name}, nil
	case "orange":
		return &OrangeFruitFactory{name: name}, nil
	}
	return nil, errors.New("not supported")
}

单元测试

 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
package factory_method

import (
	"testing"
)

func TestNewFruitFactory(t *testing.T) {
	type args struct {
		name string
	}
	tests := []struct {
		name  string
		args  args
		want  string
		want1 string
	}{
		{
			name:  "apple",
			args:  struct{ name string }{name: "apple"},
			want:  "apple",
			want1: "apple",
		},
		{
			name:  "orange",
			args:  struct{ name string }{name: "orange"},
			want:  "orange",
			want1: "orange",
		}, {
			name:  "banana",
			args:  struct{ name string }{name: "banana"},
			want:  "banana",
			want1: "banana",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, _ := NewFruitFactory(tt.args.name)
			if got.CreateFruit().Type() != tt.want1 {
				t.Errorf("NewFruitFactory() got = %v, want1 %v", got.CreateFruit().Type(), tt.want1)
			}
			if got.CreateFruit().Name() != tt.want {
				t.Errorf("NewFruitFactory() got = %v, want %v", got.CreateFruit().Type(), tt.want)
			}
		})
	}
}

优点:

  • 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  • 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
  • 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

缺点:

  • 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  • 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

抽象工厂方法

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。

抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。

定义

抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。