設計模式 - Abstract Factory Pattern 抽象工廠模式
Intro
假設我們今天一開始的需求是必須實作出A銀行的金流操作(有刷卡服務跟行動支付),
而在往後的需求擴充中,我又必須要新增B銀行的金流操作(一樣具有刷卡服務跟行動支付)
實作
簡單工廠
一開始我們只會有一個A銀行,所以一開始我們用簡單工廠去解決這個問題
1 2 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; } } }
|
使用方式大概如下
1 2 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
1 2 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
|
工廠方法
再來因為我們知道當用簡單工廠實作需求時,如果遇到要擴充的時候會違反開放封閉原則,所以我們在這邊當然也可以選擇用工廠方法去時做這個需求
1 2 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(); } }
|
使用方式大概如下
1 2 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
1 2 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去製造出這兩個具體的服務,當然這種修改方式理所當然地違反了開放封閉原則
1 2 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; } } }
|
使用方式大概如下
1 2 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
1 2 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個類別
1 2 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(); } }
|
使用方式大概如下
1 2 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
1 2 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
|
抽象工廠
底下我們嘗試著使用抽象工廠去重新將程式碼整理一次
1 2 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(); } }
|
使用方式大概如下
1 2 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
1 2 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
1 2 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
|
優點
- 不同的工廠產出的產品因為介面相同,故彼此相容,大大降低了抽換產品系列的難易度
- 可以避免產品與使用端的程式碼的耦合
- 開放封閉原則:新增新的產品系列只需新增相同介面的類別就可以了,不會改變原本的程式碼
- 增進了產品物件一致性
缺點
- 程式碼複雜化
- 開放封閉原則:如果我今天要新增的不是新的產品系列,而是產品的新的行為(假想我們今天要新增轉帳付款),如此一來經由介面的擴充,及新增帶有新介面的產品外,我們勢必還得要對現有的工廠介面作出修改,連帶著就必須修改到實作該介面的所有實體工廠