Design Patterns @Python Course Note
-
Factories 工廠模式
-
概述:
- 工廠模式(Factory Pattern)是一種創建型設計模式,它允許客戶端在不指定具體類型的情況下創建對象。
-
用途及時機:
- 當物件的建構需要支援多種方式 → 需要根據不同的參數創建不同類型實例的情況。
- 需要創建的對象具有複雜的構造方法,而且需要進行重複使用。工廠模式可以將創建對象的邏輯封裝在工廠類中,客戶端只需要調用工廠類的方法即可獲得所需的對象。
- 封裝: 不讓(遮蔽)使用者接觸到不支援的方法 → 區別工廠來創建物件
- 例如有三合一的印表機原型,定義了列印、掃描、傳真三種方法,但如果某一台只支援列印的傳統印表機物件,繼承了該原型物件,但即便已經不實作掃描及傳真方法,仍會讓 User 看到這個方法,造成誤用
- 需要創建的對象存在多種類型,根據不同的條件進行創建。
- 封裝: 不讓(遮蔽)使用者接觸到不支援的方法 → 區別工廠來創建物件
- 工廠模式不適用於以下類型:
- 只需要創建一個對象的情況。在這種情況下,可以直接創建對象,而不需要使用工廠模式。
- 創建對象的過程很簡單,不需要進行封裝。在這種情況下,直接創建對象即可。
-
工廠類型:
- 簡單工廠模式 (Factory Method or Simple Factory)
- 這個方式將創建對象的代碼與使用該對象的代碼分離,對每個輸入的參數返回不同對象的方法
- 解決直接透過 init + if 的條件式創建 —> 違反OCP介面開閉原則(對擴展開放,對修改封閉)
- 抽象工廠模式 (Abstract Factory)
- 當創建目標對象為多種類型時,就需要抽象工廠類,讓一個工廠可以生產多種類型產品
- 簡單工廠模式 (Factory Method or Simple Factory)
-
流程示意:
(以上圖片摘自: Wikipedia - Abstract factory pattern)
-
-
工廠模式範例說明:
- 首先來看一個二維座標 Point 物件,要創建一個二維座標物件:
- 可以直接使用 x, y 座標來表示
- 也可以使用極座標法,使用向量長度 r 以及與水平夾角的角度 θ 來表示:
- x = r * cos(θ); y = r * sin(θ)
- 首先來看一個二維座標 Point 物件,要創建一個二維座標物件:
from enum import Enum
from math import *
class CoordinateSystem(Enum):
CARTESIAN = 1
POLAR = 2
class Point:
# 不合法的 __init__ 寫法 (只能有一個 __init__ 方法)
#def __init__(self, x, y):
# self.x = x
# self.y = y
#def __init__(self, a, b):
# self.x = a * cos(b)
# self.y = a * sin(b)
# 違反 OCP Rule --> 已經修改到類別中
def __init__(self, a, b, system=CoordinateSystem.CARTESIAN):
if system == CoordinateSystem.CARTESIAN:
self.x = a
self.y = b
elif system == CoordinateSystem.POLAR:
self.x = a * cos(b)
self.y = a * sin(b)
- 簡單工廠模式 (Simple Factory):
from math import *
from functools import reduce
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f'x: {self.x}, y: {self.y}'
# 將工廠方法用在類別物件下的靜態方法 ---> 這仍然違反OCP介面開閉原則
@staticmethod
def new_cartesian_point(x, y):
return Point(x, y)
@staticmethod
def new_polar_point(rho, theta):
angle = radians(theta)
return Point(rho * cos(angle), rho * sin(angle))
# 將工廠方法獨立出來到一個工廠類別中,這樣就不需要動到目標物件,而透過工廠擴充方法來使用不同方法創建物件
class PointFactory:
@staticmethod
def new_cartesian_point(x, y):
return Point(x, y)
@staticmethod
def new_polar_point(rho, theta):
angle = radians(theta)
return Point(rho * cos(angle), rho * sin(angle))
p1 = Point(1, 2)
p2 = Point.new_cartesian_point(3, 4)
p3 = Point.new_polar_point(6, 60)
p4 = PointFactory.new_cartesian_point(7, 8)
p5 = PointFactory.new_polar_point(6, 90)
result = reduce(lambda x, y: str(x) + '\n' + str(y) , [p1, p2, p3, p4, p5])
print(result)
- 抽象工廠模式 (Abstract Factory):
from abc import ABC # Abstract Base Class
from enum import Enum, auto
# 熱飲基類
class HotDrink(ABC):
def consume(self):
pass
# 茶 --> 繼承熱飲基類
class Tea(HotDrink):
def consume(self):
print('This tea is nice but I\'d prefer it with milk')
# 咖啡 --> 繼承熱飲基類
class Coffee(HotDrink):
def consume(self):
print('This coffee is delicious')
# 創建一個熱飲工廠基類
class HotDrinkFactory(ABC):
def prepare(self, amount):
pass
# 創建一個茶工廠類別,繼承熱飲工廠基類 --> 以創建茶物件
class TeaFactory(HotDrinkFactory):
def prepare(self, amount):
print(f'Put in tea bag, boil water, pour {amount}ml, enjoy!')
return Tea()
# 創建一個咖啡工廠類別,繼承熱飲工廠基類 --> 以創建咖啡物件
class CoffeeFactory(HotDrinkFactory):
def prepare(self, amount):
print(f'Grind some beans, boil water, pour {amount}ml, enjoy!')
return Coffee()
def make_drink(type):
if type == 'tea':
return TeaFactory().prepare(200)
elif type == 'coffee':
return CoffeeFactory().prepare(50)
else:
return None
make_drink('tea')
make_drink('coffee')