抽象基類(Abstract Base Class,ABC)用於定義介面契約,確保子類別實作必要的方法。這在建立可擴展的框架時特別有用。

為什麼需要抽象基類?

問題場景

假設我們有多種檔案解析器:

 1class JsonParser:
 2    def parse(self, content: str) -> dict:
 3        return json.loads(content)
 4
 5class YamlParser:
 6    def parse(self, content: str) -> dict:
 7        return yaml.safe_load(content)
 8
 9class XmlParser:
10    # 忘記實作 parse 方法!
11    def read(self, content: str) -> dict:  # 方法名稱錯誤
12        ...

問題:沒有強制的介面,容易出錯。

使用 ABC 解決

 1from abc import ABC, abstractmethod
 2
 3class BaseParser(ABC):
 4    """解析器抽象基類"""
 5
 6    @abstractmethod
 7    def parse(self, content: str) -> dict:
 8        """解析內容並返回字典"""
 9        pass
10
11class XmlParser(BaseParser):
12    # 如果沒有實作 parse,會報錯
13    def parse(self, content: str) -> dict:
14        ...

基本語法

定義抽象基類

 1from abc import ABC, abstractmethod
 2from typing import Optional
 3
 4class BaseParser(ABC):
 5    """解析器基類"""
 6
 7    def __init__(self, encoding: str = "utf-8"):
 8        self.encoding = encoding
 9
10    @abstractmethod
11    def parse(self, content: str) -> dict:
12        """
13        解析內容(子類別必須實作)
14
15        Args:
16            content: 要解析的內容
17
18        Returns:
19            dict: 解析後的資料
20        """
21        pass
22
23    @abstractmethod
24    def validate(self, content: str) -> bool:
25        """驗證內容格式(子類別必須實作)"""
26        pass
27
28    def parse_file(self, path: str) -> dict:
29        """
30        解析檔案(共用方法)
31
32        這是具體實作,子類別可以直接使用或覆寫。
33        """
34        with open(path, encoding=self.encoding) as f:
35            content = f.read()
36        return self.parse(content)

實作子類別

 1class JsonParser(BaseParser):
 2    """JSON 解析器"""
 3
 4    def parse(self, content: str) -> dict:
 5        return json.loads(content)
 6
 7    def validate(self, content: str) -> bool:
 8        try:
 9            json.loads(content)
10            return True
11        except json.JSONDecodeError:
12            return False
13
14class YamlParser(BaseParser):
15    """YAML 解析器"""
16
17    def parse(self, content: str) -> dict:
18        return yaml.safe_load(content) or {}
19
20    def validate(self, content: str) -> bool:
21        try:
22            yaml.safe_load(content)
23            return True
24        except yaml.YAMLError:
25            return False

抽象屬性

除了方法,也可以定義抽象屬性:

 1from abc import ABC, abstractmethod
 2
 3class BaseParser(ABC):
 4
 5    @property
 6    @abstractmethod
 7    def file_extension(self) -> str:
 8        """支援的檔案副檔名"""
 9        pass
10
11    @property
12    @abstractmethod
13    def mime_type(self) -> str:
14        """MIME 類型"""
15        pass
16
17class JsonParser(BaseParser):
18
19    @property
20    def file_extension(self) -> str:
21        return ".json"
22
23    @property
24    def mime_type(self) -> str:
25        return "application/json"

範本方法模式

ABC 常與範本方法模式搭配使用:

 1from abc import ABC, abstractmethod
 2
 3class DataProcessor(ABC):
 4    """資料處理器基類"""
 5
 6    def process(self, data: str) -> dict:
 7        """
 8        範本方法:定義處理流程
 9
10        1. 驗證
11        2. 前處理
12        3. 解析
13        4. 後處理
14        """
15        # 步驟 1:驗證(子類別實作)
16        if not self.validate(data):
17            raise ValueError("Invalid data")
18
19        # 步驟 2:前處理(可選覆寫)
20        data = self.preprocess(data)
21
22        # 步驟 3:解析(子類別實作)
23        result = self.parse(data)
24
25        # 步驟 4:後處理(可選覆寫)
26        return self.postprocess(result)
27
28    @abstractmethod
29    def validate(self, data: str) -> bool:
30        """驗證資料(子類別必須實作)"""
31        pass
32
33    @abstractmethod
34    def parse(self, data: str) -> dict:
35        """解析資料(子類別必須實作)"""
36        pass
37
38    def preprocess(self, data: str) -> str:
39        """前處理(預設不做任何處理)"""
40        return data
41
42    def postprocess(self, result: dict) -> dict:
43        """後處理(預設不做任何處理)"""
44        return result

實作檢查

無法直接實例化

1class BaseParser(ABC):
2    @abstractmethod
3    def parse(self, content: str) -> dict:
4        pass
5
6# 錯誤:無法實例化抽象類別
7parser = BaseParser()  # TypeError!

必須實作所有抽象方法

1class IncompleteParser(BaseParser):
2    # 沒有實作 parse 方法
3    pass
4
5# 錯誤:無法實例化
6parser = IncompleteParser()  # TypeError!

檢查子類別關係

 1from abc import ABC
 2
 3class BaseParser(ABC):
 4    pass
 5
 6class JsonParser(BaseParser):
 7    pass
 8
 9# 使用 isinstance 和 issubclass
10parser = JsonParser()
11isinstance(parser, BaseParser)     # True
12issubclass(JsonParser, BaseParser) # True

與 Protocol 的比較

Python 3.8+ 引入了 Protocol,提供結構化子型別(Structural Subtyping):

ABC(名義子型別)

 1from abc import ABC, abstractmethod
 2
 3class Parseable(ABC):
 4    @abstractmethod
 5    def parse(self, content: str) -> dict:
 6        pass
 7
 8# 必須明確繼承
 9class JsonParser(Parseable):  # 必須繼承 Parseable
10    def parse(self, content: str) -> dict:
11        return json.loads(content)

Protocol(結構化子型別)

 1from typing import Protocol
 2
 3class Parseable(Protocol):
 4    def parse(self, content: str) -> dict:
 5        ...
 6
 7# 不需要繼承,只要有相同的方法
 8class JsonParser:  # 不需要繼承
 9    def parse(self, content: str) -> dict:
10        return json.loads(content)
11
12def process(parser: Parseable) -> None:
13    parser.parse("...")
14
15# JsonParser 自動符合 Parseable 協議
16process(JsonParser())  # OK

何時使用哪個?

特性ABCProtocol
需要繼承
執行時檢查支援有限支援
可以有共用方法是(3.8+)
適合場景框架設計型別提示

最佳實踐

1. 保持介面簡潔

 1# 好:專注於核心方法
 2class BaseParser(ABC):
 3    @abstractmethod
 4    def parse(self, content: str) -> dict:
 5        pass
 6
 7# 不好:太多抽象方法
 8class BaseParser(ABC):
 9    @abstractmethod
10    def parse(self): pass
11    @abstractmethod
12    def validate(self): pass
13    @abstractmethod
14    def preprocess(self): pass
15    @abstractmethod
16    def postprocess(self): pass
17    @abstractmethod
18    def format(self): pass

2. 提供有用的預設實作

1class BaseParser(ABC):
2    @abstractmethod
3    def parse(self, content: str) -> dict:
4        pass
5
6    # 有預設實作的方法
7    def parse_file(self, path: str) -> dict:
8        with open(path) as f:
9            return self.parse(f.read())

3. 清晰的文檔

 1class BaseParser(ABC):
 2    """
 3    解析器抽象基類
 4
 5    所有解析器都應繼承此類別並實作 parse 方法。
 6
 7    Example:
 8        class MyParser(BaseParser):
 9            def parse(self, content: str) -> dict:
10                return {"data": content}
11    """
12
13    @abstractmethod
14    def parse(self, content: str) -> dict:
15        """
16        解析內容
17
18        Args:
19            content: 要解析的字串內容
20
21        Returns:
22            dict: 解析後的字典
23
24        Raises:
25            ParseError: 如果解析失敗
26        """
27        pass

思考題

  1. 什麼時候應該使用 ABC,什麼時候使用 Protocol?
  2. 抽象方法可以有實作嗎?有什麼用途?
  3. 如何測試抽象基類?

實作練習

  1. 設計一個 BaseValidator 抽象基類,定義驗證器的介面
  2. 實作兩個繼承自 BaseValidator 的具體驗證器
  3. 使用範本方法模式實作一個多步驟的資料處理流程

延伸閱讀(進階系列)


上一章:類別設計原則 下一章:工廠模式