创建型设计模式-抽象工厂

什么是抽象工厂

它能创建一系列相关的对象,而无需指定其具体类

  1. 抽象产品:为构成系列产品的一组不同但 相关的产品声明接口

  2. 具体产品(Concrete Product)是抽象产品的多种不同类型实 现。所有变体(维多利亚/现代)都必须实现相应的抽象产品 (椅子/沙发

  3. 抽象工厂(Abstract Factory)接口声明了一组创建各种抽象 产品的方法

  4. 具体工厂(Concrete Factory)实现抽象工厂的构建方法。每 个具体工厂都对应特定产品变体,且仅创建此种产品变体。

  5. 尽管具体工厂会对具体产品进行初始化,其构建方法签名必须返回相应的抽象产品。这样,使用工厂类的客户端代码就 不会与工厂创建的特定产品变体耦合。客户端(Client)只 需通过抽象接口调用工厂和产品对象,就能与任何具体工厂/ 产品变体交互

Example

抽象工厂接口

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

具体工厂A

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,
},
}
}

具体工厂B

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,
},
}
}

抽象产品A

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
}

具体产品A1

package main

type AdidasShoe struct {
Shoe
}

具体产品A2

package main

type NikeShoe struct {
Shoe
}

抽象产品B

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
}
adidasShirt.go: 具体产品
package main

type AdidasShirt struct {
Shirt
}

具体产品B1

package main

type NikeShirt struct {
Shirt
}

客户端

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

适用场景

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

优缺点

优点

  • 确保同一工厂生成的产品相互匹配

  • 避免客户端和具体产品代码的耦合

  • 单一职责原则。你可以将产品生成代码抽取到同一位置,使 得代码易于维护

  • 开闭原则。向应用程序中引入新产品变体时,你无需修改客 户端代码

缺点

  • 由于采用该模式需要向应用中引入众多接口和类,代码可能 会比之前更加复杂。

与其他模式的关系

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