返回文章列表

行為設計模式責任鏈與命令模式

本文深入探討責任鏈模式和命令模式,並提供 Python 實作範例。責任鏈模式用於事件驅動系統,允許請求沿著處理鏈傳遞,直到被處理。命令模式則將操作封裝為物件,實作操作的可延遲執行和 Undo 功能,提升程式碼彈性與可維護性。

軟體設計 設計模式

責任鏈模式和命令模式是兩種常見的行為設計模式,它們都能有效地解決軟體設計中的特定問題。責任鏈模式適用於事件驅動系統,允許請求在處理鏈中傳遞,直到找到合適的處理者。這種模式可以避免將請求的傳送者與接收者耦合在一起,提高系統的靈活性。命令模式則將操作封裝成物件,使操作的執行和復原更加方便。這對於需要實作 Undo/Redo 功能的應用程式來說尤其重要,例如文字編輯器、繪圖軟體等。這兩種模式都能提高程式碼的可讀性、可維護性和可擴充套件性,是軟體開發中常用的設計模式。

行為設計模式:責任鏈模式與命令模式

在軟體開發中,行為設計模式用於管理物件之間的互動與行為分配。本篇文章將深入探討兩種重要的行為設計模式:責任鏈模式(Chain of Responsibility)和命令模式(Command),並提供詳細的實作範例與分析。

責任鏈模式:事件驅動系統的實作

責任鏈模式允許將請求沿著處理鏈傳遞,直到有一個物件能夠處理它。以下是一個簡單的事件驅動視窗系統範例,展示了責任鏈模式的應用。

系統架構與實作

首先,我們定義了一個Event類別來描述事件。該類別包含事件名稱,並提供字串表示方法:

class Event:
 def __init__(self, name):
 self.name = name
 
 def __str__(self):
 return self.name

接著,我們實作了Widget類別作為應用程式的核心類別。該類別使用了動態分派機制來決定由哪個處理器處理特定事件:

class Widget:
 def __init__(self, parent=None):
 self.parent = parent
 
 def handle(self, event):
 handler = f"handle_{event}"
 if hasattr(self, handler):
 method = getattr(self, handler)
 method(event)
 elif self.parent is not None:
 self.parent.handle(event)
 elif hasattr(self, "handle_default"):
 self.handle_default(event)

具體元件實作

我們定義了三個具體的元件類別:MainWindowSendDialogMsgText,每個類別都繼承自Widget並實作特定的事件處理方法:

class MainWindow(Widget):
 def handle_close(self, event):
 print(f"MainWindow: {event}")
 
 def handle_default(self, event):
 print(f"MainWindow Default: {event}")

class SendDialog(Widget):
 def handle_paint(self, event):
 print(f"SendDialog: {event}")

class MsgText(Widget):
 def handle_down(self, event):
 print(f"MsgText: {event}")

系統執行流程

main()函式中,我們建立了元件例項並測試事件處理流程:

def main():
 mw = MainWindow()
 sd = SendDialog(mw)
 msg = MsgText(sd)
 
 for e in ("down", "paint", "unhandled", "close"):
 evt = Event(e)
 print(f"Sending event -{evt}- to MainWindow")
 mw.handle(evt)
 print(f"Sending event -{evt}- to SendDialog")
 sd.handle(evt)
 print(f"Sending event -{evt}- to MsgText")
 msg.handle(evt)

系統輸出結果

執行上述程式碼後,我們可以觀察到事件在不同元件之間的傳遞過程。例如,當傳送down事件給MsgText元件時,它能夠直接處理該事件;而當傳送paint事件給MsgText時,由於MsgText未定義handle_paint方法,事件會被傳遞給其父元件SendDialog處理。

命令模式:實作Undo功能

命令模式透過將操作封裝為物件,實作了操作的可延遲執行和Undo功能。以下是一個簡單的命令模式範例。

命令介面與具體命令

首先,我們定義命令介面Command,並實作具體的命令類別,如RenameCommand

class Command:
 def execute(self):
 raise NotImplementedError

class RenameCommand(Command):
 def __init__(self, obj, new_name):
 self.obj = obj
 self.new_name = new_name
 self.old_name = obj.name
 
 def execute(self):
 self.obj.name = self.new_name
 
 def undo(self):
 self.obj.name = self.old_name

命令歷史管理

為了實作Undo功能,我們需要管理已執行的命令歷史:

class CommandHistory:
 def __init__(self):
 self.history = []
 
 def execute_command(self, command):
 command.execute()
 self.history.append(command)
 
 def undo_last_command(self):
 if self.history:
 command = self.history.pop()
 command.undo()

應使用案例項

class Object:
 def __init__(self, name):
 self.name = name

def main():
 obj = Object("OriginalName")
 history = CommandHistory()
 
 rename_cmd = RenameCommand(obj, "NewName")
 history.execute_command(rename_cmd)
 print(obj.name) # 輸出: NewName
 
 history.undo_last_command()
 print(obj.name) # 輸出: OriginalName

Plantuml責任鏈模式流程

圖表翻譯:

此圖示展示了責任鏈模式的事件處理流程。當事件產生後,首先由當前元件嘗試處理。如果處理成功,流程結束;如果無法處理,則檢查是否存在父元件。若有父元件,則將事件傳遞給父元件處理;若無父元件,則檢查是否存在預設處理方法。如果有預設處理,則執行預設處理;否則,流程結束。

命令模式(Command Pattern)在檔案操作中的應用

命令模式是一種行為設計模式,它允許將請求封裝成物件,從而使客戶端能夠使用不同的請求、佇列或日誌請求來引數化其他物件。命令模式還支援可復原的操作。在本篇文章中,我們將探討命令模式在檔案操作中的應用,包括建立檔案、讀取檔案內容和重新命名檔案。

命令模式的優點

使用命令模式的主要優點包括:

  • 將請求封裝成物件,使得請求可以被佇列化或日誌化。
  • 支援可復原的操作,使得操作可以被復原或重做。
  • 提高了程式碼的可擴充套件性和可維護性。

實作命令模式

要實作命令模式,我們需要定義一個命令介面或抽象類別,該介面或抽象類別包含 execute()undo() 方法。然後,我們可以建立具體的命令類別來實作該介面或繼承該抽象類別。

重新命名檔案命令

首先,我們定義一個 RenameFile 類別來實作重新命名檔案的操作。該類別包含 __init__()execute()undo() 方法。

import logging
import os

logging.basicConfig(level=logging.DEBUG)

class RenameFile:
    def __init__(self, src, dest):
        self.src = src
        self.dest = dest

    def execute(self):
        logging.info(f"[重新命名 '{self.src}' 為 '{self.dest}']")
        os.rename(self.src, self.dest)

    def undo(self):
        logging.info(f"[重新命名 '{self.dest}' 回 '{self.src}']")
        os.rename(self.dest, self.src)

建立檔案命令

接下來,我們定義一個 CreateFile 類別來實作建立檔案的操作。該類別包含 __init__()execute()undo() 方法。

class CreateFile:
    def __init__(self, path, txt="hello world\n"):
        self.path = path
        self.txt = txt

    def execute(self):
        logging.info(f"[建立檔案 '{self.path}']")
        with open(self.path, "w", encoding="utf-8") as out_file:
            out_file.write(self.txt)

    def undo(self):
        logging.info(f"刪除檔案 {self.path}")
        os.remove(self.path)

讀取檔案命令

最後,我們定義一個 ReadFile 類別來實作讀取檔案內容的操作。該類別包含 __init__()execute() 方法。

class ReadFile:
    def __init__(self, path):
        self.path = path

    def execute(self):
        logging.info(f"[讀取檔案 '{self.path}']")
        with open(self.path, "r", encoding="utf-8") as in_file:
            print(in_file.read(), end="")

使用命令模式

main() 函式中,我們建立了一系列命令物件並將它們新增到一個列表中。然後,我們遍歷該列表並執行每個命令。

def main():
    orig_name, new_name = "file1", "file2"
    commands = (
        CreateFile(orig_name),
        ReadFile(orig_name),
        RenameFile(orig_name, new_name),
    )
    for c in commands:
        c.execute()

    answer = input("是否復原已執行的命令? [y/n] ")
    if answer not in "yY":
        print(f"結果是 {new_name}")
        exit()

    for c in reversed(commands):
        try:
            c.undo()
        except AttributeError as e:
            logging.error(str(e))

命令模式的應用場景

命令模式在許多應用場景中都非常有用,例如:

  • 圖形使用者介面(GUI)中的按鈕和選單項。
  • 事務性行為和日誌記錄。
  • 巨集(Macros)。

透過使用命令模式,我們可以將請求封裝成物件,從而提高程式碼的可擴充套件性和可維護性。

程式流程圖

圖表翻譯:

此圖示展示了檔案操作的流程。首先,系統會檢查檔案是否存在。如果檔案存在,系統會讀取檔案內容;如果檔案不存在,系統會建立一個新檔案。然後,無論檔案是否存在,系統都會進行重新命名檔案的操作。最後,流程結束。

觀察者模式(The Observer Pattern)深度解析與實務應用

觀察者模式是一種重要的行為設計模式,用於建立物件之間的發布-訂閱關係。在此模式中,一個物件(稱為主題或可觀察物件)會通知多個依賴它的物件(稱為觀察者),當其狀態發生變化時。

觀察者模式的核心概念

  1. 主題(Subject):維護觀察者列表並在狀態變化時通知它們
  2. 觀察者(Observer):定義一個更新介面,以便在主題狀態變化時接收通知
  3. 具體主題(Concrete Subject):實作主題介面,維護具體狀態並在狀態變化時通知觀察者
  4. 具體觀察者(Concrete Observer):實作觀察者介面,定義如何對主題的狀態變化做出反應

觀察者模式的實務應用場景

1. 即時天氣監測系統

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title 行為設計模式責任鏈與命令模式

package "物件導向程式設計" {
    package "核心概念" {
        component [類別 Class] as class
        component [物件 Object] as object
        component [屬性 Attribute] as attr
        component [方法 Method] as method
    }

    package "三大特性" {
        component [封裝
Encapsulation] as encap
        component [繼承
Inheritance] as inherit
        component [多型
Polymorphism] as poly
    }

    package "設計原則" {
        component [SOLID] as solid
        component [DRY] as dry
        component [KISS] as kiss
    }
}

class --> object : 實例化
object --> attr : 資料
object --> method : 行為
class --> encap : 隱藏內部
class --> inherit : 擴展功能
inherit --> poly : 覆寫方法
solid --> dry : 設計模式

note right of solid
  S: 單一職責
  O: 開放封閉
  L: 里氏替換
  I: 介面隔離
  D: 依賴反轉
end note

@enduml

圖表翻譯:

此圖示展示了一個根據觀察者模式的天氣監測系統架構。天氣站作為主題,當資料更新時,會通知所有註冊的觀察者(如顯示裝置、天氣應用程式等)。每個觀察者接收到更新通知後,會根據收到的資料執行相應的更新動作。

程式碼實作:天氣監測系統

1. 定義觀察者介面

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, temperature: float, humidity: float, pressure: float) -> None:
        """更新觀察者狀態"""
        pass

2. 實作天氣站主題類別

class WeatherStation:
    def __init__(self):
        self.observers = []
        self.temperature = None
        self.humidity = None
        self.pressure = None

    def register_observer(self, observer: Observer) -> None:
        """註冊觀察者"""
        self.observers.append(observer)

    def remove_observer(self, observer: Observer) -> None:
        """移除觀察者"""
        self.observers.remove(observer)

    def notify_observers(self) -> None:
        """通知所有觀察者"""
        for observer in self.observers:
            observer.update(self.temperature, self.humidity, self.pressure)

    def set_weather_data(self, temperature: float, humidity: float, pressure: float) -> None:
        """設定天氣資料並通知觀察者"""
        self.temperature = temperature
        self.humidity = humidity
        self.pressure = pressure
        self.notify_observers()

3. 實作具體觀察者

class DisplayDevice(Observer):
    def __init__(self, name: str):
        self.name = name

    def update(self, temperature: float, humidity: float, pressure: float) -> None:
        """更新顯示裝置的資料"""
        print(f"{self.name} 顯示裝置")
        print(f" - 溫度:{temperature}°C")
        print(f" - 濕度:{humidity}%")
        print(f" - 氣壓:{pressure}hPa")

class WeatherApp(Observer):
    def __init__(self, name: str):
        self.name = name

    def update(self, temperature: float, humidity: float, pressure: float) -> None:
        """更新天氣應用程式的資料"""
        print(f"{self.name} 天氣應用程式 - 天氣更新")
        print(f"溫度:{temperature}°C,濕度:{humidity}%,氣壓:{pressure}hPa")

4. 主程式執行範例

def main():
    # 建立天氣站物件
    weather_station = WeatherStation()

    # 建立並註冊觀察者
    living_room_display = DisplayDevice("客廳")
    bedroom_display = DisplayDevice("臥室")
    mobile_app = WeatherApp("行動裝置")

    weather_station.register_observer(living_room_display)
    weather_station.register_observer(bedroom_display)
    weather_station.register_observer(mobile_app)

    # 模擬天氣資料變化
    weather_station.set_weather_data(25.5, 60, 1013.2)
    print("-" * 50)
    weather_station.set_weather_data(26.0, 58, 1012.8)

if __name__ == "__main__":
    main()

內容解密:

  1. 觀察者模式實作了主題與觀察者之間的鬆散耦合
  2. 天氣站作為主題,維護觀察者列表並在資料變化時通知它們
  3. 不同的觀察者(如顯示裝置和天氣應用程式)實作了不同的更新邏輯
  4. 系統支援動態註冊和移除觀察者
  5. 程式碼具有良好的擴充套件性和可維護性

觀察者模式的優缺點分析

優點:

  1. 降低耦合度:主題與觀察者之間實作鬆散耦合
  2. 提高擴充套件性:可隨時新增或移除觀察者
  3. 支援廣播通訊:主題可一次通知多個觀察者

缺點:

  1. 可能造成效能問題:若觀察者過多,可能影響系統效能
  2. 通知順序不確定:觀察者接收通知的順序可能不一致
  3. 記憶體洩漏風險:若未正確移除觀察者,可能造成記憶體洩漏

最佳實踐建議

  1. 明確定義觀察者介面:確保所有觀察者實作統一的更新方法
  2. 使用弱參照管理觀察者:避免因觀察者未被正確移除而導致的記憶體洩漏
  3. 考慮使用非同步通知機制:提升系統的回應效能
  4. 建立完善的錯誤處理機制:處理觀察者更新過程中的異常情況