对象创建模式
通过“对象创建” 模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。
典型模式:
- Factory Method
- Abstract Factory
- Prototype
- Builder
Factory Method工厂方法
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
动机:
- 在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
- 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
例子
同样以文件分割器为例,有一个较大的文件,想要被分成多个小块的文件。
package factory_method
import "strconv"
type FileSplitter struct {
path string
nums int
}
func (s *FileSplitter) split() {
//do something
//split a big file to nums small files
for i := 0; i < s.nums; i++ {
}
}
type TextBox struct {
text string
}
func (box *TextBox) getText() string {
return box.text
}
type MainForm struct {
txtFilePath *TextBox
txtFileNumber *TextBox
}
func (main *MainForm) Button1_Click() {
filePath := main.txtFilePath.getText()
number, _ := strconv.Atoi(main.txtFileNumber.getText())
splitter := FileSplitter{filePath, number}
splitter.split()
}
当点击后就会执行分隔。
由于,FileSplitter
可能是想要变化的,在button
点击里被写死了,不能改变了。假设有BinarySplitter
, TxtSplitter
,PictureSplitter
,VideoSplitter
,这么多分割器,
首先想要抽象出一个分割器
// 分割器的抽象
type ISplitter interface {
split()
}
具体实现
// 二进制文件分割器
type BinarySplitter struct {
ISplitter //继承
}
func (bin *BinarySplitter) split() {
fmt.Println("BinarySplitter split")
}
// Txt文件分割器
type TxtSplitter struct {
ISplitter //继承
}
func (txt *TxtSplitter) split() {
fmt.Println("TxtSplitter split")
}
// Picture分割器
type PictureSplitter struct {
ISplitter //继承
}
func (pic *PictureSplitter) split() {
fmt.Println("PictureSplitter split")
}
// Video分割器
type VideoSplitter struct {
ISplitter //继承
}
func (video *VideoSplitter) split() {
fmt.Println("VideoSplitter split")
}
然后我们可以将实例化改变,
func (main *MainForm) Button1_Click() {
_ = main.txtFilePath.getText()
_, _ = strconv.Atoi(main.txtFileNumber.getText())
var splitter ISplitter
splitter = &BinarySplitter{}
splitter.split()
}
左边确实是抽象依赖了,但是BinarySplitter
仍然是依赖具体,打破了依赖倒置原则。
于是如何去除BinarySplitter
这样的具体依赖就是创建模式要解决的问题。这是面向接口编程的必然需求。
对象创建的方法,不用new
用一个方法来返回我们需要的对象。
// 分割工厂
type SplitterFactory struct {
}
func (sf *SplitterFactory) CreateSplitter() ISplitter {
return &BinarySplitter{}
}
使用
func (main *MainForm) Button1_Click() {
_ = main.txtFilePath.getText()
_, _ = strconv.Atoi(main.txtFileNumber.getText())
var splitter ISplitter
var factory SplitterFactory
splitter = factory.CreateSplitter()
splitter.split()
}
但是实际上由于CreateSplitter
(编译时)依赖于BinarySplitter
,由于依赖的传递性,所以仍然没有解决问题。
考虑将具体实现交给后续调用,设计为接口
// 分割工厂
type SplitterFactory interface {
CreateSplitter() ISplitter
}
func (main *MainForm) Button1_Click() {
_ = main.txtFilePath.getText()
_, _ = strconv.Atoi(main.txtFileNumber.getText())
var splitter ISplitter
var factory SplitterFactory
splitter = factory.CreateSplitter()
splitter.split()
}
那么SplitterFactory
又赋值什么呢?
通过工厂创建具体的对象,
// 具体工厂
// 二进制文件分割器工厂
type BinarySplitterFactory struct {
SplitterFactory //继承
}
func (bin *BinarySplitterFactory) CreateSplitter() ISplitter {
return &BinarySplitter{}
}
// Txt文件分割器工厂
type TxtSplitterFactory struct {
SplitterFactory //继承
}
func (txt *TxtSplitterFactory) CreateSplitter() ISplitter {
return &TxtSplitter{}
}
// Picture分割器工厂
type PictureSplitterFactory struct {
SplitterFactory //继承
}
func (pic *PictureSplitterFactory) CreateSplitter() ISplitter {
return &PictureSplitter{}
}
// Video分割器工厂
type VideoSplitterFactory struct {
SplitterFactory //继承
}
func (video *VideoSplitterFactory) CreateSplitter() ISplitter {
return &VideoSplitter{}
}
那么SplitterFactory
又什么时候赋值呢?
一般是构造具体的MainForm
的时候
type MainForm struct {
factory SplitterFactory
}
func NewMainForm(factory SplitterFactory) *MainForm {
return &MainForm{factory: factory}
}
func (main *MainForm) Button1_Click() {
splitter := main.factory.CreateSplitter()
splitter.split()
}
这就实现了多态实例化的功能。
问题,将来实际上还是有具体的实现。
答:MainForm
里不依赖了,具体不由我来实现了,而把这个变化放到其他对方去了。
使用
func TestMainForm_Button1_Click(t *testing.T) {
//真正实例化的地方,
main := NewMainForm(&PictureSplitterFactory{})
main.Button1_Click()
}
类图
总结
书上类图:
总结:
- Factory Method模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
- Factory Method模式通过面向对象的手法,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
- Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。
抽象工厂
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。
动机
- 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
- 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作”的紧耦合?
对不同的数据库需要不同的实现,SqlConnection
,SqlCommand
,想法是先抽象
如果按照工厂方法,解决一个类对应一个工厂。一个具体实现一个具体工厂类。
package abstract_factory
// 数据库访问有关的接口
type IDBConnection interface {
}
// 工厂
type IDBConnectionFactory interface {
CreateDBConnection() IDBConnection
}
type IDBCommand interface {
SetConnection(connection IDBConnection)
}
type IDBCommandFactory interface {
CreateDBCommand() IDBCommand
}
type IDBReader interface {
}
type IDBReaderFactory interface {
CreateDBReader() IDBReader
}
// 对SqlServer的支持
type SqlConnection struct {
IDBConnection
}
// 具体工厂
type SqlConnectionFactory struct {
IDBConnectionFactory
}
type SqlCommand struct {
IDBCommand
}
type SqlCommandFactory struct {
IDBCommandFactory
}
type SqlDataReader struct {
IDBReader
}
type SqlDataReaderFactory struct {
IDBReaderFactory
}
// oracle 的支持
type OracleConnection struct {
IDBConnection
}
type OracleConnectionFactory struct {
IDBConnectionFactory
}
type OracleCommand struct {
IDBCommand
}
type OracleCommandFactory struct {
IDBCommandFactory
}
type OracleDataReader struct {
IDBReader
}
type OracleDataReaderFactory struct {
IDBReaderFactory
}
type Employee struct{}
type EmployeeDAO struct {
dbConnectionFactory IDBConnectionFactory //指针
dbCommandFactory IDBCommandFactory
dbReaderFactory IDBReaderFactory
}
func (dao *EmployeeDAO) GetEmployees() []Employee {
connection := dao.dbConnectionFactory.CreateDBConnection()
command := dao.dbCommandFactory.CreateDBCommand()
command.SetConnection(connection)
return nil
}
看上去是解决了,但实际上没有体现依赖性,如果是 SQL
的Connection
,传给Oracle
的Command
,肯定会报错。
想法是把IDBConnectionFactory
,IDBCommandFactory
,IDBReaderFactory
把这三个工厂变成一个工厂,一一对应,相干的
package abstract_factory
// 数据库访问有关的接口
type IDBConnection interface {
}
type IDBCommand interface {
SetConnection(connection IDBConnection)
ExecuteReader() IDBReader
}
type IDBReader interface {
Read() bool
}
type IDBFactory interface {
CreateDBConnection() IDBConnection
CreateDBCommand() IDBCommand
CreateDBReader() IDBReader
}
对于具体的数据库
package abstract_factory
import "fmt"
// 对SqlServer的支持
type SqlConnection struct {
IDBConnection
}
type SqlCommand struct {
IDBCommand
}
type SqlDataReader struct {
IDBReader
}
type SqlDBFactory struct {
IDBFactory
}
// oracle 的支持
type OracleConnection struct {
IDBConnection
}
type OracleCommand struct {
IDBCommand
}
type OracleDataReader struct {
IDBReader
}
func (reader *OracleDataReader) Read() bool {
fmt.Println("OracleDataReader Read")
return true
}
type OracleDBFactory struct {
IDBFactory
}
// 为工厂实现具体方法
func (factory *OracleDBFactory) CreateDBConnection() IDBConnection {
return &OracleConnection{}
}
func (factory *OracleDBFactory) CreateDBCommand() IDBCommand {
return &OracleCommand{}
}
func (factory *OracleDBFactory) CreateDBReader() IDBReader {
return &OracleDataReader{}
}
type Employee struct{}
type EmployeeDAO struct {
dbFactory IDBFactory
}
// 实例化DAO
func NewEmployeeDAO(factory IDBFactory) *EmployeeDAO {
return &EmployeeDAO{dbFactory: factory}
}
func (dao *EmployeeDAO) GetEmployees() []Employee {
connection := dao.dbFactory.CreateDBConnection() //同一个工厂可以保证创建的对象是同一类
command := dao.dbFactory.CreateDBCommand()
command.SetConnection(connection) //关联
reader := command.ExecuteReader() //关联
for reader.Read() {
}
return nil
}
总结
类图
- 如果没有应对“多系列对象构建”的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂完全可以。
- “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系。不同系列的对象之间不能相互依赖。
- Abstract Factory模式主要在于应对“新系列”的需求变动。其缺点在于难以应对“新对象”的需求变动。
原型模式
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象
动机
- 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
- 如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?
复杂对象,只要原型被创建了,以后需要原型都可以通过克隆实现。
而工厂方法如果创建过于复杂,不能解决中间状态,则用原型模式。
解决
原型模式将工厂方法抽取到自身,同时重命名为clone
,是深拷贝!深拷贝可以通过序列化反序列化实现。
原型不是直接用的,而是通过clone
创建新的对象供使用,
package prototype
import "fmt"
// 分割器的抽象
type ISplitter interface {
split()
clone() ISplitter //通过原型克隆自己
}
// 二进制文件分割器
type BinarySplitter struct {
ISplitter //继承
count int
}
func (bin *BinarySplitter) split() {
bin.count++
fmt.Println("BinarySplitter split,", bin.count)
}
func (bin *BinarySplitter) clone() ISplitter {
fmt.Println("BinarySplitter clone")
// 通过原型克隆自己
// 创建一个新的BinarySplitter实例,并复制原对象的属性
clone := &BinarySplitter{}
clone.ISplitter = bin.ISplitter
clone.count = bin.count
return clone
}
type MainForm struct {
prototype ISplitter //原型对象
}
func NewMainForm(prototype ISplitter) *MainForm {
return &MainForm{prototype: prototype}
}
func (main *MainForm) Button1_Click() {
// 通过原型对象创建新的分割器对象
splitter := main.prototype.clone()
splitter.split()
}
总结
类图
- Prototype: 模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。
- Prototype: 模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地动态创建“拥有某些稳定接口”的新对象一一所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方Clone。
- Prototype: 模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。
构建器Builder
将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)
动机
- 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将它们组合在一起的算法却相对稳定。
- 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?
问题
假设有一个游戏,创建房屋,
不管怎么样的材质地基,墙壁,天花板,门、窗是需要的,流程是固定的,但具体的材料可能不一样,
package builder
type BuildPart interface {
BuildPart01()
BuildPart02()
BuildPart03() bool
BuildPart04()
BuildPart05()
}
type House struct {
BuildPart //是House的虚方法,必须要实现
flag bool
}
// 真正的构造方法
func (this *House) Init() {
this.BuildPart01()
for i := 0; i < 4; i++ {
this.BuildPart02()
}
flag := this.BuildPart03()
if flag {
this.BuildPart04()
}
this.BuildPart05()
}
问题是由于子步骤是变化的,但是流程是固定的,所以抽离了BuildPart*
。与模板方法非常像。
package builder
type BuildPart interface {
BuildPart01()
BuildPart02()
BuildPart03() bool
BuildPart04()
BuildPart05()
}
// 房子基类
type House struct {
BuildPart //是House的虚方法,必须要实现
}
// 真正的构造方法
func (this *House) Init() {
this.BuildPart01()
for i := 0; i < 4; i++ {
this.BuildPart02()
}
flag := this.BuildPart03()
if flag {
this.BuildPart04()
}
this.BuildPart05()
}
// 石头房子
type StoneHouse struct {
House //继承
}
// 实例化石头房子
func NewStoneHouse() *StoneHouse {
stone := &StoneHouse{}
stone.BuildPart = stone //实现了虚方法
return stone
}
func (this *StoneHouse) BuildPart01() {
println("StoneHouse BuildPart01")
}
func (this *StoneHouse) BuildPart02() {
println("StoneHouse BuildPart02")
}
func (this *StoneHouse) BuildPart03() bool {
println("StoneHouse BuildPart03")
return true
}
func (this *StoneHouse) BuildPart04() {
println("StoneHouse BuildPart04")
}
func (this *StoneHouse) BuildPart05() {
println("StoneHouse BuildPart05")
}
使用
func TestHouse_Init(t *testing.T) {
stoneHouse := NewStoneHouse()
stoneHouse.Init()
}
由于有时候一个类本身功能就比较复杂,于是将Builder
进一步抽离,
package builder
// 房子基类
type House struct {
// 各种字段
door string
floor string
wall string
roof string
light string
}
type BuildPart interface {
BuildPart01()
BuildPart02()
BuildPart03() bool
BuildPart04()
BuildPart05()
GetResult() *House
}
// 负责构造房子的Builder
type HouseBuilder struct {
pHouse BuildPart // 是BuildPart的虚方法,必须要实现
}
func (hb *HouseBuilder) GetResult() *House {
return hb.pHouse.GetResult()
}
// 此前Init的具体实现,改名为Construct
type HouseDirector struct {
pHouseBuilder BuildPart
}
// 构造器
func NewHouseDirector() *HouseDirector {
return &HouseDirector{}
}
func (hd *HouseDirector) SetBuilder(builder BuildPart) {
hd.pHouseBuilder = builder
}
func (hd *HouseDirector) Construct() *House {
hd.pHouseBuilder.BuildPart01()
for i := 0; i < 4; i++ {
hd.pHouseBuilder.BuildPart02()
}
flag := hd.pHouseBuilder.BuildPart03()
if flag {
hd.pHouseBuilder.BuildPart04()
}
hd.pHouseBuilder.BuildPart05()
return hd.pHouseBuilder.GetResult()
}
// 石头房子
type StoneHouse struct {
House // 继承
}
// 实例化石头房子
func NewStoneHouse() *StoneHouse {
return &StoneHouse{}
}
// 石头房子Builder
type StoneHouseBuilder struct {
HouseBuilder // 继承
pHouse *StoneHouse
}
// 实例化
func NewStoneHouseBuilder(house *StoneHouse) *StoneHouseBuilder {
stone := &StoneHouseBuilder{pHouse: house}
return stone
}
func (shb *StoneHouseBuilder) BuildPart01() {
println("StoneHouse BuildPart01")
shb.pHouse.wall = "Stone"
}
func (shb *StoneHouseBuilder) BuildPart02() {
println("StoneHouse BuildPart02")
shb.pHouse.door = "Stone"
}
func (shb *StoneHouseBuilder) BuildPart03() bool {
println("StoneHouse BuildPart03")
shb.pHouse.roof = "Stone"
return true
}
func (shb *StoneHouseBuilder) BuildPart04() {
println("StoneHouse BuildPart04")
shb.pHouse.floor = "Stone"
}
func (shb *StoneHouseBuilder) BuildPart05() {
println("StoneHouse BuildPart05")
shb.pHouse.light = "Stone"
}
func (shb *StoneHouseBuilder) GetResult() *House {
return &shb.pHouse.House
}
这个版本太复杂,使用第一个版本也可以,使用
func TestHouse(t *testing.T) {
director := NewHouseDirector()
stoneHouseBuilder := NewStoneHouseBuilder(NewStoneHouse())
director.SetBuilder(stoneHouseBuilder)
house := director.Construct()
fmt.Printf("%+v\n", house)
}
总结
- Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中“分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化。
- 变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
- 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#) 。