在現代軟體開發中,系統架構的複雜度與資源管理的效率是兩個核心挑戰。Facade 設計模式提供了一種優雅的方式來隱藏系統內部的複雜性,讓客戶端能夠透過簡潔的介面與系統互動。Flyweight 設計模式則專注於記憶體使用的最佳化,透過物件分享機制來處理需要大量相似物件的場景。本文將深入探討這兩種結構型設計模式的核心原理、實作技巧與最佳實踐,並提供完整的 Python 程式碼範例來說明如何在實際專案中應用這些模式。
Facade 設計模式的核心概念
Facade 設計模式屬於結構型設計模式,其主要目的是為複雜的子系統提供一個統一且簡化的介面。在大型軟體系統中,通常會包含多個相互依賴的子系統,每個子系統都有自己的介面與實作細節。直接讓客戶端與這些子系統互動,不僅增加了耦合度,也使得客戶端程式碼變得複雜難以維護。Facade 模式透過引入一個高層介面,將這些複雜的互動封裝起來,讓客戶端只需要與 Facade 物件溝通。
這種設計帶來了多項優點。首先,它降低了客戶端與子系統之間的耦合度,客戶端不需要了解子系統的內部結構就能使用系統功能。其次,Facade 成為處理橫切關注點的理想位置,例如日誌記錄、錯誤處理、交易管理等功能都可以在 Facade 層集中實作。第三,當子系統的實作需要改變時,只需要修改 Facade 的內部實作,客戶端程式碼不需要任何調整。
在實務應用中,Facade 模式特別適合以下場景:當系統包含多個複雜的子系統且需要提供簡化的存取方式時;當需要將系統與客戶端解耦以便於獨立演進時;當需要在系統入口處統一處理驗證、授權、日誌等橫切關注點時。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
' Facade 模式結構
rectangle "客戶端" as client
rectangle "Facade" as facade {
rectangle "統一介面" as interface
}
rectangle "子系統" as subsystem {
rectangle "驗證服務" as auth
rectangle "資料服務" as data
rectangle "日誌服務" as log
rectangle "快取服務" as cache
}
client --> facade : "簡化呼叫"
facade --> auth
facade --> data
facade --> log
facade --> cache
note bottom of facade
封裝複雜的子系統互動
提供簡潔的高層介面
集中處理橫切關注點
end note
@enduml實作整合多個子系統的 Facade
讓我們透過一個實際的範例來說明 Facade 模式的實作方式。假設我們正在開發一個需要整合身份驗證、資料擷取與日誌記錄的系統。這三個子系統各自擁有複雜的介面與實作,直接讓客戶端與它們互動會造成程式碼的複雜化。透過 Facade 模式,我們可以將這些互動封裝成單一的方法呼叫。
"""
Facade 設計模式實作範例
展示如何整合多個子系統並提供統一介面
"""
class AuthenticationService:
"""
身份驗證服務子系統
負責處理使用者身份驗證邏輯
"""
def authenticate(self, username: str, password: str) -> str:
"""
驗證使用者身份並回傳存取權杖
參數:
username: 使用者名稱
password: 使用者密碼
回傳:
驗證成功時回傳存取權杖字串
例外:
ValueError: 當身份驗證失敗時拋出
"""
# 實際應用中會包含更複雜的驗證邏輯
# 例如密碼雜湊比對、帳戶狀態檢查等
if username == "admin" and password == "secure_password":
# 生成並回傳權杖
# 實際應用會使用 JWT 或其他權杖機制
return f"token_{username}_{hash(password)}"
raise ValueError(f"使用者 '{username}' 身份驗證失敗")
class DataService:
"""
資料服務子系統
負責根據權杖擷取與處理資料
"""
def __init__(self):
# 模擬資料儲存
self._data_store = {
"users": [
{"id": 1, "name": "張三", "role": "admin"},
{"id": 2, "name": "李四", "role": "user"},
{"id": 3, "name": "王五", "role": "user"}
],
"products": [
{"id": 101, "name": "筆記型電腦", "price": 35000},
{"id": 102, "name": "無線滑鼠", "price": 800},
{"id": 103, "name": "機械鍵盤", "price": 3500}
]
}
def fetch_data(self, token: str, data_type: str) -> dict:
"""
根據權杖與資料類型擷取資料
參數:
token: 存取權杖
data_type: 要擷取的資料類型
回傳:
包含請求資料的字典
例外:
ValueError: 當權杖無效或資料類型不存在時拋出
"""
# 驗證權杖有效性
if not token.startswith("token_"):
raise ValueError("無效的存取權杖")
# 檢查資料類型是否存在
if data_type not in self._data_store:
raise ValueError(f"資料類型 '{data_type}' 不存在")
return {"type": data_type, "data": self._data_store[data_type]}
class LoggingService:
"""
日誌服務子系統
負責記錄系統事件與錯誤
"""
def __init__(self):
self._logs = []
def log_event(self, level: str, message: str) -> None:
"""
記錄事件到日誌系統
參數:
level: 日誌層級(INFO、WARNING、ERROR)
message: 日誌訊息
"""
import datetime
timestamp = datetime.datetime.now().isoformat()
log_entry = f"[{timestamp}] [{level}] {message}"
self._logs.append(log_entry)
print(log_entry)
def get_logs(self) -> list:
"""取得所有日誌記錄"""
return self._logs.copy()
class ServiceFacade:
"""
服務外觀類別
整合身份驗證、資料擷取與日誌記錄子系統
提供簡化的統一介面給客戶端使用
"""
def __init__(self):
"""初始化 Facade 並建立子系統實例"""
self._auth_service = AuthenticationService()
self._data_service = DataService()
self._log_service = LoggingService()
def get_user_data(self, username: str, password: str) -> dict:
"""
取得使用者資料的簡化介面
自動處理身份驗證、資料擷取與日誌記錄
參數:
username: 使用者名稱
password: 使用者密碼
回傳:
包含處理後使用者資料的字典
例外:
Exception: 當任何步驟失敗時拋出
"""
try:
# 步驟 1:身份驗證
self._log_service.log_event(
"INFO",
f"開始為使用者 '{username}' 進行身份驗證"
)
token = self._auth_service.authenticate(username, password)
self._log_service.log_event(
"INFO",
f"使用者 '{username}' 身份驗證成功"
)
# 步驟 2:擷取資料
self._log_service.log_event(
"INFO",
f"開始為使用者 '{username}' 擷取資料"
)
raw_data = self._data_service.fetch_data(token, "users")
self._log_service.log_event(
"INFO",
f"成功擷取 {len(raw_data['data'])} 筆使用者資料"
)
# 步驟 3:處理資料
processed_data = self._process_data(raw_data)
self._log_service.log_event(
"INFO",
f"資料處理完成,共 {processed_data['count']} 筆記錄"
)
return processed_data
except Exception as e:
self._log_service.log_event(
"ERROR",
f"處理使用者 '{username}' 的請求時發生錯誤:{str(e)}"
)
raise
def _process_data(self, raw_data: dict) -> dict:
"""
內部方法:處理原始資料
參數:
raw_data: 原始資料字典
回傳:
處理後的資料字典
"""
data_list = raw_data.get("data", [])
return {
"type": raw_data.get("type"),
"count": len(data_list),
"items": data_list,
"summary": {
"total_records": len(data_list),
"data_type": raw_data.get("type")
}
}
# 使用範例
if __name__ == "__main__":
# 建立 Facade 實例
facade = ServiceFacade()
# 透過簡化介面取得資料
# 客戶端不需要知道內部有多個子系統
try:
result = facade.get_user_data("admin", "secure_password")
print(f"\n處理結果:")
print(f" 資料類型:{result['type']}")
print(f" 記錄數量:{result['count']}")
for item in result['items']:
print(f" - {item['name']} ({item['role']})")
except Exception as e:
print(f"發生錯誤:{e}")
這個範例展示了 Facade 模式如何將複雜的子系統互動封裝成單一的 get_user_data 方法。客戶端只需要提供使用者名稱與密碼,Facade 就會自動處理身份驗證、資料擷取、資料處理與日誌記錄等所有步驟。這種設計大幅簡化了客戶端程式碼,同時也讓系統的維護與擴展變得更加容易。
快取機制與效能最佳化
在實際應用中,Facade 經常需要處理資源密集型的操作,例如網路請求、資料庫查詢等。為了提升效能,我們可以在 Facade 中引入快取機制,避免重複執行相同的操作。
"""
帶有快取機制的 Facade 實作
展示如何透過快取提升效能
"""
import time
from typing import Optional, Any
class CachedServiceFacade(ServiceFacade):
"""
具備快取功能的服務外觀
繼承自 ServiceFacade 並加入快取機制
"""
def __init__(self, cache_ttl: int = 300):
"""
初始化快取 Facade
參數:
cache_ttl: 快取存活時間(秒),預設 300 秒
"""
super().__init__()
self._cache = {} # 快取資料儲存
self._cache_timestamps = {} # 快取時間戳記
self._cache_ttl = cache_ttl # 快取存活時間
def _get_from_cache(self, key: str) -> Optional[Any]:
"""
從快取中取得資料
參數:
key: 快取鍵值
回傳:
快取的資料,若不存在或已過期則回傳 None
"""
if key not in self._cache:
return None
# 檢查快取是否過期
cached_time = self._cache_timestamps.get(key, 0)
if time.time() - cached_time > self._cache_ttl:
# 快取已過期,清除它
del self._cache[key]
del self._cache_timestamps[key]
return None
return self._cache[key]
def _set_cache(self, key: str, value: Any) -> None:
"""
將資料存入快取
參數:
key: 快取鍵值
value: 要快取的資料
"""
self._cache[key] = value
self._cache_timestamps[key] = time.time()
def get_user_data(self, username: str, password: str) -> dict:
"""
取得使用者資料(帶快取功能)
此方法會先檢查快取中是否有有效的權杖,
若有則直接使用,否則才進行身份驗證
參數:
username: 使用者名稱
password: 使用者密碼
回傳:
包含處理後使用者資料的字典
"""
# 建立快取鍵值
auth_cache_key = f"auth:{username}"
data_cache_key = f"data:{username}:users"
try:
# 嘗試從快取取得權杖
token = self._get_from_cache(auth_cache_key)
if token:
self._log_service.log_event(
"INFO",
f"使用快取的權杖為使用者 '{username}' 提供服務"
)
else:
# 快取中沒有有效權杖,執行身份驗證
self._log_service.log_event(
"INFO",
f"開始為使用者 '{username}' 進行身份驗證"
)
token = self._auth_service.authenticate(username, password)
self._set_cache(auth_cache_key, token)
self._log_service.log_event(
"INFO",
f"使用者 '{username}' 身份驗證成功,權杖已快取"
)
# 嘗試從快取取得資料
cached_data = self._get_from_cache(data_cache_key)
if cached_data:
self._log_service.log_event(
"INFO",
f"為使用者 '{username}' 回傳快取的資料"
)
return cached_data
# 快取中沒有資料,從資料服務擷取
self._log_service.log_event(
"INFO",
f"開始為使用者 '{username}' 擷取資料"
)
raw_data = self._data_service.fetch_data(token, "users")
processed_data = self._process_data(raw_data)
# 將處理後的資料存入快取
self._set_cache(data_cache_key, processed_data)
self._log_service.log_event(
"INFO",
f"資料已擷取並快取,共 {processed_data['count']} 筆記錄"
)
return processed_data
except Exception as e:
self._log_service.log_event(
"ERROR",
f"處理使用者 '{username}' 的請求時發生錯誤:{str(e)}"
)
raise
def clear_cache(self, username: Optional[str] = None) -> None:
"""
清除快取
參數:
username: 若指定則只清除該使用者的快取,
否則清除所有快取
"""
if username:
# 清除特定使用者的快取
keys_to_remove = [
key for key in self._cache.keys()
if username in key
]
for key in keys_to_remove:
del self._cache[key]
del self._cache_timestamps[key]
self._log_service.log_event(
"INFO",
f"已清除使用者 '{username}' 的快取"
)
else:
# 清除所有快取
self._cache.clear()
self._cache_timestamps.clear()
self._log_service.log_event(
"INFO",
"已清除所有快取"
)
# 使用範例
if __name__ == "__main__":
# 建立帶快取的 Facade
cached_facade = CachedServiceFacade(cache_ttl=60)
# 第一次呼叫會執行完整的驗證與資料擷取流程
print("第一次呼叫:")
result1 = cached_facade.get_user_data("admin", "secure_password")
print(f"取得 {result1['count']} 筆記錄\n")
# 第二次呼叫會使用快取
print("第二次呼叫(使用快取):")
result2 = cached_facade.get_user_data("admin", "secure_password")
print(f"取得 {result2['count']} 筆記錄\n")
# 清除快取後再次呼叫
cached_facade.clear_cache("admin")
print("清除快取後的呼叫:")
result3 = cached_facade.get_user_data("admin", "secure_password")
print(f"取得 {result3['count']} 筆記錄")
這個範例展示了如何在 Facade 中實作快取機制。透過快取權杖與資料,我們可以避免重複的身份驗證請求與資料擷取操作,大幅提升系統效能。快取具有過期時間設定,確保資料的新鮮度。同時也提供了清除快取的方法,讓系統能夠在需要時強制更新資料。
Flyweight 設計模式的核心概念
Flyweight 設計模式同樣屬於結構型設計模式,但它解決的問題與 Facade 截然不同。當系統需要建立大量相似的物件時,每個物件都佔用一定的記憶體空間,這可能導致嚴重的記憶體消耗問題。Flyweight 模式透過物件分享來解決這個問題,讓多個邏輯上獨立的物件能夠共用相同的底層資料。
Flyweight 模式的核心概念是將物件的狀態分為兩種:內在狀態與外在狀態。內在狀態是物件可以分享的部分,它是不可變的且不依賴於物件的使用情境。外在狀態則是每個物件獨有的部分,它依賴於使用情境且可能會變化。透過將內在狀態抽取出來並在多個物件間分享,Flyweight 模式能夠大幅降低記憶體使用量。
這種模式特別適合以下場景:當應用程式需要建立大量相似物件時;當物件的大部分狀態可以被抽取為外在狀態時;當移除外在狀態後,可以用少量的分享物件取代大量原本的物件時。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
' Flyweight 模式結構
rectangle "客戶端" as client
rectangle "Flyweight 工廠" as factory {
rectangle "物件池" as pool
}
rectangle "Flyweight 物件" as flyweight {
rectangle "內在狀態\n(分享)" as intrinsic
}
rectangle "外在狀態\n(獨立)" as extrinsic
client --> factory : "請求物件"
factory --> pool : "管理"
pool --> flyweight : "儲存"
client --> extrinsic : "維護"
client --> flyweight : "操作\n(傳入外在狀態)"
note bottom of flyweight
多個客戶端分享
相同的 Flyweight 物件
只有內在狀態被儲存
end note
@enduml實作字形渲染的 Flyweight 模式
讓我們透過一個字形渲染系統的範例來說明 Flyweight 模式的實作方式。在文字編輯器或排版系統中,需要渲染大量的字元。每個字元都有其字型、樣式等屬性,但許多字元會使用相同的字型與樣式組合。透過 Flyweight 模式,我們可以讓這些字元分享相同的字型與樣式物件,只在渲染時提供各自的位置與顏色資訊。
"""
Flyweight 設計模式實作範例
以字形渲染系統展示物件分享機制
"""
from typing import Tuple
class Glyph:
"""
字形類別(Flyweight)
封裝字元的內在狀態:字元本身、字型與樣式
"""
def __init__(self, char: str, font: str, style: str):
"""
初始化字形物件
參數:
char: 字元
font: 字型名稱
style: 樣式(如 Regular、Bold、Italic)
"""
# 內在狀態:這些屬性在物件建立後不會改變
# 且可以在多個使用情境間分享
self._char = char
self._font = font
self._style = style
@property
def char(self) -> str:
"""取得字元"""
return self._char
@property
def font(self) -> str:
"""取得字型"""
return self._font
@property
def style(self) -> str:
"""取得樣式"""
return self._style
def render(self, x: int, y: int, color: str, size: int = 12) -> None:
"""
渲染字形
參數為外在狀態,由客戶端在呼叫時提供:
x: X 座標位置
y: Y 座標位置
color: 顏色
size: 字體大小
"""
print(
f"渲染 '{self._char}' "
f"[{self._font}, {self._style}, {size}pt] "
f"於 ({x}, {y}) "
f"顏色:{color}"
)
def __repr__(self) -> str:
return f"Glyph('{self._char}', {self._font}, {self._style})"
class GlyphFactory:
"""
字形工廠類別
負責管理 Glyph Flyweight 物件的建立與分享
"""
# 類別層級的 Flyweight 物件池
_flyweights = {}
@classmethod
def get_glyph(cls, char: str, font: str, style: str) -> Glyph:
"""
取得字形物件
若物件池中已存在相同內在狀態的物件則回傳它,
否則建立新物件並加入物件池
參數:
char: 字元
font: 字型名稱
style: 樣式
回傳:
Glyph 物件
"""
# 使用內在狀態作為快取鍵值
key = (char, font, style)
if key not in cls._flyweights:
# 物件不存在,建立新的 Flyweight
cls._flyweights[key] = Glyph(char, font, style)
print(f"建立新的 Glyph:{key}")
else:
print(f"重複使用現有的 Glyph:{key}")
return cls._flyweights[key]
@classmethod
def get_flyweight_count(cls) -> int:
"""取得物件池中的 Flyweight 數量"""
return len(cls._flyweights)
@classmethod
def clear_pool(cls) -> None:
"""清除物件池"""
cls._flyweights.clear()
@classmethod
def get_pool_info(cls) -> dict:
"""取得物件池的詳細資訊"""
return {
"total_flyweights": len(cls._flyweights),
"flyweights": list(cls._flyweights.keys())
}
class TextRenderer:
"""
文字渲染器
使用 Flyweight 模式來渲染文字
"""
def __init__(self, default_font: str = "Arial",
default_style: str = "Regular"):
"""
初始化文字渲染器
參數:
default_font: 預設字型
default_style: 預設樣式
"""
self._default_font = default_font
self._default_style = default_style
# 儲存要渲染的字形及其外在狀態
self._render_queue = []
def add_text(self, text: str, start_x: int, start_y: int,
color: str = "black", size: int = 12,
font: str = None, style: str = None) -> None:
"""
將文字加入渲染佇列
參數:
text: 要渲染的文字
start_x: 起始 X 座標
start_y: 起始 Y 座標
color: 文字顏色
size: 字體大小
font: 字型(若未指定則使用預設值)
style: 樣式(若未指定則使用預設值)
"""
font = font or self._default_font
style = style or self._default_style
x = start_x
for char in text:
if char == " ":
# 空白字元不建立 Flyweight,只移動位置
x += size // 2
continue
# 取得或建立 Flyweight
glyph = GlyphFactory.get_glyph(char, font, style)
# 儲存 Flyweight 與其外在狀態
self._render_queue.append({
"glyph": glyph,
"x": x,
"y": start_y,
"color": color,
"size": size
})
# 移動到下一個字元位置
x += size
def render_all(self) -> None:
"""渲染佇列中的所有字形"""
print("\n開始渲染文字:")
print("-" * 60)
for item in self._render_queue:
item["glyph"].render(
item["x"],
item["y"],
item["color"],
item["size"]
)
print("-" * 60)
print(f"渲染完成,共 {len(self._render_queue)} 個字形")
print(f"使用了 {GlyphFactory.get_flyweight_count()} 個 Flyweight 物件")
def clear(self) -> None:
"""清除渲染佇列"""
self._render_queue.clear()
# 使用範例
if __name__ == "__main__":
# 清除之前的物件池
GlyphFactory.clear_pool()
# 建立文字渲染器
renderer = TextRenderer(default_font="微軟正黑體", default_style="Regular")
# 加入要渲染的文字
print("準備渲染文字...\n")
renderer.add_text(
"HELLO WORLD",
start_x=10,
start_y=20,
color="black",
size=14
)
renderer.add_text(
"HELLO FLYWEIGHT",
start_x=10,
start_y=40,
color="blue",
size=14
)
# 執行渲染
renderer.render_all()
# 顯示物件池資訊
print("\n物件池資訊:")
pool_info = GlyphFactory.get_pool_info()
print(f"總共建立了 {pool_info['total_flyweights']} 個 Flyweight 物件")
# 計算記憶體節省
total_chars = len("HELLO WORLD") + len("HELLO FLYWEIGHT") - 2 # 扣除空白
saved_objects = total_chars - pool_info['total_flyweights']
print(f"總共需要渲染 {total_chars} 個字元")
print(f"節省了 {saved_objects} 個物件的建立")
這個範例清楚展示了 Flyweight 模式的運作方式。當渲染相同的字元(例如 “HELLO” 中的 ‘L’)時,系統會重複使用同一個 Glyph 物件,而不是為每個字元建立新的物件。字元、字型與樣式作為內在狀態被分享,而位置、顏色與大小則作為外在狀態在渲染時提供。這種設計在需要渲染大量文字的應用中能夠顯著降低記憶體使用量。
UI 元件的 Flyweight 實作
Flyweight 模式在 UI 開發中也有廣泛的應用。當應用程式需要顯示大量具有相似樣式的元件時,例如列表中的項目或表格中的儲存格,可以讓這些元件分享相同的樣式物件。
"""
UI 元件的 Flyweight 模式實作
展示如何分享按鈕樣式以減少記憶體使用
"""
from typing import List, Tuple
class ButtonStyle:
"""
按鈕樣式類別(Flyweight)
封裝按鈕的視覺樣式屬性
"""
def __init__(self, background_color: str, border_style: str,
border_radius: int, font_family: str):
"""
初始化按鈕樣式
所有參數都是內在狀態,在物件建立後不會改變
參數:
background_color: 背景顏色
border_style: 邊框樣式
border_radius: 圓角半徑
font_family: 字型家族
"""
self._background_color = background_color
self._border_style = border_style
self._border_radius = border_radius
self._font_family = font_family
@property
def background_color(self) -> str:
return self._background_color
@property
def border_style(self) -> str:
return self._border_style
@property
def border_radius(self) -> int:
return self._border_radius
@property
def font_family(self) -> str:
return self._font_family
def __repr__(self) -> str:
return (
f"ButtonStyle("
f"bg={self._background_color}, "
f"border={self._border_style}, "
f"radius={self._border_radius}px, "
f"font={self._font_family})"
)
class ButtonStyleFactory:
"""
按鈕樣式工廠
管理 ButtonStyle Flyweight 物件
"""
_styles = {}
@classmethod
def get_style(cls, background_color: str, border_style: str,
border_radius: int = 4,
font_family: str = "sans-serif") -> ButtonStyle:
"""
取得按鈕樣式物件
參數:
background_color: 背景顏色
border_style: 邊框樣式
border_radius: 圓角半徑
font_family: 字型家族
回傳:
ButtonStyle 物件
"""
key = (background_color, border_style, border_radius, font_family)
if key not in cls._styles:
cls._styles[key] = ButtonStyle(
background_color,
border_style,
border_radius,
font_family
)
return cls._styles[key]
@classmethod
def get_style_count(cls) -> int:
"""取得樣式物件數量"""
return len(cls._styles)
class Button:
"""
按鈕元件類別
使用 Flyweight 樣式物件
"""
def __init__(self, label: str, style: ButtonStyle,
x: int, y: int, width: int, height: int):
"""
初始化按鈕
參數:
label: 按鈕文字(外在狀態)
style: 按鈕樣式(Flyweight,內在狀態)
x: X 座標(外在狀態)
y: Y 座標(外在狀態)
width: 寬度(外在狀態)
height: 高度(外在狀態)
"""
self._label = label
self._style = style # 分享的 Flyweight 物件
self._x = x
self._y = y
self._width = width
self._height = height
def render(self) -> None:
"""渲染按鈕"""
print(
f"渲染按鈕 '{self._label}' "
f"於 ({self._x}, {self._y}) "
f"大小:{self._width}x{self._height} "
f"樣式:{self._style}"
)
def handle_click(self) -> None:
"""處理點擊事件"""
print(f"按鈕 '{self._label}' 被點擊")
class ButtonPanel:
"""
按鈕面板
管理多個按鈕並展示 Flyweight 模式的效益
"""
def __init__(self):
self._buttons: List[Button] = []
def add_button(self, label: str, x: int, y: int,
width: int = 100, height: int = 30,
style_type: str = "primary") -> Button:
"""
加入按鈕到面板
參數:
label: 按鈕文字
x: X 座標
y: Y 座標
width: 寬度
height: 高度
style_type: 樣式類型(primary/secondary/danger)
回傳:
建立的 Button 物件
"""
# 根據樣式類型取得預定義的樣式
style_configs = {
"primary": ("#007bff", "solid", 4, "Arial"),
"secondary": ("#6c757d", "solid", 4, "Arial"),
"danger": ("#dc3545", "solid", 4, "Arial"),
"success": ("#28a745", "solid", 4, "Arial"),
"outline": ("#ffffff", "dashed", 4, "Arial"),
}
config = style_configs.get(style_type, style_configs["primary"])
style = ButtonStyleFactory.get_style(*config)
button = Button(label, style, x, y, width, height)
self._buttons.append(button)
return button
def render_all(self) -> None:
"""渲染所有按鈕"""
print("\n渲染按鈕面板:")
print("-" * 80)
for button in self._buttons:
button.render()
print("-" * 80)
def get_statistics(self) -> dict:
"""取得統計資訊"""
return {
"total_buttons": len(self._buttons),
"unique_styles": ButtonStyleFactory.get_style_count(),
"memory_saved": len(self._buttons) - ButtonStyleFactory.get_style_count()
}
# 使用範例
if __name__ == "__main__":
# 建立按鈕面板
panel = ButtonPanel()
# 加入多個按鈕
# 許多按鈕會分享相同的樣式
panel.add_button("儲存", 10, 10, style_type="primary")
panel.add_button("確認", 120, 10, style_type="primary")
panel.add_button("提交", 230, 10, style_type="primary")
panel.add_button("取消", 10, 50, style_type="secondary")
panel.add_button("關閉", 120, 50, style_type="secondary")
panel.add_button("刪除", 10, 90, style_type="danger")
panel.add_button("移除", 120, 90, style_type="danger")
panel.add_button("清除", 230, 90, style_type="danger")
panel.add_button("新增", 10, 130, style_type="success")
panel.add_button("建立", 120, 130, style_type="success")
# 渲染所有按鈕
panel.render_all()
# 顯示統計資訊
stats = panel.get_statistics()
print(f"\n統計資訊:")
print(f" 總按鈕數:{stats['total_buttons']}")
print(f" 唯一樣式數:{stats['unique_styles']}")
print(f" 節省的物件數:{stats['memory_saved']}")
# 計算節省比例
if stats['total_buttons'] > 0:
save_ratio = (stats['memory_saved'] / stats['total_buttons']) * 100
print(f" 記憶體節省比例:{save_ratio:.1f}%")
執行緒安全的 Flyweight 實作
在多執行緒環境中使用 Flyweight 模式時,需要特別注意執行緒安全問題。多個執行緒同時存取 Flyweight 工廠可能會導致競爭條件,造成重複建立物件或其他不一致的情況。以下範例展示如何實作執行緒安全的 Flyweight 工廠。
"""
執行緒安全的 Flyweight 工廠實作
確保在多執行緒環境下的正確性
"""
import threading
from typing import Dict, Tuple
class ThreadSafeGlyphFactory:
"""
執行緒安全的字形工廠
使用鎖機制確保多執行緒環境下的正確性
"""
_flyweights: Dict[Tuple[str, str, str], 'Glyph'] = {}
_lock = threading.Lock()
@classmethod
def get_glyph(cls, char: str, font: str, style: str) -> 'Glyph':
"""
執行緒安全地取得字形物件
參數:
char: 字元
font: 字型
style: 樣式
回傳:
Glyph 物件
"""
key = (char, font, style)
# 使用雙重檢查鎖定模式(Double-Checked Locking)
# 先不加鎖檢查,提升效能
if key in cls._flyweights:
return cls._flyweights[key]
# 若不存在,則加鎖後再次檢查並建立
with cls._lock:
# 再次檢查,因為其他執行緒可能已經建立
if key not in cls._flyweights:
cls._flyweights[key] = Glyph(char, font, style)
return cls._flyweights[key]
@classmethod
def get_flyweight_count(cls) -> int:
"""取得 Flyweight 數量"""
with cls._lock:
return len(cls._flyweights)
@classmethod
def clear_pool(cls) -> None:
"""清除物件池"""
with cls._lock:
cls._flyweights.clear()
def worker_function(thread_id: int, text: str) -> None:
"""
工作執行緒函數
模擬多個執行緒同時請求 Flyweight 物件
"""
for char in text:
glyph = ThreadSafeGlyphFactory.get_glyph(
char, "Arial", "Regular"
)
# 模擬使用 Flyweight 進行某些操作
print(f"執行緒 {thread_id}:處理字元 '{char}'")
# 使用範例
if __name__ == "__main__":
# 清除物件池
ThreadSafeGlyphFactory.clear_pool()
# 建立多個執行緒
threads = []
texts = ["HELLO", "WORLD", "FLYWEIGHT"]
for i, text in enumerate(texts):
thread = threading.Thread(
target=worker_function,
args=(i, text)
)
threads.append(thread)
# 啟動所有執行緒
print("啟動執行緒...\n")
for thread in threads:
thread.start()
# 等待所有執行緒完成
for thread in threads:
thread.join()
# 顯示結果
print(f"\n所有執行緒完成")
print(f"物件池中共有 {ThreadSafeGlyphFactory.get_flyweight_count()} 個 Flyweight")
結合 Facade 與 Flyweight 模式
在實際應用中,不同的設計模式經常會結合使用。Facade 模式可以用來簡化對 Flyweight 工廠的存取,同時加入額外的功能如日誌記錄、效能監控等。
"""
結合 Facade 與 Flyweight 模式的範例
展示如何同時運用兩種模式
"""
class RenderingFacade:
"""
渲染系統外觀
整合 Flyweight 模式的字形工廠與其他服務
"""
def __init__(self):
self._render_count = 0
self._cache_hits = 0
self._cache_misses = 0
def render_text(self, text: str, x: int, y: int,
font: str = "Arial", style: str = "Regular",
color: str = "black") -> None:
"""
渲染文字的簡化介面
參數:
text: 要渲染的文字
x: 起始 X 座標
y: Y 座標
font: 字型
style: 樣式
color: 顏色
"""
current_x = x
for char in text:
if char == " ":
current_x += 10
continue
# 檢查是否為快取命中
key = (char, font, style)
is_cached = key in GlyphFactory._flyweights
# 取得 Flyweight
glyph = GlyphFactory.get_glyph(char, font, style)
# 更新統計
if is_cached:
self._cache_hits += 1
else:
self._cache_misses += 1
# 渲染
glyph.render(current_x, y, color)
self._render_count += 1
current_x += 12
def get_statistics(self) -> dict:
"""取得渲染統計資訊"""
total_requests = self._cache_hits + self._cache_misses
hit_rate = (
self._cache_hits / total_requests * 100
if total_requests > 0 else 0
)
return {
"total_renders": self._render_count,
"cache_hits": self._cache_hits,
"cache_misses": self._cache_misses,
"hit_rate": f"{hit_rate:.1f}%",
"flyweight_count": GlyphFactory.get_flyweight_count()
}
# 使用範例
if __name__ == "__main__":
GlyphFactory.clear_pool()
# 使用 Facade 簡化渲染操作
facade = RenderingFacade()
print("使用 RenderingFacade 渲染文字:\n")
facade.render_text("Hello World", 10, 20, color="blue")
facade.render_text("Hello Again", 10, 40, color="red")
# 顯示統計
stats = facade.get_statistics()
print(f"\n渲染統計:")
print(f" 總渲染次數:{stats['total_renders']}")
print(f" 快取命中:{stats['cache_hits']}")
print(f" 快取未命中:{stats['cache_misses']}")
print(f" 命中率:{stats['hit_rate']}")
print(f" Flyweight 數量:{stats['flyweight_count']}")
總結與最佳實踐
Facade 與 Flyweight 是兩種重要的結構型設計模式,各自解決不同的軟體設計問題。Facade 模式專注於簡化複雜系統的存取介面,而 Flyweight 模式則專注於最佳化記憶體使用。
在使用 Facade 模式時,應該注意幾個關鍵點。首先,Facade 應該只暴露客戶端真正需要的功能,避免成為一個臃腫的「上帝物件」。其次,Facade 不應該阻止客戶端直接存取子系統,當客戶端需要更細緻的控制時,應該允許它繞過 Facade。第三,可以考慮使用多個 Facade 來分離不同的關注點,例如一個處理使用者相關操作,另一個處理訂單相關操作。
在使用 Flyweight 模式時,關鍵在於正確識別內在狀態與外在狀態。內在狀態必須是不可變的,且能夠在多個物件間安全分享。外在狀態則由客戶端維護並在需要時傳入。此外,在多執行緒環境中,必須確保 Flyweight 工廠的執行緒安全性。
這兩種模式可以結合使用,Facade 可以簡化對 Flyweight 工廠的存取,同時提供額外的功能如統計、日誌、快取等。透過適當地運用這些設計模式,我們能夠建構出更加模組化、可維護且高效能的軟體系統。