1 意图

抽象工厂(abstract factory)模式让你创建一系列相关的对象,而无需指定具体类型。

abstract-factory-zh.png

2 问题

开发家具商店应用时,代码含有一些类:

  • 产品系列:如ChairSofaCoffeeTable
  • 产品系列的变体:比如说,可以有ModernVictorian(维多利亚)ArtDeco(艺术风装饰)等风格的ChairSofaCoffeeTable

problem-zh.png

  • 需要能够单独生成每种家具对象,这样才能确保各种家具风格一致。

abstract-factory-comic-1-zh.png

  • 不希望添加新产品时修改已有代码。家具生产商的产品目录更新很频繁,不可能每次目录更新时都修改核心代码。

3 解决方案

  • 抽象工厂模式建议为系列中的每一类产品明确声明接口(如ChairSofaCoffeeTable
  • 确保产品的所有变体都实现了这些接口。例如,所有风格的椅子都必须实现Chair接口;所有风格的咖啡桌都必须实现CoffeeTable接口。

solution1.png

  • 然后,声明抽象工厂,必须包含系列中所有产品类别的构造方法。例如,createChaircreateSofacreateCoffeeTable。这些方法必须返回抽象的产品类型,即第一步声明的接口(ChairSofaCoffeeTable)。

solution2.png

  • 处理产品变体:为产品系列的每种变体,创建基于抽象工厂接口的具体工厂类。例如,ModernFurnitureFactory(现代家具工厂)只能创建Modern风格的产品,即ModernChairModernSofaModernCoffeeTable
  • 客户端代码通过抽象的工厂接口来创建产品。无需修改客户端代码,就可以向客户端返回不同类型的工厂类,从而让客户端收到不同类型的产品变体。

abstract-factory-comic-2-zh.png

客户端无需了解工厂类(只需要调用工厂的createChair()方法),不用了解工厂类创建出的椅子类型,只需要知道椅子实现了sitOn()方法就可以了。

  • 谁创建实际的工厂对象:通常应用在初始化阶段创建具体的工厂对象。应用根据配置文件或者环境设置来选择工厂类别。

    4 结构

structure-indexed.png

  1. 抽象产品(abstract product):构成产品系列的一组不同,但相关的产品接口(椅子、沙发、咖啡桌)。
  2. 具体产品(concrete product):抽象产品的多种不同类型的实现(现代/维多利亚风格的椅子/沙发),必须实现相应的抽象产品接口(椅子/沙发)。
  3. 抽象工厂(abstract factory):声明一组创建各种抽象产品的方法。
  4. 具体工厂(concrete factory):实现抽象工厂接口。每个具体工厂对应特定的产品变体系列(如现代风格),只创建这种产品变体。
  5. 虽然具体工厂创建的是具体产品,但是构建方法的返回类型是相应的抽象产品。这样,使用工厂类的客户端就不会与工厂返回的特定产品变体耦合(客户端得到的是抽象产品类型,不知道具体类型是什么)。客户端只需要通过抽象接口(抽象工厂、抽象产品)创建和使用产品,不与具体工厂/产品耦合。

5 伪代码

example.png

  1. // 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列
  2. // 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。系列产
  3. // 品可有多个变体,但不同变体的产品不能搭配使用。
  4. interface GUIFactory is
  5. method createButton():Button
  6. method createCheckbox():Checkbox
  7. // 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配
  8. // 使用。具体工厂方法签名会返回一个抽象产品,但在方法内部则会对具体产品进
  9. // 行实例化。
  10. class WinFactory implements GUIFactory is
  11. method createButton():Button is
  12. return new WinButton()
  13. method createCheckbox():Checkbox is
  14. return new WinCheckbox()
  15. // 每个具体工厂中都会包含一个相应的产品变体。
  16. class MacFactory implements GUIFactory is
  17. method createButton():Button is
  18. return new MacButton()
  19. method createCheckbox():Checkbox is
  20. return new MacCheckbox()
  21. // 系列产品中的特定产品必须有一个基础接口。所有产品变体都必须实现这个接口。
  22. interface Button is
  23. method paint()
  24. // 具体产品由相应的具体工厂创建。
  25. class WinButton implements Button is
  26. method paint() is
  27. // 根据 Windows 样式渲染按钮。
  28. class MacButton implements Button is
  29. method paint() is
  30. // 根据 macOS 样式渲染按钮
  31. // 这是另一个产品的基础接口。所有产品都可以互动,但是只有相同具体变体的产
  32. // 品之间才能够正确地进行交互。
  33. interface Checkbox is
  34. method paint()
  35. class WinCheckbox implements Checkbox is
  36. method paint() is
  37. // 根据 macOS 样式渲染复选框。
  38. class MacCheckbox implements Checkbox is
  39. method paint() is
  40. // 根据 macOS 样式渲染复选框。
  41. // 客户端代码仅通过抽象类型(GUIFactory、Button 和 Checkbox)使用工厂
  42. // 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
  43. class Application is
  44. private field factory: GUIFactory
  45. private field button: Button
  46. constructor Application(factory: GUIFactory) is
  47. this.factory = factory
  48. method createUI() is
  49. this.button = factory.createButton()
  50. method paint() is
  51. button.paint()
  52. // 程序会根据当前配置或环境设定选择工厂类型,并在运行时创建工厂(通常在初
  53. // 始化阶段)。
  54. class ApplicationConfigurator is
  55. method main() is
  56. config = readApplicationConfigFile()
  57. if (config.OS == "Windows") then
  58. factory = new WinFactory()
  59. else if (config.OS == "Mac") then
  60. factory = new MacFactory()
  61. else
  62. throw new Exception("错误!未知的操作系统。")
  63. Application app = new Application(factory)

6 应用场景

  • 代码需要与多个不同系列的相关产品交互,但无法提前得知相关信息,或者出于对未来扩展性的考虑,不希望代码基于具体的产品类型,这种情况下,可以使用抽象工厂模式。抽象工厂提供了接口,可以创建每个系列的产品。只要通过该接口创建对象,就不会生成与已有产品类型不一致的产品。
  • 如果有一组基于抽象方法的类,且主要功能因为抽象方法而变得不明确,则可以考虑使用抽象工厂模式。设计良好的程序中,每个类仅负责一件事。如果一个类与多种类型的产品交互,就可以考虑将工厂方法抽取到独立的工厂类,或者具备完整功能的抽象工厂类中。

    7 实现方式

  1. 以不同的产品类型(椅子、沙发、咖啡桌)和变体(现代、维多利亚)为维度绘制矩阵。
  2. 为所有产品声明抽象产品接口,让所有具体产品类型实现这些接口。
  3. 声明抽象工厂接口,为每种抽象产品提供一个构建方法。
  4. 为每种产品变体实现一个具体工厂类。
  5. 编写初始化代码,根据应用程序配置或者当前环境,初始化某个具体工厂类,将该工厂对象传递给需要创建产品的类。
  6. 找出代码中所有对产品构造函数的直接调用,替换为对工厂对象中相应构建方法的调用。

    8 优点和缺点

  • 可以确保同一个工厂生成的产品是相互匹配的(都是现代风格,或者都是维多利亚风格)。
  • 可以避免客户端与具体产品代码耦合。
  • 单一职责原则:将生成产品的代码抽取到单个位置,让代码容易维护。
  • 开闭原则:向应用程序引入新产品变体时,无需修改客户端代码。
  • 变得复杂:使用该模式需要引入众多接口和类,代码可能会比之前更加复杂。

9 与其他模式的关系

  • 许多设计工作初期使用工厂方法模式,随后演化成使用抽象工厂模式原型模式生成器模式
  • 生成器重点关注如何分步生成(一种)复杂对象,而抽象工厂专门用于生产一系列(多种)相关对象。抽象工厂会马上返回产品(一步构造),生成器则允许在获取产品前执行一些额外的构造步骤(多步构造)。
  • 抽象工厂通常基于一组工厂方法,也可以使用原型模式来生成这些类的方法。
  • 只需要对客户端代码隐藏子系统创建对象的方式时,可以使用抽象工厂代替外观模式
  • 可以将抽象工厂与桥接模式搭配使用。如果由桥定义的抽象只能与特定实现合作,这一模式搭配就非常有用。这种情况下,抽象工厂可以对这些关系进行封装,对客户端隐藏复杂性。
  • 抽象工厂、生成器原型模式都可以用单例模式实现。

10 代码示例

03 创建型模式2:抽象工厂 - 图9

10.1 抽象产品

10.1.1 抽象产品:iShoe

  1. package main
  2. type iShoe interface {
  3. setLogo(logo string)
  4. setSize(size int)
  5. getLogo() string
  6. getSize() int
  7. }
  8. type shoe struct {
  9. logo string
  10. size int
  11. }
  12. func (s *shoe) setLogo(logo string) {
  13. s.logo = logo
  14. }
  15. func (s *shoe) getLogo() string {
  16. return s.logo
  17. }
  18. func (s *shoe) setSize(size int) {
  19. s.size = size
  20. }
  21. func (s *shoe) getSize() int {
  22. return s.size
  23. }

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()
}