4.1 類別設計原則
4.1 類別設計原則
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): ...思考題
- 什麼時候應該使用類別,什麼時候應該使用函式?
_method和__method有什麼區別?- 為什麼 Hook 系統同時提供類別和便捷函式?
實作練習
- 將一組相關的函式重構為一個類別
- 為現有類別添加完整的文檔字串
- 實作一個遵循單一職責原則的驗證器類別
延伸閱讀(進階系列)
下一章:抽象基類 ABC