在23种设计模式中,工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:
1.简单工厂(Simple Factory)
2.工厂方法模式(Factory Method)
3.抽象工厂模式(Abstract Factory)
这三种模式从上到下逐步抽象,并且具有一般性。 还有一种分法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归属一类。两者皆可,这本为使用《JAVA与模式》的分类方法。因此,在本节我们将会学习简单工厂(Simple Factory)和工厂方法模式(Factory Method)。
简单工厂模式在开发中应用的情况也非常多,单其并不是GOF二十三种设计模式之一,最多只能算是工厂方法的一种特殊形式,从设计模式的类型上来说,简单工厂模式是属于创建型模式,又叫静态工厂方法(Static Factory Method)模式。简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品(这些产品类继承自一个父类或接口)类的实例。
假设我是一间只卖绿茶的饮料店,客人买了一杯绿茶时我们会这样做,如下:
// 绿茶 func GreenTeaOrders() GreenTea { greenTea := GreenTea{} greenTea.AddMaterial() // 加料 greenTea.Brew() // 冲泡 greenTea.PouredCup() // 装杯 return greenTea }
但是,如果只卖绿茶已经不能应付客人想多选择的需求,我们就必须增加更多个饮料品项,现在我们增加了红茶供客人选择,如下:
// 绿茶 func GreenTeaOrders() GreenTea { greenTea := GreenTea{} greenTea.AddMaterial() // 加料 greenTea.Brew() // 冲泡 greenTea.PouredCup() // 装杯 return greenTea } // 红茶 func BlackTeaOrders() BlackTea { blackTea := BlackTea{} blackTea.AddMaterial() // 加料 blackTea.Brew() // 冲泡 blackTea.PouredCup() // 装杯 return blackTea }
这样客人就多了红茶可以选择。但是当饮料品继续增加的时候,我的BeverageStores类别就必须加入对应的饮品方法,这些饮品方法内做的事情其实也是一样的,我们可以发现AddMaterial()、Brew()、PouredCup()三个方法都是固定必须处理的事情,这时候我们就可以透过使用接口将这些动作进行封装,所以我们将这三个方法加入接口中,如下:
type IBeverageStores interface { AddMaterial() Brew() PouredCup() }
接着必须将饮料类别实现接口方法后,再调用产生饮料的方法,如下:
// GreenTea,实现了接口IBeverageStores type GreenTea struct {} func (this * GreenTea) AddMaterial() {} func (this * GreenTea) Brew() {} func (this * GreenTea) PouredCup() {} //BlackTea, 实现了接口IBeverageStores type BlackTea struct {} func (this * BlackTea) AddMaterial() {} func (this * BlackTea) Brew() {} func (this * BlackTea) PouredCup() {} func BeverageOrder(beverageType string) (tea IBeverageStores) { if beverageType == "BlackTea" { tea = new(BlackTea) } else if beverageType == "GreenTea" { tea = new(GreenTea) } else { tea = nil } if tea != nil { tea.AddMaterial() tea.Brew() tea.PouredCup() } return }
现在透过回传IBeverageProvide介面的方式,我可以不用再增加多余的饮品方法了,但这时又产生了新的问题,我想到在OO的原则下因遵循[类别应该开放便于扩展,应该关闭禁止修改],这里由于我们使用的是IF ELSE 与new关键字来实例化封装类别,所以当增加新饮品时修改应是不可避免的,然后我们已经知道了哪些地方是必须要修改的,所以我应该将这些地方提出来进行封装,以减少 BeverageStores类别对于饮料类别的依赖性。
我再次调整BeverageOrders类别,将产生实例化的部分提取出到一个新的SimpleBeverageFactory类别中,如下:
// SimpleBeverageFactory type SimpleBeverageFactory struct {} func (this *SimpleBeverageFactory) CreateBeverage(beverageType string) (tea IBeverageStores) { if beverageType == "BlackTea" { tea = new(BlackTea) } else if beverageType == "GreenTea" { tea = new(GreenTea) } else { tea = nil } return }
由以上程序段可以看到SimpleBeverageFactory 类别将产生饮品实例的逻辑置入 CreateBeverage 方法中,透过将产生实例的逻辑区断提出后放置到另一个类别中,我们就可以称此类别为一个工厂类别,由这个类别的CreateBeverage 方法来决定要产生哪一个饮品类别,日后如要增加新饮品类别就由此处修改即可。
接着我们需要调整原本的BeverageStores类别,让此类别能够透过传入对应的工厂类别建立饮品的实例,如下:
// BeverageStores type BeverageStores struct { beverageFactory SimpleBeverageFactory } func NewBeverageStores(beverageFactory SimpleBeverageFactory) * BeverageStores { return &BeverageStores{beverageFactory : beverageFactory} } func (this * BeverageStores) BeverageOrders(beverageType string) { tea := this.beverageFactory.CreateBeverage(beverageType) if tea != nil { tea.AddMaterial() tea.Brew() tea.PouredCup() } }
套用工厂模式前的类别前图如下,BeverageStores类别直接关联了GreenTea类别与BlackTea类别:
而透过界面与面单工厂模式封装后如下,BeverageStores类别切开了与GreenTea类别及BlackTea类别的关联性,让BeverageStroes类别关联于SimpleBeverageFactory工厂类别,SimpleBeverageFactory工厂类别则透过 IBeverageProvide饮品介面切开与饮品类别的关联,降低了各类别的耦合性。
最后让我们来制作饮料给客人吧:P
func main() { beverageStore := NewBeverageStores(SimpleBeverageFactory{}) beverageStore.BeverageOrders("GreenTea") beverageStore.BeverageOrders("BlackTea") }
1.用过使用工厂类,外接不再需要关心如何创造各种具体的产品,只需要提供一个产品的名称作为参数传给工厂,就可以直接得到一个想要的产品对象,并且可以不按接口规范来调用产品对象的所有功能(方法)。
2.容易构造,逻辑简单。
1.细心的朋友肯能早就发现了,这么多if else 判断完全是苦力活啊,如果我有一个新产品要加进来,就要同时添加一个新产品类,并且必须要修改工厂类,再加入一个 else if 分支才可以,这样就违背了开放-关闭原则中的对修改关闭的原则了。 当系统中的具体产品类不断增多的时候,就要不断修改工厂类,对系统维护和扩展不利。
2.一个工厂类中集合了所有的类的实例创建逻辑,违反了高内聚的责任分配原则,将全部的创建逻辑都集中到了一个工厂类当中,因此一般只在很简单的情况下应用,比如当工厂类负责创建的对象比较少时。
上一节说明了简单工厂模式,接下里继续说明工厂方法模式,工厂方法模式主要的定义为:让子类别决定要实体化的类别为何。也就是说当建立产品时你可以自行决定由那个子类来建立实际产品。
简单工厂方法其实与工厂方法有些相似,但是仔细看看,简单工厂是将全部的事情于简单工厂的方法处理完成,而工厂方法却会将要处理的事情交由实际实践的子类别处理,也就是当产品的种类增加的时候在简单工厂的情况下必须要修改简单工厂的方法,而工厂方法模式下只需要多增加一个新产品工厂的子类别去实践,如此就符合【开放封闭】原则,但工厂方法有个缺点就是会产生大量的工厂子类别。
组成工厂方法模式有四个角色,如下:
同样以上一篇的范例为例,在简单工厂时我将建立实例的动作置于简单工厂方法中的CreateBeverage()方法中,如下:
// SimpleBeverageFactory type SimpleBeverageFactory struct {} func (this *SimpleBeverageFactory) CreateBeverage(beverageType string) (tea IBeverageStores) { if beverageType == "BlackTea" { tea = new(BlackTea) } else if beverageType == "GreenTea" { tea = new(GreenTea) } else { tea = nil } return }
但现在饮料店为了满足顾客对于饮料种类的喜好而需要让提供更多不同种类的饮料时,就必须要修改CreateBeverage() 此方法中的判断,添加更多不同种类的饮料物件,如下:
// SimpleBeverageFactory type SimpleBeverageFactory struct {} func (this *SimpleBeverageFactory) CreateBeverage(beverageType string) (tea IBeverageStores) { if beverageType == "BlackTea" { tea = new(BlackTea) } else if beverageType == "GreenTea" { tea = new(GreenTea) } else if pBeverageType == "MilkTea" // 多了奶茶 tea = new(MilkTea) else { tea = nil } return }
由上所展示的代码可以看出为了应付更多种类的饮料,次简单工厂方法开始变得复杂且依赖性提高,并且不管是更改绿茶或红茶的口味都需要动到此方法,所以为了降低此方法的复杂度和依赖性,我们应该将饮料种类进行切割处理。
首先需要建立一个产品的接口,由实现此产品接口的产品类别来建立产品的实例,因此我们需要将绿茶和红茶分别建立各自的工厂方法并且实现产品接口的CreateBeverage 方法。如下:
type IBeverageFactory interface { CreateBeverage() IBeverageProvide } // GreenTeaFactory type GreenTeaFactory struct {} func (this * GreenTeaFactory) CreateBeverage() IBeverageProvide { return new(GreenTea) } // BlackTeaFactory type BlackTeaFactory struct {} func (this * BlackTeaFactory) CreateBeverage() IBeverageProvide { return new(BlackTea) }
将绿茶与红茶切开建立各自的工厂方法后,现在我们如果要再增加奶茶时也不需要动到原本绿茶和红茶的工厂方法了,只需要增加一个实现IBeverageFactory接口的子类别,如下:
// MilkTeaFactory type MilkTeaFactory struct {} func (this * MilkTeaFactory) CreateBeverage() IBeverageProvide { return new(MilkTea) }
接着定义抽象产品角色,通过接口定义产品的共同方法让实现此接口的产品类别去实践,如下:
type IBeverageProvide interface { AddMaterial() Brew() PouredCup() }
当产品的接口定义完成后,就要建立该产品工厂所要创建的产品类别,也就是加入绿茶、红茶产品类别并且实现IBeverageProvide 接口的方法。如下:
// GreenTea,实现了接口IBeverageStores type GreenTea struct {} func (this * GreenTea) AddMaterial() {} func (this * GreenTea) Brew() {} func (this * GreenTea) PouredCup() {} //BlackTea, 实现了接口IBeverageStores type BlackTea struct {} func (this * BlackTea) AddMaterial() {} func (this * BlackTea) Brew() {} func (this * BlackTea) PouredCup() {} //MilkTea, 实现了接口IBeverageStores type MilkTea struct {} func (this * MilkTea) AddMaterial() {} func (this * MilkTea) Brew() {} func (this * MilkTea) PouredCup() {}
经过以上的几个动作后,来看看转变为工厂方法模式后的类别图,如下:
由上图可见,GreenTeaFactory与BlackTeaFactory工厂方法共同实现了IBeverageFactory 接口的CreateBeverage() 方法,在 CreateBeverage() 方法中单纯的实例化了各自的产品类型GreenTea与BlackTea,看的出来实践饮料的做法已经交由各自的工厂方法处理,而当饮料种类增加时只需要加入新的具体工厂角色与具体产品角色,从而也就有降低物件的耦合性了。
最后来呼叫看看执行的语法,如下:
func main() { beverageStore := NewBeverageStores(new(MilkTeaFactory)) beverageStore.BeverageOrders() beverageStore = NewBeverageStores(new(BlackTeaFactory)) beverageStore.BeverageOrders() beverageStore = NewBeverageStores(new(GreenTeaFactory)) beverageStore.BeverageOrders() }
首先,代码结构清晰,封装良好,客户端只需要知道具体的工厂类名称即可(如果命名非常规范,则只需要知道产品的名字就够了),根本不用关心创建对象的复杂过程(实例代码很简单,但实际应用中创建和构造对象的过程可能会非常复杂),减少了模块的耦合性。
其次,符合了开放原则的要求,在新增加新产品的情况下,唔、无需修改现有产品类和工厂类,只要追加新的具体产品类和具体工厂类就够了,系统的拓展新得到了很大的提升。这也弥补了简单工厂模式对修改开放的诟病。
再次,屏蔽了上层调用者和具体产品实现的牵连。 不管产品的实现如何变化,只要接口不边,上层调用都不会随着变化,只要修改相应的产品类或产品工厂类就够了。
最后,工厂方法模式还满足了迪米特原则,依赖倒置原则,里氏替换原则,是典型的解耦框架。