Class interfaces present one cohesive abstraction - don't mix domain logic with serialization, persistence, or unrelated concerns
A class interface should present ONE cohesive abstraction. All methods should work toward a consistent purpose at a consistent level.
Core principle: Each class implements one Abstract Data Type (ADT). If you can't identify what ADT the class implements, it has poor abstraction.
Goal: Anyone using the class should see a clear, consistent set of related operations, not a miscellaneous grab-bag.
Apply when designing any class:
Warning signs of poor abstraction:
Baseline violation:
class Employee:
def calculate_annual_salary(self): # ✅ Domain operation
return self.salary * 12
def update_department(self, dept): # ✅ Domain operation
self.department = dept
def to_json(self): # ❌ Serialization detail
return json.dumps({...})
def get_details(self): # ✅ Domain operation
return {...}
Problem: Employee is a domain concept. JSON is a serialization format. Mixing these means:
✅ Separate concerns:
class Employee:
"""Domain: Employee business logic only."""
def __init__(self, name, employee_id, department, salary):
self.name = name
self.employee_id = employee_id
self.department = department
self.salary = salary
def calculate_annual_salary(self): # Domain
return self.salary * 12
def update_department(self, dept): # Domain
self.department = dept
# Separate serializer
class EmployeeSerializer:
"""Concern: Serialization formats."""
@staticmethod
def to_json(employee: Employee) -> str:
return json.dumps({
'name': employee.name,
'id': employee.employee_id,
...
})
@staticmethod
def to_xml(employee: Employee) -> str:
# Can add XML without touching Employee
pass
Now: Employee knows nothing about formats. Add CSV/XML/Protobuf without changing Employee.
Baseline violation:
class Program:
"""Initialize application components."""
def _init_database(self): # Database concern
pass
def _setup_web_server(self): # Web concern
pass
def _start_background_jobs(self): # Jobs concern
pass
def _init_command_stack(self): # Command concern
pass
def _init_report_formatter(self): # Reports concern
pass
Problem: These are unrelated functions grouped because they happen at startup (temporal cohesion). The class has no consistent abstraction - it's a miscellaneous collection.
Code Complete specifically calls this out as poor abstraction.
✅ Each subsystem initializes itself:
class DatabaseSystem:
"""Abstraction: Database operations."""
def initialize(self):
# Database-specific initialization
pass
class WebServer:
"""Abstraction: Web serving."""
def start(self):
# Web server initialization
pass
class BackgroundJobManager:
"""Abstraction: Job processing."""
def start(self):
# Job system initialization
pass
# Coordinator stays high-level
class Application:
def __init__(self):
self.database = DatabaseSystem()
self.web_server = WebServer()
self.jobs = BackgroundJobManager()
def start(self):
# High-level orchestration
self.database.initialize()
self.web_server.start()
self.jobs.start()
Now: Each class has consistent abstraction. Program no longer a grab-bag.
Baseline violation:
class DataProcessor:
"""Mixes data access with statistics."""
def process_dataset(self, dataset_id):
# Loads from PostgreSQL (persistence concern)
values = self._load_dataset(dataset_id)
# Calculates statistics (business logic concern)
mean = statistics.mean(values)
# Two concerns in one class
Problem: Class does two things - data access AND statistics. If you switch from PostgreSQL to MongoDB, you must modify this class. If you change statistical algorithm, you modify same class.
✅ Separate concerns:
class DatasetRepository:
"""Abstraction: Dataset storage/retrieval."""
def get_dataset_values(self, dataset_id: str) -> list[float]:
# PostgreSQL details hidden here
# Can switch to MongoDB without affecting calculator
pass
class StatisticsCalculator:
"""Abstraction: Statistical computations."""
def calculate_metrics(self, values: list[float]) -> dict:
# Pure calculation, no database knowledge
mean = statistics.mean(values)
median = statistics.median(values)
# Returns statistics only
pass
class DataProcessor:
"""Abstraction: Orchestration."""
def __init__(self, repository, calculator):
self._repository = repository
self._calculator = calculator
def process_dataset(self, dataset_id: str) -> dict:
# High-level only - delegates to focused abstractions
values = self._repository.get_dataset_values(dataset_id)
return self._calculator.calculate_metrics(values)
Now: Each class has single, consistent abstraction. Database changes don't affect calculator. Algorithm changes don't affect repository.
For any class, ask:
What abstraction does this class represent?
Are all methods at same abstraction level?
Do methods belong together?
If answers reveal inconsistency → poor abstraction.
Unrelated things in one class:
class Utilities:
def validate_email(self): # Validation
def format_currency(self): # Formatting
def connect_database(self): # Database
No relationship. Avoid this.
Things done at same time:
class Startup:
def init_database(self): # Done at startup
def init_webserver(self): # Done at startup
def init_logging(self): # Done at startup
Related by WHEN not WHAT. Weak abstraction.
One clear purpose:
class EmployeeCalculations:
def calculate_annual_salary(self, employee):
def calculate_tax_withholding(self, employee):
def calculate_benefits_cost(self, employee):
All methods work toward one purpose. Best abstraction.
Always aim for functional cohesion.
| Violation | Example | Fix |
|-----------|---------|-----|
| Domain + Format mixed | Employee.to_json() | Separate Serializer class |
| Business + Persistence mixed | OrderProcessor with SQL | Separate Repository from logic |
| High + Low level mixed | Orchestration with SQL queries | Extract low-level to private/separate |
| Grab-bag class | Utilities, Program, Helpers | Split by actual purpose |
| Temporal cohesion | Startup class with unrelated inits | Each subsystem owns initialization |
class Employee:
"""Abstraction: Employee domain model."""
# All methods are employee operations
def calculate_annual_salary(self):
def update_department(self, dept):
def get_compensation_summary(self):
def is_eligible_for_bonus(self):
Consistent: All domain operations about employees.
class Employee:
"""Mixed abstraction - unclear purpose."""
def calculate_annual_salary(self): # Domain
def to_json(self): # Serialization
def save_to_database(self): # Persistence
def send_welcome_email(self): # Notification
Inconsistent: Domain + serialization + persistence + notifications.
Split when:
Example split:
# Before: One class, mixed abstractions
class UserService:
def create_user(self, email, name):
# Validation
if not self._is_valid_email(email):
raise ValueError()
# Create domain object
user = User(email, name)
# Persist to database
self._db.execute("INSERT INTO users ...")
# Send welcome email
self._smtp.send(email, "Welcome!")
return user
# After: Focused classes
class UserValidator:
"""Abstraction: Validation."""
def validate_registration(self, email, name):
pass
class UserRepository:
"""Abstraction: Persistence."""
def save_user(self, user):
pass
class UserNotifier:
"""Abstraction: Notifications."""
def send_welcome_email(self, user):
pass
class UserService:
"""Abstraction: Orchestration."""
def create_user(self, email, name):
self._validator.validate_registration(email, name)
user = User(email, name)
self._repository.save_user(user)
self._notifier.send_welcome_email(user)
return user
Class level:
Manager, Handler, Processor, Utility)Method level:
to_json)All of these mean: Improve abstraction consistency.
From baseline testing:
| Excuse | Reality | |--------|---------| | "Keeps everything in one place" | One place ≠ good organization. Split by purpose, not location. | | "It's just a coordinator" | Coordinators coordinate related things. Unrelated = grab-bag. | | "Easier than multiple classes" | Easier to write ≠ easier to maintain. Abstraction quality matters. | | "Production-ready code" | Working ≠ well-abstracted. Can be both. | | "All used during startup" | Temporal relationship is weak. Use functional relationships. | | "Serialization is part of the object" | No - serialization is a separate concern. External responsibility. |
For each class, verify:
If any "no" → split class or clarify abstraction.
From Code Complete:
From baseline testing:
With this skill: Separate concerns, maintain abstraction consistency, functional cohesion.
For single responsibility: See skills/coding/keeping-routines-focused - same principle applies to classes (one clear purpose)
For encapsulation: See skills/encapsulating-complexity - hiding implementation details supports abstraction consistency
For complexity: See skills/reducing-complexity - consistent abstractions reduce mental load
npx skills add obra/Maintaining Consistent Abstractions下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer