1 意图
抽象工厂(abstract factory)
模式让你创建一系列相关的对象,而无需指定具体类型。
2 问题
开发家具商店应用时,代码含有一些类:
- 产品系列:如
Chair
、Sofa
、CoffeeTable
- 产品系列的变体:比如说,可以有
Modern
、Victorian(维多利亚)
、ArtDeco(艺术风装饰)
等风格的Chair
、Sofa
、CoffeeTable
。
- 需要能够单独生成每种家具对象,这样才能确保各种家具风格一致。
- 不希望添加新产品时修改已有代码。家具生产商的产品目录更新很频繁,不可能每次目录更新时都修改核心代码。
3 解决方案
- 抽象工厂模式建议为系列中的每一类产品明确声明接口(如
Chair
、Sofa
、CoffeeTable
) - 确保产品的所有变体都实现了这些接口。例如,所有风格的椅子都必须实现
Chair
接口;所有风格的咖啡桌都必须实现CoffeeTable
接口。
- 然后,声明抽象工厂,必须包含系列中所有产品类别的构造方法。例如,
createChair
、createSofa
、createCoffeeTable
。这些方法必须返回抽象的产品类型,即第一步声明的接口(Chair
、Sofa
、CoffeeTable
)。
- 处理产品变体:为产品系列的每种变体,创建基于抽象工厂接口的具体工厂类。例如,
ModernFurnitureFactory(现代家具工厂)
只能创建Modern
风格的产品,即ModernChair
、ModernSofa
、ModernCoffeeTable
。 - 客户端代码通过抽象的工厂接口来创建产品。无需修改客户端代码,就可以向客户端返回不同类型的工厂类,从而让客户端收到不同类型的产品变体。
客户端无需了解工厂类(只需要调用工厂的createChair()
方法),不用了解工厂类创建出的椅子类型,只需要知道椅子实现了sitOn()
方法就可以了。
- 抽象产品(abstract product):构成产品系列的一组不同,但相关的产品接口(椅子、沙发、咖啡桌)。
- 具体产品(concrete product):抽象产品的多种不同类型的实现(现代/维多利亚风格的椅子/沙发),必须实现相应的抽象产品接口(椅子/沙发)。
- 抽象工厂(abstract factory):声明一组创建各种抽象产品的方法。
- 具体工厂(concrete factory):实现抽象工厂接口。每个具体工厂对应特定的产品变体系列(如现代风格),只创建这种产品变体。
- 虽然具体工厂创建的是具体产品,但是构建方法的返回类型是相应的抽象产品。这样,使用工厂类的客户端就不会与工厂返回的特定产品变体耦合(客户端得到的是抽象产品类型,不知道具体类型是什么)。客户端只需要通过抽象接口(抽象工厂、抽象产品)创建和使用产品,不与具体工厂/产品耦合。
5 伪代码
// 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列
// 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。系列产
// 品可有多个变体,但不同变体的产品不能搭配使用。
interface GUIFactory is
method createButton():Button
method createCheckbox():Checkbox
// 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配
// 使用。具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进
// 行实例化。
class WinFactory implements GUIFactory is
method createButton():Button is
return new WinButton()
method createCheckbox():Checkbox is
return new WinCheckbox()
// 每个具体工厂中都会包含一个相应的产品变体。
class MacFactory implements GUIFactory is
method createButton():Button is
return new MacButton()
method createCheckbox():Checkbox is
return new MacCheckbox()
// 系列产品中的特定产品必须有一个基础接口。所有产品变体都必须实现这个接口。
interface Button is
method paint()
// 具体产品由相应的具体工厂创建。
class WinButton implements Button is
method paint() is
// 根据 Windows 样式渲染按钮。
class MacButton implements Button is
method paint() is
// 根据 macOS 样式渲染按钮
// 这是另一个产品的基础接口。所有产品都可以互动,但是只有相同具体变体的产
// 品之间才能够正确地进行交互。
interface Checkbox is
method paint()
class WinCheckbox implements Checkbox is
method paint() is
// 根据 macOS 样式渲染复选框。
class MacCheckbox implements Checkbox is
method paint() is
// 根据 macOS 样式渲染复选框。
// 客户端代码仅通过抽象类型(GUIFactory、Button 和 Checkbox)使用工厂
// 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
class Application is
private field factory: GUIFactory
private field button: Button
constructor Application(factory: GUIFactory) is
this.factory = factory
method createUI() is
this.button = factory.createButton()
method paint() is
button.paint()
// 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初
// 始化阶段)。
class ApplicationConfigurator is
method main() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
factory = new WinFactory()
else if (config.OS == "Mac") then
factory = new MacFactory()
else
throw new Exception("错误!未知的操作系统。")
Application app = new Application(factory)
6 应用场景
- 代码需要与多个不同系列的相关产品交互,但无法提前得知相关信息,或者出于对未来扩展性的考虑,不希望代码基于具体的产品类型,这种情况下,可以使用抽象工厂模式。抽象工厂提供了接口,可以创建每个系列的产品。只要通过该接口创建对象,就不会生成与已有产品类型不一致的产品。
- 如果有一组基于抽象方法的类,且主要功能因为抽象方法而变得不明确,则可以考虑使用抽象工厂模式。设计良好的程序中,每个类仅负责一件事。如果一个类与多种类型的产品交互,就可以考虑将工厂方法抽取到独立的工厂类,或者具备完整功能的抽象工厂类中。
7 实现方式
- 以不同的产品类型(椅子、沙发、咖啡桌)和变体(现代、维多利亚)为维度绘制矩阵。
- 为所有产品声明抽象产品接口,让所有具体产品类型实现这些接口。
- 声明抽象工厂接口,为每种抽象产品提供一个构建方法。
- 为每种产品变体实现一个具体工厂类。
- 编写初始化代码,根据应用程序配置或者当前环境,初始化某个具体工厂类,将该工厂对象传递给需要创建产品的类。
- 找出代码中所有对产品构造函数的直接调用,替换为对工厂对象中相应构建方法的调用。
8 优点和缺点
- 可以确保同一个工厂生成的产品是相互匹配的(都是现代风格,或者都是维多利亚风格)。
- 可以避免客户端与具体产品代码耦合。
- 单一职责原则:将生成产品的代码抽取到单个位置,让代码容易维护。
- 开闭原则:向应用程序引入新产品变体时,无需修改客户端代码。
- 变得复杂:使用该模式需要引入众多接口和类,代码可能会比之前更加复杂。
9 与其他模式的关系
- 许多设计工作初期使用工厂方法模式,随后演化成使用抽象工厂模式、原型模式、生成器模式。
- 生成器重点关注如何分步生成(一种)复杂对象,而抽象工厂专门用于生产一系列(多种)相关对象。抽象工厂会马上返回产品(一步构造),生成器则允许在获取产品前执行一些额外的构造步骤(多步构造)。
- 抽象工厂通常基于一组工厂方法,也可以使用原型模式来生成这些类的方法。
- 只需要对客户端代码隐藏子系统创建对象的方式时,可以使用抽象工厂代替外观模式。
- 可以将抽象工厂与桥接模式搭配使用。如果由桥定义的抽象只能与特定实现合作,这一模式搭配就非常有用。这种情况下,抽象工厂可以对这些关系进行封装,对客户端隐藏复杂性。
- 抽象工厂、生成器、原型模式都可以用单例模式实现。
10 代码示例
10.1 抽象产品
10.1.1 抽象产品:iShoe
package main
type iShoe interface {
setLogo(logo string)
setSize(size int)
getLogo() string
getSize() int
}
type shoe struct {
logo string
size int
}
func (s *shoe) setLogo(logo string) {
s.logo = logo
}
func (s *shoe) getLogo() string {
return s.logo
}
func (s *shoe) setSize(size int) {
s.size = size
}
func (s *shoe) getSize() int {
return s.size
}
10.1.2 抽象产品: iShirt
package main
type iShirt interface {
setLogo(logo string)
setSize(size int)
getLogo() string
getSize() int
}
type shirt struct {
logo string
size int
}
func (s *shirt) setLogo(logo string) {
s.logo = logo
}
func (s *shirt) getLogo() string {
return s.logo
}
func (s *shirt) setSize(size int) {
s.size = size
}
func (s *shirt) getSize() int {
return s.size
}
10.2 具体产品
10.2.1 nike
的具体产品
package main
type nikeShoe struct {
shoe
}
type nikeShirt struct{
shirt
}
10.2.2 adidas
的具体产品
package main
type adidasShoe struct {
shoe
}
type adidasShirt struct{
shirt
}
10.3 抽象工厂
package main
import "fmt"
type iSportsFactory interface {
makeShoe() iShoe
makeShirt() iShirt
}
func getSportsFactory(brand string) (iSportsFactory, error) {
if brand == "adidas" {
return &adidas{}, nil
}
if brand == "nike" {
return &nike{}, nil
}
return nil, fmt.Errorf("Wrong brand type passed")
}
10.4 具体工厂
10.4.1 nike
的具体工厂
package main
type nike struct {
}
func (n *nike) makeShoe() iShoe {
return &nikeShoe{
shoe: shoe{
logo: "nike",
size: 14,
},
}
}
func (n *nike) makeShirt() iShirt {
return &nikeShirt{
shirt: shirt{
logo: "nike",
size: 14,
},
}
}
10.4.2 adidas
的具体工厂
package main
type adidas struct {
}
func (a *adidas) makeShoe() iShoe {
return &adidasShoe{
shoe: shoe{
logo: "adidas",
size: 14,
},
}
}
func (a *adidas) makeShirt() iShirt {
return &adidasShirt{
shirt: shirt{
logo: "adidas",
size: 14,
},
}
}
10.5 客户端
package main
import "fmt"
func main() {
adidasFactory, _ := getSportsFactory("adidas")
nikeFactory, _ := getSportsFactory("nike")
nikeShoe := nikeFactory.makeShoe()
nikeShirt := nikeFactory.makeShirt()
adidasShoe := adidasFactory.makeShoe()
adidasShirt := adidasFactory.makeShirt()
printShoeDetails(nikeShoe)
printShirtDetails(nikeShirt)
printShoeDetails(adidasShoe)
printShirtDetails(adidasShirt)
}
func printShoeDetails(s iShoe) {
fmt.Printf("Logo: %s", s.getLogo())
fmt.Println()
fmt.Printf("Size: %d", s.getSize())
fmt.Println()
}
func printShirtDetails(s iShirt) {
fmt.Printf("Logo: %s", s.getLogo())
fmt.Println()
fmt.Printf("Size: %d", s.getSize())
fmt.Println()
}