設計模式 - Abstract Factory Pattern 抽象工廠模式
Intro
假設我們今天一開始的需求是必須實作出A銀行的金流操作(有刷卡服務跟行動支付),
而在往後的需求擴充中,我又必須要新增B銀行的金流操作(一樣具有刷卡服務跟行動支付)
實作
簡單工廠
一開始我們只會有一個A銀行,所以一開始我們用簡單工廠去解決這個問題
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 
 | interface ICardPayment
 {
 public function payByCard(): string;
 }
 
 class ABankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'ABank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '3%現金回饋';
 }
 }
 
 interface IMobilePayment
 {
 public function payByMobile(): string;
 }
 
 class ABankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'ABank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '100元1點的點數回饋';
 }
 }
 
 class ABankServiceFactory
 {
 public function createBankService(string $service_type)
 {
 switch ($service_type) {
 case 'card':
 return new ABankCardPayment();
 break;
 case 'mobile':
 return new ABankMobilePayment();
 break;
 }
 }
 }
 
 
 | 
使用方式大概如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 
 $ABankService = new ABankServiceFactory();
 $PayService = $ABankService->createBankService('card');
 $PayService->payByCard();
 
 
 $ABankService = new ABankServiceFactory();
 $PayService = $ABankService->createBankService('mobile');
 $PayService->payByMobile();
 
 
 | 
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IMobilePayment{
 {method} + payByMobile(): string
 }
 
 interface ICardPayment{
 {method} + payByCard(): string
 }
 
 class ABankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class ABankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 class ABankServiceFactory{
 {method} + createBankService(string $service_type)
 }
 
 ABankServiceFactory .down.> ABankCardPayment : create
 ABankServiceFactory .down.> ABankMobilePayment : create
 ABankMobilePayment .down.|> IMobilePayment
 ABankCardPayment .down.|> ICardPayment
 @enduml
 
 
 | 
工廠方法
再來因為我們知道當用簡單工廠實作需求時,如果遇到要擴充的時候會違反開放封閉原則,所以我們在這邊當然也可以選擇用工廠方法去時做這個需求
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 
 | interface ICardPayment
 {
 public function payByCard(): string;
 }
 
 class ABankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'ABank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '3%現金回饋';
 }
 }
 
 interface IMobilePayment
 {
 public function payByMobile(): string;
 }
 
 class ABankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'ABank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '100元1點的點數回饋';
 }
 }
 
 interface ICardPaymentFactory
 {
 public function createCardPayment(): ICardPayment;
 }
 
 class ABankCardPaymentFactory implements ICardPaymentFactory
 {
 public function createCardPayment(): ICardPayment
 {
 return new ABankCardPayment();
 }
 }
 
 interface IMobilePaymentFactory
 {
 public function createMobilePayment(): IMobilePayment;
 }
 
 class ABankMobilePaymentFactory implements IMobilePaymentFactory
 {
 public function createMobilePayment(): IMobilePayment
 {
 return new ABankMobilePayment();
 }
 }
 
 
 | 
使用方式大概如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 
 $ABankCardPaymentFactory = new ABankCardPaymentFactory();
 $PayService = $ABankCardPaymentFactory->createCardPayment();
 $PayService->payByCard();
 
 
 $ABankMobilePaymentFactory = new ABankMobilePaymentFactory();
 $PayService = $ABankMobilePaymentFactory->createMobilePayment();
 $PayService->payByMobile();
 
 
 | 
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IMobilePayment{
 {method} + payByMobile(): string
 }
 
 interface ICardPayment{
 {method} + payByCard(): string
 }
 
 class ABankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class ABankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 interface ICardPaymentFactory{
 {method} + createCardPayment(): ICardPayment
 }
 
 interface IMobilePaymentFactory{
 {method} + createMobilePayment(): IMobilePayment
 }
 
 class ABankCardPaymentFactory{
 {method} + createCardPayment(): ICardPayment
 }
 
 class ABankMobilePaymentFactory{
 {method} + createMobilePayment(): IMobilePayment
 }
 
 ICardPaymentFactory .left.> ICardPayment : create
 IMobilePaymentFactory .left.> IMobilePayment : create
 ABankMobilePayment .up.|> IMobilePayment
 ABankCardPayment .up.|> ICardPayment
 ABankCardPaymentFactory .up.|> ICardPaymentFactory
 ABankMobilePaymentFactory .up.|> IMobilePaymentFactory
 @enduml
 
 | 
以上我們用兩種工廠解決了我們要使用A銀行的金流操作的需求,而在此時你的需求方跟你說我們現在要新增B銀行的金流操作,底下我們來看看如果用簡單工廠及工廠方法擴充B銀行的金流操作會是什麼樣的狀況吧
簡單工廠新增B銀行
以簡單工廠為例,我希望我的工廠可以多生產B銀行的刷卡服務跟B銀行的行動支付,所以我新增了兩個case去製造出這兩個具體的服務,當然這種修改方式理所當然地違反了開放封閉原則
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 
 | class BBankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'BBank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '5%現金回饋';
 }
 }
 
 class BBankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'BBank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '10元1點的點數回饋';
 }
 }
 
 class BankServiceFactory
 {
 public function createBankService(string $service_type)
 {
 switch ($service_type) {
 case 'a_card':
 return new ABankCardPayment();
 break;
 case 'a_mobile':
 return new ABankMobilePayment();
 break;
 case 'b_card':
 return new BBankCardPayment();
 break;
 case 'b_mobile':
 return new BBankMobilePayment();
 break;
 }
 }
 }
 
 
 | 
使用方式大概如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 
 $BankServiceFactory = new BankServiceFactory();
 $PayService = $BankServiceFactory->createBankService('a_card');
 $PayService->payByCard();
 
 
 $BankServiceFactory = new BankServiceFactory();
 $PayService = $BankServiceFactory->createBankService('a_mobile');
 $PayService->payByMobile()
 
 
 $BankServiceFactory = new BankServiceFactory();
 $PayService = $BankServiceFactory->createBankService('b_card');
 $PayService->payByCard();
 
 
 $BankServiceFactory = new BankServiceFactory();
 $PayService = $BankServiceFactory->createBankService('b_mobile');
 $PayService->payByMobile()
 
 
 | 
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IMobilePayment{
 {method} + payByMobile(): string
 }
 
 interface ICardPayment{
 {method} + payByCard(): string
 }
 
 class ABankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class ABankCardPayment{
 {method} + ABankCardPayment(): string
 {method} + getCashReturn(): string
 }
 
 class BBankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class BBankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 class BankServiceFactory{
 {method} + createBankService(string $service_type)
 }
 
 BankServiceFactory .down.> ABankCardPayment : create
 BankServiceFactory .down.> ABankMobilePayment : create
 BankServiceFactory .down.> BBankCardPayment : create
 BankServiceFactory .down.> BBankMobilePayment : create
 ABankMobilePayment .down.|> IMobilePayment
 ABankCardPayment .down.|> ICardPayment
 BBankMobilePayment .down.|> IMobilePayment
 BBankCardPayment .down.|> ICardPayment
 @enduml
 
 | 
工廠方法新增B銀行
再來我們嘗試著用工廠方法去擴充我們B銀行的金流操作,可以看到當我使用工廠方法去做擴充的時候,一個銀行有n種金流服務時,我就必須要新增該金流服務以及他對應的工廠,總共要新增n*2個類別,也就是說我們除了上方的類別外,還需要新增以下4個類別
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 
 | class BBankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'BBank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '5%現金回饋';
 }
 }
 
 class BBankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'BBank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '10元1點的點數回饋';
 }
 }
 
 class BBankCardPaymentFactory implements ICardPaymentFactory
 {
 public function createCardPayment(): ICardPayment
 {
 return new BBankCardPayment();
 }
 }
 
 class BBankMobilePaymentFactory implements IMobilePaymentFactory
 {
 public function createMobilePayment(): IMobilePayment
 {
 return new BBankMobilePayment();
 }
 }
 
 
 | 
使用方式大概如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 
 $ABankCardPaymentFactory = new ABankCardPaymentFactory();
 $PayService = $ABankCardPaymentFactory->createCardPayment();
 $PayService->payByCard();
 
 
 $ABankMobilePaymentFactory = new ABankMobilePaymentFactory();
 $PayService = $ABankMobilePaymentFactory->createMobilePayment();
 $PayService->payByMobile();
 
 
 $BBankCardPaymentFactory = new BBankCardPaymentFactory();
 $PayService = $BBankCardPaymentFactory->createCardPayment();
 $PayService->payByCard();
 
 
 $BBankMobilePaymentFactory = new BBankMobilePaymentFactory();
 $PayService = $BBankMobilePaymentFactory->createMobilePayment();
 $PayService->payByMobile();
 
 
 | 
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IMobilePayment{
 {method} + payByMobile(): string
 }
 
 interface ICardPayment{
 {method} + payByCard(): string
 }
 
 class ABankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class ABankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 class BBankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class BBankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 interface ICardPaymentFactory{
 {method} + createCardPayment(): ICardPayment
 }
 
 interface IMobilePaymentFactory{
 {method} + createMobilePayment(): IMobilePayment
 }
 
 class ABankCardPaymentFactory{
 {method} + createCardPayment(): ICardPayment
 }
 
 class ABankMobilePaymentFactory{
 {method} + createMobilePayment(): IMobilePayment
 }
 
 class BBankCardPaymentFactory{
 {method} + createCardPayment(): ICardPayment
 }
 
 class BBankMobilePaymentFactory{
 {method} + createMobilePayment(): IMobilePayment
 }
 
 ICardPaymentFactory .left.> ICardPayment : create
 IMobilePaymentFactory .left.> IMobilePayment : create
 ABankMobilePayment .up.|> IMobilePayment
 ABankCardPayment .up.|> ICardPayment
 BBankMobilePayment .up.|> IMobilePayment
 BBankCardPayment .up.|> ICardPayment
 ABankCardPaymentFactory .up.|> ICardPaymentFactory
 ABankMobilePaymentFactory .up.|> IMobilePaymentFactory
 BBankCardPaymentFactory .up.|> ICardPaymentFactory
 BBankMobilePaymentFactory .up.|> IMobilePaymentFactory
 @enduml
 
 | 
抽象工廠
底下我們嘗試著使用抽象工廠去重新將程式碼整理一次
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 
 | interface ICardPayment
 {
 public function payByCard(): string;
 }
 
 class ABankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'ABank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '3%現金回饋';
 }
 }
 
 class BBankCardPayment implements ICardPayment
 {
 public function payByCard(): string
 {
 return 'BBank 刷卡付款 -> 回饋:' . $this->getCashReturn();
 }
 
 public function getCashReturn(): string
 {
 return '5%現金回饋';
 }
 }
 
 interface IMobilePayment
 {
 public function payByMobile(): string;
 }
 
 class ABankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'ABank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '100元1點的點數回饋';
 }
 }
 
 class BBankMobilePayment implements IMobilePayment
 {
 public function payByMobile(): string
 {
 return 'BBank 行動支付 -> 回饋:' . $this->getBonusPoint();
 }
 
 public function getBonusPoint(): string
 {
 return '10元1點的點數回饋';
 }
 }
 
 interface IBankServiceFactory
 {
 public function createCardPaymentService(): ICardPayment;
 
 public function createMobilePaymentService(): IMobilePayment;
 }
 
 class ABankServiceFactory implements IBankServiceFactory
 {
 public function createCardPaymentService(): ICardPayment
 {
 return new ABankCardPayment();
 }
 
 public function createMobilePaymentService(): IMobilePayment
 {
 return new ABankMobilePayment();
 }
 }
 
 class BBankServiceFactory implements IBankServiceFactory
 {
 public function createCardPaymentService(): ICardPayment
 {
 return new BBankCardPayment();
 }
 
 public function createMobilePaymentService(): IMobilePayment
 {
 return new BBankMobilePayment();
 }
 }
 
 
 | 
使用方式大概如下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | 
 $ABankServiceFactory = new ABankServiceFactory();
 $PayService = $ABankServiceFactory->createCardPaymentService();
 $PayService->payByCard()
 
 
 $ABankServiceFactory = new ABankServiceFactory();
 $PayService = $ABankServiceFactory->createMobilePaymentService();
 $PayService->payByMobile();
 
 
 $BBankServiceFactory = new BBankServiceFactory();
 $PayService = $BBankServiceFactory->createCardPaymentService();
 $PayService->payByCard();
 
 
 $BBankServiceFactory = new BBankServiceFactory();
 $PayService = $BBankServiceFactory->createMobilePaymentService();
 $PayService->payByMobile();
 
 
 | 
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IMobilePayment{
 {method} + payByMobile(): string
 }
 
 interface ICardPayment{
 {method} + payByCard(): string
 }
 
 class ABankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class ABankCardPayment{
 {method} + ABankCardPayment(): string
 {method} + getCashReturn(): string
 }
 
 class BBankMobilePayment{
 {method} + payByMobile(): string
 {method} + getBonusPoint(): string
 }
 
 class BBankCardPayment{
 {method} + payByCard(): string
 {method} + getCashReturn(): string
 }
 
 interface IBankServiceFactory{
 {method} + createCardPaymentService(): ICardPayment
 {method} + createMobilePaymentService(): IMobilePayment
 }
 
 class ABankServiceFactory{
 {method} + createCardPaymentService(): ICardPayment
 {method} + createMobilePaymentService(): IMobilePayment
 }
 
 class BBankServiceFactory{
 {method} + createCardPaymentService(): ICardPayment
 {method} + createMobilePaymentService(): IMobilePayment
 }
 
 ABankMobilePayment .down.|> IMobilePayment
 ABankCardPayment .down.|> ICardPayment
 BBankMobilePayment .down.|> IMobilePayment
 BBankCardPayment .down.|> ICardPayment
 ABankServiceFactory .up.|> IBankServiceFactory
 BBankServiceFactory .up.|> IBankServiceFactory
 IBankServiceFactory .up.> ICardPayment : create
 IBankServiceFactory .up.> IMobilePayment : create
 @enduml
 
 | 
時機
- 當生產的產品有多個系列時(如上例子,同一種金流服務有多個銀行支援)
- 期望產品系列可以簡易的抽換時
- 當你期望產品系列的搭配可以較為順利時
- 一整個系列的產品必須一起使用,又要確保搭配上不會錯誤時
目的
以同一個介面來建立一系列相關或是相依的產品,不需指定個物件真正所屬的具體類別
類別圖

Plant UML
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | @startuml
 skinparam classAttributeIconSize 0
 
 interface IProductA
 interface IProductB
 class ConcreteProductB1
 class ConcreteProductA1
 class ConcreteProductB2
 class ConcreteProductA2
 
 interface IAbstractFactory{
 {method} + createProductA(): IProductA
 {method} + createProductB(): IProductB
 }
 
 class ConcreteFactory1{
 {method} + createProductA(): IProductA
 {method} + createProductB(): IProductB
 }
 
 class ConcreteFactory2{
 {method} + createProductA(): IProductA
 {method} + createProductB(): IProductB
 }
 
 ConcreteProductB1 .down.|> IProductB
 ConcreteProductA1 .down.|> IProductA
 ConcreteProductB2 .down.|> IProductB
 ConcreteProductA2 .down.|> IProductA
 ConcreteFactory1 .up.|> IAbstractFactory
 ConcreteFactory2 .up.|> IAbstractFactory
 IAbstractFactory .up.> IProductA : create
 IAbstractFactory .up.> IProductB : create
 @enduml
 
 | 
優點
- 不同的工廠產出的產品因為介面相同,故彼此相容,大大降低了抽換產品系列的難易度
- 可以避免產品與使用端的程式碼的耦合
- 開放封閉原則:新增新的產品系列只需新增相同介面的類別就可以了,不會改變原本的程式碼
- 增進了產品物件一致性
缺點
- 程式碼複雜化
- 開放封閉原則:如果我今天要新增的不是新的產品系列,而是產品的新的行為(假想我們今天要新增轉帳付款),如此一來經由介面的擴充,及新增帶有新介面的產品外,我們勢必還得要對現有的工廠介面作出修改,連帶著就必須修改到實作該介面的所有實體工廠