Strategy Pattern 策略模式

策略模式(Strategy Pattern)是一種行為型設計模式,它定義了一系列的演算法,將每個演算法都封裝起來,而且可以互換。

什麼情況適合使用 Strategy Pattern 策略模式

  1. 有多種相關的演算法: 當一個類別擁有多種相關的演算法,並且需要在執行時選擇其中一種演算法時,策略模式能夠提供一個清晰的結構,將每種演算法封裝成獨立的策略。

    如果確定只有少數兩三種情形,直接用判斷式(if)即可,沒必要使用策略模式。

  2. 避免使用條件語句: 如果程式碼中有大量的條件語句,用來選擇不同的演算法時,策略模式可以幫助將這些演算法的實現分離出來,讓程式碼更容易理解和維護。

    這其實和上一點相同,只是從另一個方向敘述。

  3. 需要動態切換演算法: 當程式需要在執行時能夠動態切換不同的演算法,而不影響客戶端的程式碼,策略模式是一個理想的選擇。這種情況下,可以通過改變策略對象,而不需要修改客戶端的程式碼,實現演算法的動態替換。

    我看到的資料,有強調兩件事,但我不確定是否要符合這兩件事,才算是策略模式。

    • 動態載入:處理前不確定資料格式,執行時才決定要使用哪個類別。這有兩層意義:

      1. 不確定資料來源格式。例如上方那個處理圖形的程式。在使用者開啟圖檔前,程式設計師並不知道使用者會開啟 jpg 或 png。(我懷疑這個不算策略模式。不過我第一個工作就是做這個,把各種不同格式的圖檔,轉成一樣的格式,然後用 BitBlt 輸出,那時是寫成 DLL。)

      2. 不確定資料輸出格式。例如老師的範例,來源都是 texture list,但輸出格式不同。

    • 傳入參數的一致性:例如老師的範例,資料來源都是 texture list。以下的三個例子:排序、Billing、圖形壓縮,資料來源也各自相同。

以下兩個不算是適合的情境,而是使用策略模式的優點。

  1. 將演算法的實現與客戶端分離: 如果你希望客戶端的程式碼保持簡潔,並且不直接依賴於具體的演算法實現,策略模式提供了一個良好的抽象,使得客戶端只需關注如何選擇和使用策略,而不需要了解具體的實現。

  2. 容易擴展: 策略模式使得新增新的演算法或策略相對容易。只需實現新的策略類別,並將其插入到現有系統中,而不會影響到現有的程式碼。

簡單小結:當程式碼中涉及到多種相關的演算法,而且我們希望能夠動態地選擇和切換這些演算法時,策略模式是一個合適的選項。

三個常見的 Strategy Pattern 實際範例:

1. 排序

假設你有一個應用程式需要對不同類型的資料進行排序,而且你希望可以動態地切換排序演算法,就可以使用策略模式。

你可以定義一個排序策略的介面,例如 SortStrategy ,然後實作不同的排序演算法,如BubbleSortStrategyQuickSortStrategy 等。最後,你的應用程式可以在執行時選擇使用哪種排序策略。

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. 圖像壓縮

如果程式需要提供不同的圖像壓縮演算法,你可以使用策略模式。

每個壓縮演算法都可以視為一個策略,例如 JPEGCompressionStrategyPNGCompressionStrategy 等。在執行時,應用程式可以根據使用者的選擇動態地切換圖像壓縮策略。

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>

Strategy Pattern 策略模式和 State Pattern 狀態模式的差異

狀態模式可視為策略模式的延伸

同:兩種模式都以組合為核心,透過將工作委託給輔助物件來處理

異:使用策略模式時,這些物件完全獨立且彼此不知道

異:狀態模式則允許物件在內部狀態改變時,改變它們的行為


Strategy Pattern 策略模式和 Command Pattern 命令模式的差異

策略模式和命令模式看似相似,但動機略有不同。

策略模式允許物件以不同的方式實現相同的目標。

命令模式允許將操作轉換為物件。

1個讚