Python 支援物件導向程式設計,但並不強制使用。本章介紹何時該使用類別,以及如何設計清晰的類別介面。

何時使用類別?

使用類別的情況

封裝狀態和行為

1class MarkdownLinkChecker:
2    def __init__(self, project_root):
3        self.project_root = Path(project_root)
4
5    def check_file(self, path):
6        ...
7
8    def check_directory(self, path):
9        ...

需要多個實例

1checker1 = MarkdownLinkChecker("/project1")
2checker2 = MarkdownLinkChecker("/project2")

有複雜的初始化邏輯

使用函式的情況

無狀態的操作

1def run_git_command(args):
2    # 不需要保存狀態
3    ...

簡單的工具函式

1def is_protected_branch(branch):
2    return branch in PROTECTED_BRANCHES

實際範例:Hook 驗證器

來自 .claude/lib/hook_validator.py

 1class HookValidator:
 2    """Hook 合規性驗證器"""
 3
 4    # 類別常數
 5    HOOK_IO_PATTERNS = [
 6        r"from\s+hook_io\s+import",
 7        r"from\s+lib\.hook_io\s+import",
 8    ]
 9
10    def __init__(self, project_root: Optional[str] = None):
11        """
12        初始化驗證器
13
14        Args:
15            project_root: 專案根目錄
16        """
17        if project_root is None:
18            project_root = os.environ.get(
19                "CLAUDE_PROJECT_DIR",
20                os.getcwd()
21            )
22        self.project_root = Path(project_root)
23
24    def validate_hook(self, hook_path: str) -> ValidationResult:
25        """驗證單個 Hook 檔案"""
26        hook_path = self._resolve_path(hook_path)
27        # ... 驗證邏輯 ...
28        return ValidationResult(...)
29
30    def validate_all_hooks(self, hooks_dir: Optional[str] = None):
31        """驗證所有 Hook 檔案"""
32        ...
33
34    # 私有方法
35    def _resolve_path(self, path: str) -> Path:
36        """解析路徑為絕對路徑"""
37        p = Path(path)
38        if p.is_absolute():
39            return p
40        return self.project_root / p

設計分析

元素說明
類別常數HOOK_IO_PATTERNS - 共用的配置
實例變數self.project_root - 每個實例可能不同
公開方法validate_hook(), validate_all_hooks()
私有方法_resolve_path() - 內部使用

類別設計原則

1. 單一職責原則(SRP)

每個類別只負責一件事:

 1# 好:每個類別有單一職責
 2class MarkdownLinkChecker:
 3    """只負責檢查 Markdown 連結"""
 4    def check_file(self, path): ...
 5    def check_directory(self, path): ...
 6
 7class HookValidator:
 8    """只負責驗證 Hook"""
 9    def validate_hook(self, path): ...
10    def validate_all_hooks(self): ...
11
12# 不好:一個類別做太多事
13class FileProcessor:
14    def check_links(self): ...
15    def validate_hooks(self): ...
16    def format_code(self): ...
17    def run_tests(self): ...

2. 封裝

隱藏內部實作,只暴露必要的介面:

 1class MarkdownLinkChecker:
 2    # 私有常數(Python 慣例用底線)
 3    _EXTERNAL_PATTERNS = [r'^https?://', r'^mailto:']
 4
 5    def __init__(self, project_root):
 6        # 私有屬性
 7        self._project_root = Path(project_root)
 8
 9    # 公開方法
10    def check_file(self, path):
11        return self._do_check(path)
12
13    # 私有方法
14    def _do_check(self, path):
15        ...
16
17    def _is_external_link(self, target):
18        ...

3. 明確的初始化

__init__ 應該完成所有必要的初始化:

 1class HookValidator:
 2    def __init__(self, project_root: Optional[str] = None):
 3        # 處理預設值
 4        if project_root is None:
 5            project_root = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
 6
 7        # 初始化所有實例變數
 8        self.project_root = Path(project_root)
 9
10        # 不要在這裡做複雜的 I/O 操作

實際範例:Markdown 連結檢查器

來自 .claude/lib/markdown_link_checker.py

 1class MarkdownLinkChecker:
 2    """Markdown 連結檢查器"""
 3
 4    # 編譯好的正則表達式(類別常數)
 5    INLINE_LINK_PATTERN = re.compile(
 6        r'(?<!!)\[([^\]]+)\]\(([^)]+)\)'
 7    )
 8
 9    EXTERNAL_PATTERNS = [
10        r'^https?://',
11        r'^mailto:',
12        r'^tel:',
13    ]
14
15    def __init__(self, project_root: Optional[str] = None):
16        if project_root is None:
17            project_root = os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())
18        self.project_root = Path(project_root)
19
20    def check_file(self, file_path: str) -> LinkCheckResult:
21        """檢查單個 Markdown 檔案"""
22        file_path = self._resolve_path(file_path)
23
24        if not file_path.exists():
25            return self._create_error_result(file_path, "檔案不存在")
26
27        content = file_path.read_text(encoding="utf-8")
28        links = self.parse_markdown_links(content)
29        broken_links = self._check_links(links, file_path.parent)
30
31        return LinkCheckResult(
32            file_path=str(file_path),
33            total_links=len(links),
34            broken_links=broken_links
35        )
36
37    def check_directory(self, dir_path: str, recursive=True):
38        """檢查目錄下所有 Markdown 檔案"""
39        ...
40
41    def parse_markdown_links(self, content: str) -> list[dict]:
42        """解析 Markdown 中的連結"""
43        ...
44
45    # === 私有方法 ===
46
47    def _resolve_path(self, path: str) -> Path:
48        p = Path(path)
49        return p if p.is_absolute() else self.project_root / p
50
51    def _is_external_link(self, target: str) -> bool:
52        return any(re.match(p, target) for p in self.EXTERNAL_PATTERNS)
53
54    def _create_error_result(self, path, message):
55        ...

類別與模組函式的搭配

提供方便的模組層級函式:

 1# markdown_link_checker.py
 2
 3class MarkdownLinkChecker:
 4    """類別實作"""
 5    ...
 6
 7# 方便使用的模組函式
 8def check_markdown_links(file_path: str, project_root: Optional[str] = None):
 9    """
10    檢查單個檔案(便捷函式)
11
12    Example:
13        result = check_markdown_links("docs/README.md")
14    """
15    checker = MarkdownLinkChecker(project_root)
16    return checker.check_file(file_path)
17
18def check_directory(dir_path: str, project_root: Optional[str] = None):
19    """檢查目錄(便捷函式)"""
20    checker = MarkdownLinkChecker(project_root)
21    return checker.check_directory(dir_path)

文檔字串

類別文檔

 1class HookValidator:
 2    """
 3    Hook 合規性驗證器
 4
 5    驗證 Hook 腳本是否遵循專案規範,包含:
 6    - 共用模組導入檢查
 7    - 輸出格式檢查
 8    - 測試存在性檢查
 9
10    Attributes:
11        project_root: 專案根目錄路徑
12
13    Example:
14        validator = HookValidator()
15        result = validator.validate_hook("my_hook.py")
16        if not result.is_compliant:
17            print(result.issues)
18    """

方法文檔

 1def validate_hook(self, hook_path: str) -> ValidationResult:
 2    """
 3    驗證單個 Hook 檔案
 4
 5    Args:
 6        hook_path: Hook 檔案路徑(相對或絕對)
 7
 8    Returns:
 9        ValidationResult: 驗證結果
10
11    Raises:
12        FileNotFoundError: 如果檔案不存在
13
14    Example:
15        result = validator.validate_hook(".claude/hooks/my_hook.py")
16    """

最佳實踐

1. 優先使用組合而非繼承

1# 好:組合
2class HookValidator:
3    def __init__(self):
4        self.link_checker = MarkdownLinkChecker()
5        self.config_loader = ConfigLoader()
6
7# 避免:深層繼承
8class SpecialValidator(HookValidator, LinkChecker, ConfigLoader):
9    ...

2. 保持類別小巧

1# 如果類別太大,考慮拆分
2class ValidationEngine:
3    def __init__(self):
4        self.import_checker = ImportChecker()
5        self.format_checker = FormatChecker()
6        self.test_checker = TestChecker()

3. 使用有意義的命名

1# 好
2class MarkdownLinkChecker:
3    def check_file(self, path): ...
4
5# 不好
6class Checker:
7    def do_it(self, x): ...

思考題

  1. 什麼時候應該使用類別,什麼時候應該使用函式?
  2. _method__method 有什麼區別?
  3. 為什麼 Hook 系統同時提供類別和便捷函式?

實作練習

  1. 將一組相關的函式重構為一個類別
  2. 為現有類別添加完整的文檔字串
  3. 實作一個遵循單一職責原則的驗證器類別

延伸閱讀(進階系列)


下一章:抽象基類 ABC