encapsulating-complexity
Hide implementation details behind interfaces - work at domain level (what), not implementation level (how)
来源信息
- 仓库
- obra/clank
- 最近来源活动
- 2025年10月7日 17:04
- 检测到的 SKILL.md 语言
- 英语
- 星标
- 41
- 分支
- 27
安装方式
默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。
检查来源文件
决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。
正在显示 SKILL.md
SKILL.md
来源说明 · 只读预览- name
- Encapsulating Complexity
- description
- Hide implementation details behind interfaces - work at domain level (what), not implementation level (how)
- when_to_use
- When designing any class or interface. When implementation details leak into public API. When storage format (JSON, SQL, files) is exposed. When working with raw data structures (dicts, rows) instead of domain objects. When client code must know HOW things work internally. When changing implementation would break client code. When database queries mixed with business logic. When switching storage type requires interface changes. When tests must know internal structure.
- version
- 1.0.0
- languages
- all
# Encapsulating Complexity
## Overview
Hide HOW things work. Expose only WHAT they do. Work at domain level (Users, Orders, Config), not implementation level (dicts, SQL rows, JSON files).
**Core principle:** The point of encapsulation is to create possibilities (many ways to implement) and restrict possibilities (one way to use). Implementation details hidden = free to change implementation without breaking clients.
**Violating the letter of this rule is violating the spirit of information hiding.**
## When to Use
**Apply to every class and interface:**
- Designing new classes/modules
- Creating public APIs
- Reviewing code for abstraction leaks
- Refactoring to improve maintainability
**Warning signs you're violating encapsulation:**
- Client code knows storage format (JSON, SQL, files)
- Interface exposes database/file operations
- Returns raw dicts/rows instead of domain objects
- Client code constructs SQL queries or file paths
- Changing from JSON to YAML breaks client code
- Switching databases requires changing all callers
- Tests must know internal data structures
- Method names reveal implementation (saveToJSON, queryDatabase)
- Public fields exposing internal state
## The Encapsulation Test
**Ask these questions about every public method/field:**
1. **Does the interface expose HOW or WHAT?**
- "Get user" = WHAT (good)
- "SELECT * FROM users" = HOW (bad)
2. **Can I change implementation without breaking clients?**
- JSON → YAML: should work
- PostgreSQL → MongoDB: should work
- If clients break: encapsulation violated
3. **Do clients work at domain level or implementation level?**
- Domain: `user.email`, `config.get_timeout()`
- Implementation: `row[2]`, `json_data['timeout_ms']`
4. **Do return values expose internals?**
- Domain: `User` object
- Implementation: `dict` with database column names
**If answers reveal implementation details → encapsulation violated.**
## Core Pattern: Hide the How
### Before (Implementation Exposed)
```python
class ConfigManager:
def __init__(self, json_path):
self.json_path = json_path # ❌ Exposes JSON
self._data = {}
def get_value(self, key):
return self._data.get(key) # ⚠️ Returns raw value
def save_to_json(self): # ❌ "JSON" in method name
with open(self.json_path, 'w') as f:
json.dump(self._data, f)
```
**Client code:**
```python
config = ConfigManager("/path/to/config.json") # Must know it's JSON
timeout = config.get_value("timeout_ms") # Must know exact key format
config.save_to_json() # Tied to JSON format
```
**If you switch JSON → YAML:** Client code breaks. Method names wrong. Constructor signature wrong.
### After (Implementation Hidden)
```python
class Config:
def __init__(self, config_file): # ✅ No format specified
self._storage = self._load(config_file) # ✅ Implementation hidden
def get_timeout(self): # ✅ Domain method, not raw key access
return self._storage.get("timeout_ms", 5000)
def set_timeout(self, seconds): # ✅ Domain operation
self._storage["timeout_ms"] = seconds * 1000 # ms internally
def save(self): # ✅ No "JSON" in name
self._persist(self._storage)
def _load(self, config_file): # ✅ Private - can change format
# Could load JSON, YAML, TOML, etc.
pass
def _persist(self, data): # ✅ Private - implementation detail
# Format hidden from clients
pass
```
**Client code:**
```python
config = Config("app.config") # Format agnostic
timeout = config.get_timeout() # Domain method (seconds)
config.set_timeout(10)
config.save()
```
**Switch JSON → YAML:** Client code unchanged. Just change `_load()` and `_persist()`.
## Domain Level vs Implementation Level
**Work at the problem domain, not the solution domain:**
### Example 1: User Management
❌ **Implementation Level:**
```python
class UserManager:
def get_user_row(self, user_id):
# Returns database row as dict
cur.execute("SELECT * FROM users WHERE id = %s", (user_id,))
return dict(cur.fetchone()) # ❌ Raw row dict
def update_user_column(self, user_id, column, value):
# ❌ Exposes database columns
cur.execute(f"UPDATE users SET {column} = %s WHERE id = %s", ...)
```
**Client must know:**
- Database schema (column names)
- SQL concepts (rows, columns)
- Data types in database
✅ **Domain Level:**
```python
class UserRepository:
def get_user(self, user_id: str) -> User:
# Returns domain object
row = self._query_user(user_id)
return User.from_storage(row) # ✅ Domain object
def update_email(self, user_id: str, new_email: str) -> None:
# ✅ Domain operation, no column names
user = self.get_user(user_id)
user.email = new_email
self._persist_user(user)
class User:
def __init__(self, id, email, name):
self.id = id
self.email = email
self.name = name
```
**Client knows:**
- Users (domain concept)
- Email (domain field)
- Nothing about database
### Example 2: Configuration
❌ **Implementation Level:**
```python
class ConfigManager:
def get(self, key):
return self._config.get(key) # ❌ Must know exact key names
# Client code
timeout = config.get("server.timeout_milliseconds") # ❌ Internal structure
port = config.get("server.port")
```
✅ **Domain Level:**
```python
class Config:
def server_timeout(self): # ✅ Domain method
return self._get("server.timeout_ms", 5000) / 1000 # Hide ms→sec
def server_port(self): # ✅ Domain method
return self._get("server.port", 8080)
# Client code
timeout = config.server_timeout() # ✅ Clean domain API
port = config.server_port()
```
## Separation of Concerns
**Don't mix persistence with domain logic:**
❌ **Mixed (From Baseline Test):**
```python
class ReportGenerator:
def generate(self, start_date, end_date):
# Database access
conn = psycopg2.connect(...)
cursor.execute("SELECT ...")
rows = cursor.fetchall()
# Business logic
total = sum(row[3] for row in rows)
# HTML formatting
html = f"<html>...</html>"
# File I/O
with open(f"report_{start_date}.html", "w") as f:
f.write(html)
```
**4 concerns mixed: database, calculation, formatting, file I/O**
✅ **Separated:**
```python
class SalesRepository:
"""Concern: Data access"""
def get_sales(self, start_date, end_date) -> List[Sale]:
# Database logic hidden here
pass
class SalesCalculator:
"""Concern: Business logic"""
def calculate_metrics(self, sales: List[Sale]) -> SalesMetrics:
# Pure calculation, no database/file knowledge
pass
class HTMLFormatter:
"""Concern: Presentation"""
def format_report(self, metrics: SalesMetrics) -> str:
# HTML generation, no database/calculation knowledge
pass
class ReportGenerator:
"""Concern: Orchestration"""
def __init__(self, repo, calculator, formatter):
self._repo = repo
self._calculator = calculator
self._formatter = formatter
def generate(self, start_date, end_date, output_path):
sales = self._repo.get_sales(start_date, end_date) # What
metrics = self._calculator.calculate_metrics(sales) # What
html = self._formatter.format_report(metrics) # What
with open(output_path, "w") as f:
f.write(html)
```
**Each class can change independently. Database switch doesn't touch formatting. HTML→PDF doesn't touch database.**
## What to Hide
### Hide Storage Format
❌ **Exposed:**
```python
class DataStore:
def load_from_json(self): # ❌ "JSON" in name
pass
def save_to_json(self): # ❌ Tied to JSON
pass
```
✅ **Hidden:**
```python
class DataStore:
def load(self): # ✅ Format agnostic
self._read_storage() # Private - can be JSON/YAML/SQL
def save(self): # ✅ Format agnostic
self._write_storage() # Private - implementation hidden
```
### Hide Database Details
❌ **Exposed:**
```python
def get_users(self):
cur.execute("SELECT * FROM users")
return [dict(row) for row in cur.fetchall()] # ❌ Raw database rows
```
✅ **Hidden:**
```python
def get_users(self) -> List[User]:
rows = self._query_all_users() # SQL hidden in private method
return [User.from_row(row) for row in rows] # ✅ Domain objects
```
### Hide Data Structures
❌ **Exposed:**
```python
class UserCache:
def __init__(self):
self.users_dict = {} # ❌ Public dict
# Client code manipulates dict directly
cache.users_dict[user_id] = user_data
```
✅ **Hidden:**
```python
class UserCache:
def __init__(self):
self._storage = {} # ✅ Private implementation
def add_user(self, user): # ✅ Domain operation
self._storage[user.id] = user
def get_user(self, user_id): # ✅ Domain operation
return self._storage.get(user_id)
```
**Internal dict can become Redis, Memcached, or database without breaking clients.**
### Hide Algorithms and Complexity
❌ **Exposed:**
```python
def sort_users_by_name(users):
# ❌ Client must understand sorting implementation
# Uses quicksort internally
return quicksort(users, key=lambda u: u.name)
```
✅ **Hidden:**
```python
def get_users_sorted_by_name(users):
# ✅ How it sorts is hidden - could be quicksort, mergesort, timsort
return sorted(users, key=lambda u: u.name)
```
## Interface Design Principles
### 1. Name by What, Not How
❌ **How (implementation exposed):**
- `save_to_json()`, `load_from_yaml()`, `write_to_file()`
- `execute_sql_query()`, `get_database_connection()`
- `parse_json_response()`, `build_xml_request()`
✅ **What (implementation hidden):**
- `save()`, `load()`, `persist()`
- `get_users()`, `find_by_email()`
- `send_request()`, `get_response()`
### 2. Accept and Return Domain Objects
❌ **Raw data structures:**
```python
def create_user(self, user_dict: dict) -> dict:
# ❌ Accepts and returns dicts
# Client must know dict structure
```
✅ **Domain objects:**
```python
def create_user(self, email: str, name: str) -> User:
# ✅ Clear parameters, returns domain object
# Internal structure hidden
```
### 3. Work at Single Abstraction Level
❌ **Mixed levels:**
```python
def process_order(self, order):
validate_order(order) # High level
# ❌ Low-level SQL mixed in
conn.execute("INSERT INTO orders VALUES (%s, %s)", ...)
send_confirmation(order) # High level
```
✅ **Consistent level:**
```python
def process_order(self, order):
# All high level - persistence hidden
validate_order(order)
saved_order = self._repository.save(order) # Hides SQL
send_confirmation(saved_order)
```
## Common Violations from Baseline Testing
### Violation 1: Exposing Storage Format
**Baseline:** ConfigManager exposes JSON in interface, UserManager exposes SQL.
❌ **What agents naturally do:**
```python
class ConfigManager:
def __init__(self, json_path): # Exposes JSON
self.json_path = json_path # Public attribute
def save(self):
with open(self.json_path, 'w') as f:
json.dump(self._config, f) # JSON logic in public view
```
✅ **Hide format:**
```python
class Config:
def __init__(self, config_file): # ✅ Format neutral
self._loader = self._create_loader(config_file) # Private
self._data = self._loader.load() # Hidden
def save(self):
self._loader.persist(self._data) # ✅ Format hidden
def _create_loader(self, config_file):
# Private - can support JSON, YAML, TOML, etc.
在 GitHub 查看这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看