策略模式(Strategy Pattern)是一種行為型設計模式,它定義了一系列的演算法,將每個演算法都封裝起來,而且可以互換。
什麼情況適合使用 Strategy Pattern 策略模式
-
有多種相關的演算法: 當一個類別擁有多種相關的演算法,並且需要在執行時選擇其中一種演算法時,策略模式能夠提供一個清晰的結構,將每種演算法封裝成獨立的策略。
如果確定只有少數兩三種情形,直接用判斷式(if)即可,沒必要使用策略模式。
-
避免使用條件語句: 如果程式碼中有大量的條件語句,用來選擇不同的演算法時,策略模式可以幫助將這些演算法的實現分離出來,讓程式碼更容易理解和維護。
這其實和上一點相同,只是從另一個方向敘述。
-
需要動態切換演算法: 當程式需要在執行時能夠動態切換不同的演算法,而不影響客戶端的程式碼,策略模式是一個理想的選擇。這種情況下,可以通過改變策略對象,而不需要修改客戶端的程式碼,實現演算法的動態替換。
我看到的資料,有強調兩件事,但我不確定是否要符合這兩件事,才算是策略模式。
-
動態載入:處理前不確定資料格式,執行時才決定要使用哪個類別。這有兩層意義:
-
不確定資料來源格式。例如上方那個處理圖形的程式。在使用者開啟圖檔前,程式設計師並不知道使用者會開啟 jpg 或 png。(我懷疑這個不算策略模式。不過我第一個工作就是做這個,把各種不同格式的圖檔,轉成一樣的格式,然後用 BitBlt 輸出,那時是寫成 DLL。)
-
不確定資料輸出格式。例如老師的範例,來源都是 texture list,但輸出格式不同。
-
-
傳入參數的一致性:例如老師的範例,資料來源都是 texture list。以下的三個例子:排序、Billing、圖形壓縮,資料來源也各自相同。
-
以下兩個不算是適合的情境,而是使用策略模式的優點。
-
將演算法的實現與客戶端分離: 如果你希望客戶端的程式碼保持簡潔,並且不直接依賴於具體的演算法實現,策略模式提供了一個良好的抽象,使得客戶端只需關注如何選擇和使用策略,而不需要了解具體的實現。 -
容易擴展: 策略模式使得新增新的演算法或策略相對容易。只需實現新的策略類別,並將其插入到現有系統中,而不會影響到現有的程式碼。
簡單小結:當程式碼中涉及到多種相關的演算法,而且我們希望能夠動態地選擇和切換這些演算法時,策略模式是一個合適的選項。
三個常見的 Strategy Pattern 實際範例:
1. 排序
假設你有一個應用程式需要對不同類型的資料進行排序,而且你希望可以動態地切換排序演算法,就可以使用策略模式。
你可以定義一個排序策略的介面,例如 SortStrategy
,然後實作不同的排序演算法,如BubbleSortStrategy
、QuickSortStrategy
等。最後,你的應用程式可以在執行時選擇使用哪種排序策略。
class SortStrategy:
def sort(self, data):
pass
class BubbleSortStrategy(SortStrategy):
def sort(self, data):
# 實現氣泡排序算法
pass
class QuickSortStrategy(SortStrategy):
def sort(self, data):
# 實現快速排序算法
pass
2. Billing 付款方式
如果程式需要處理不同的付款方式,例如信用卡、PayPal、銀行轉帳等。你可以使用策略模式來實現支付策略。
每種付款方式都可以被視為一個策略,而在執行時,你可以動態地選擇使用哪種付款方式。
class PaymentStrategy:
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
# 使用信用卡支付
pass
class PayPalPayment(PaymentStrategy):
def pay(self, amount):
# 使用PayPal支付
pass
class BankTransferPayment(PaymentStrategy):
def pay(self, amount):
# 使用銀行轉帳支付
pass
3. 圖像壓縮
如果程式需要提供不同的圖像壓縮演算法,你可以使用策略模式。
每個壓縮演算法都可以視為一個策略,例如 JPEGCompressionStrategy
、PNGCompressionStrategy
等。在執行時,應用程式可以根據使用者的選擇動態地切換圖像壓縮策略。
class CompressionStrategy:
def compress(self, image):
pass
class JPEGCompressionStrategy(CompressionStrategy):
def compress(self, image):
# 使用JPEG壓縮算法
pass
class PNGCompressionStrategy(CompressionStrategy):
def compress(self, image):
# 使用PNG壓縮算法
pass
4. 實際範例:印表機選擇
Strategy Pattern 策略模式範例:
from abc import ABC # abstract base class 抽象基底類別,建立統一的介面和規範
from enum import Enum, auto # auto:自動分配獨特的值給列舉的成員
class OutputFormat(Enum):
MARKDOWN = auto()
HTML = auto()
# not required but a good idea
class ListStrategy(ABC):
def start(self, buffer): pass
def end(self, buffer): pass
def add_list_item(self, buffer, item): pass
class MarkdownListStrategy(ListStrategy):
def add_list_item(self, buffer, item):
buffer.append(f' * {item}\n')
class HtmlListStrategy(ListStrategy):
def start(self, buffer):
buffer.append('<ul>\n')
def end(self, buffer):
buffer.append('</ul>\n')
def add_list_item(self, buffer, item):
buffer.append(f' <li>{item}</li>\n')
# (續上方程式)
class TextProcessor:
def __init__(self, list_strategy=HtmlListStrategy()):
self.buffer = []
self.list_strategy = list_strategy
def append_list(self, items):
self.list_strategy.start(self.buffer)
for item in items:
self.list_strategy.add_list_item(
self.buffer, item
)
self.list_strategy.end(self.buffer)
def set_output_format(self, format):
if format == OutputFormat.MARKDOWN:
self.list_strategy = MarkdownListStrategy()
elif format == OutputFormat.HTML:
self.list_strategy = HtmlListStrategy()
def clear(self):
self.buffer.clear()
def __str__(self):
return ''.join(self.buffer)
# (續上方程式)
if __name__ == '__main__':
items = ['foo', 'bar', 'baz']
tp = TextProcessor()
tp.set_output_format(OutputFormat.MARKDOWN)
tp.append_list(items)
print(tp)
tp.set_output_format(OutputFormat.HTML)
tp.clear()
tp.append_list(items)
print(tp)
# 輸出
* foo
* bar
* baz
<ul>
<li>foo</li>
<li>bar</li>
<li>baz</li>
</ul>