DAY 16:Abstract Factory Pattern,膜拜那个工厂之神吧!

工厂模式主要有三种不同的实作:

  • Simple Factory Pattern
  • Factory Method Pattern
  • Abstract Factory Pattern

这三种实作由简单到复杂,今天会介绍 Factory Method Pattern 延伸的 Abstract Factory Pattern

(文创系的看到我画的 UML 後画了一张他理解图,恩...真滴棒XD)

什麽是 Abstract Factory Pattern?

将性质相同产品集合起来由一个工厂生产

优点:

  • 符合开闭原则,与 Factory Method Pattern 相同,在新增产品时不必修改 function 实作(对修改封闭),但可以透过不同工厂来新增(对扩充开放)
  • 可避免 Factory Method Pattern 一个产品就要一个工厂导致工厂过多的问题

缺点:

  • 由於工厂为一群产品的集合,如果你要新增一个产品,必须在每个工厂都新增此产品,成本会很大。这称为开闭原则的倾斜性,即「新增有一群产品的工厂很简单(倾向此目的),但新增一个产品至集合很困难」

问题情境

我们修改昨天的情境,使用者想要有个「游戏房」,使用者除了要游戏主机,还要游戏产品。

  • 要生产游戏主机与电视给使用者,使用者不需要知道「如何生产 CPU、显示晶片、游戏制作」,使用者只需要获得此产品就行。
  • 买 Sony 系列的主机,就会买 PS 相关的游戏;买任天堂系列的主机,就会买 Switch 相关的游戏。

解决方式

套用到上方的 UML 图後如下:

我们的工厂 interface 不像昨天只是单纯生产游戏主机,而是要生产游戏屋相关的产品,所以改名为GameRoomFactory{},里头.GameMachineFactory()可以生产主机,.GameFactory()可以生产游戏。

而工厂生产的主机与游戏,也分别抽象出两个 interfaceGameGameMachine

使用者只管「把主机抱回家(.GameMachineFactory())」与「取得游戏来玩(.GameFactory())」,实际会获得什麽主机与游戏完全看使用者选择 Sony 还是任天堂。所以User()会只依赖於GameRoomFactory{}interface,并不直接呼叫SonyFactory{}NintendoFactory{}

选择品牌来决定要什麽样的产品集群,这样的集群又被称为「产品族」,在抽象工厂常用此名词称呼。

程序码如下:

(相关的 code 在Github - go-design-patterns)

package main

import "fmt"

// 定义抽象工厂
type GameRoomFactory interface {
	GameMachineFactory() GameMachine
	GameFactory() Game
}

// 定义抽象产品
type Game interface {
	Start()
}

type GameMachine interface {
	PlayGame()
}

// 实作产品
type PS5 struct{}

func (p PS5) PlayGame() {
	fmt.Println("loading cd...play!")
}
func (p PS5) addCDMachine() {
	fmt.Println("adding cd machine...done!")
}
func (p PS5) addCPU() {
	fmt.Println("adding cpu...done!")
}
func (p PS5) addGPU() {
	fmt.Println("adding gpu...done!")
}

type GameFinalFantasy struct{}

func (s *GameFinalFantasy) build() {
	fmt.Println("build game...done!")
}
func (s *GameFinalFantasy) Start() {
	fmt.Println("start game...done!")
}

type Switch struct{}

func (s Switch) PlayGame() {
	fmt.Println("loading cd...play!")
}
func (s Switch) addCDMachine() {
	fmt.Println("adding cd machine...done!")
}
func (s Switch) addCPU() {
	fmt.Println("adding cpu...done!")
}
func (s Switch) addGPU() {
	fmt.Println("adding gpu...done!")
}

type GameMario struct{}

func (s *GameMario) build() {
	fmt.Println("build game...done!")
}
func (s *GameMario) Start() {
	fmt.Println("start game...done!")
}

// 实作工厂
type SonyFactory struct{}

func (f *SonyFactory) GameMachineFactory() GameMachine {
	ps5 := &PS5{}
	ps5.addCDMachine()
	ps5.addCPU()
	ps5.addGPU()
	return &PS5{}
}

func (f *SonyFactory) GameFactory() Game {
	game := &GameFinalFantasy{}
	game.build()
	return game
}

type NintendoFactory struct{}

func (n *NintendoFactory) GameMachineFactory() GameMachine {
	switchMachine := &Switch{}
	switchMachine.addCDMachine()
	switchMachine.addCPU()
	switchMachine.addGPU()
	return &PS5{}
}

func (n *NintendoFactory) GameFactory() Game {
	game := &GameMario{}
	game.build()
	return game
}

func User(gameHomeFactory GameRoomFactory) {
	gameMachine := gameHomeFactory.GameMachineFactory()
	game := gameHomeFactory.GameFactory()
	game.Start()
	gameMachine.PlayGame()
}

func main() {
	User(&SonyFactory{})
}

<<:  JS 14 - 控制物件

>>:  [DAY 14] 轮回故事里的那些因果 : RNN 简介

Day21 Redis架构实战-高可用性

Redis高可用性 前面的说明与范例都是透过单台的Redis Server的方式进行,这样的配置下当...

GitHub Branch 策略 - 哪一种方式适合你?

若您对於 Git 相当熟悉,你应该对於建立分支(Branch) 应该不陌生。依据 GitHub 官方...

Day17 NodeJS-Express II

今天要针对Routes和Middleware的部份进一步了解Express框架。 Express中的...

【Day 09】- 大家都爱的 BeautifulSoup

前情提要 前一篇文章带大家看了Requests-HTML 库的使用,用他来做资料清洗使我们真正想要的...

Day 25路由

前言 路由简单来说就是连接介面的桥梁,而这个桥梁就叫做Navigator,就是导航的意思,用於管理进...