440_Clean Architecture整洁架构¶
一句话说明¶
整洁架构是 Robert C. Martin 提出的分层架构思想:业务规则在中心,外部细节(数据库、UI、框架)在外层,依赖方向只能从外向内,核心业务逻辑不受任何技术选型影响。
核心知识点¶
白话理解¶
想象洋葱:最里面是最核心的业务规则(纯粹的逻辑),外面套着用例层,再外面是接口适配层,最外面才是数据库/Web框架这些"脏活"。洋葱的特点是:外层可以知道内层,内层永远不知道外层是谁——这就是依赖规则。
四层结构¶
外层 → 内层(依赖方向)
┌─────────────────────────────┐
│ Frameworks & Drivers │ ← 数据库、Web框架、外部服务
│ ┌───────────────────────┐ │
│ │ Interface Adapters │ │ ← Controller、Presenter、Gateway
│ │ ┌─────────────────┐ │ │
│ │ │ Use Cases │ │ │ ← 业务用例、应用服务
│ │ │ ┌───────────┐ │ │ │
│ │ │ │ Entities │ │ │ │ ← 核心业务实体和规则(最稳定)
│ │ │ └───────────┘ │ │ │
│ │ └─────────────────┘ │ │
│ └───────────────────────┘ │
└─────────────────────────────┘
核心依赖规则¶
内层代码绝对不能依赖外层代码。数据库换了、框架换了,只要内层没变,业务逻辑完全不受影响。
经典题目与解法¶
题目1:用整洁架构实现"用户注册"功能¶
题意:实现用户注册,数据库可以随时替换,业务逻辑不受影响。
# ============ 第1层:实体层(Entity)最内层 ============
class User:
"""纯业务实体,不依赖任何框架或数据库"""
def __init__(self, user_id: str, username: str, email: str):
self.user_id = user_id
self.username = username
self.email = email
def is_valid_email(self) -> bool:
"""业务规则:邮件必须含@"""
return '@' in self.email
def __repr__(self):
return f"User({self.username}, {self.email})"
# ============ 第2层:用例层(Use Case)业务逻辑 ============
from abc import ABC, abstractmethod
import uuid
class UserRepository(ABC):
"""用例层定义接口(内层定义规范,外层实现)"""
@abstractmethod
def save(self, user: User) -> bool: pass
@abstractmethod
def find_by_email(self, email: str) -> User: pass
class RegisterUserUseCase:
"""用户注册用例——只有业务逻辑,不管数据怎么存"""
def __init__(self, repo: UserRepository):
self.repo = repo # 依赖注入,内层只知道接口
def execute(self, username: str, email: str) -> dict:
# 1. 创建实体并校验
user = User(str(uuid.uuid4()), username, email)
if not user.is_valid_email():
return {'success': False, 'error': '邮件格式无效'}
# 2. 检查邮件是否已注册
if self.repo.find_by_email(email):
return {'success': False, 'error': '邮件已被注册'}
# 3. 保存
self.repo.save(user)
return {'success': True, 'user_id': user.user_id}
# ============ 第3层:接口适配层(Interface Adapter)============
class RegisterUserController:
"""接收 HTTP 请求,调用用例,格式化响应"""
def __init__(self, use_case: RegisterUserUseCase):
self.use_case = use_case
def handle(self, request: dict) -> dict:
"""HTTP 请求处理入口"""
username = request.get('username', '')
email = request.get('email', '')
if not username or not email: # 参数校验
return {'status': 400, 'message': '参数缺失'}
result = self.use_case.execute(username, email) # 调用用例
if result['success']:
return {'status': 201, 'user_id': result['user_id']}
else:
return {'status': 400, 'message': result['error']}
# ============ 第4层:基础设施层(Framework/DB)最外层 ============
class InMemoryUserRepository(UserRepository):
"""内存实现(开发/测试用)"""
def __init__(self):
self._store = {} # 内存存储
def save(self, user: User) -> bool:
self._store[user.email] = user
return True
def find_by_email(self, email: str) -> User:
return self._store.get(email) # 返回None表示未找到
# ============ 组装(Composition Root)============
repo = InMemoryUserRepository() # 外层
use_case = RegisterUserUseCase(repo) # 中层
controller = RegisterUserController(use_case) # 接口层
# 模拟 HTTP 请求
print(controller.handle({'username': '张三', 'email': 'zhangsan@qq.com'}))
print(controller.handle({'username': '李四', 'email': 'zhangsan@qq.com'})) # 邮件已注册
print(controller.handle({'username': '王五', 'email': 'invalid-email'})) # 格式无效
时间复杂度:注册 O(1)(内存字典)
架构收益:换数据库只需替换 InMemoryUserRepository 实现,业务逻辑不动
题目2:整洁架构测试策略——用 Mock 测试用例层¶
# 用 Mock 仓储测试用例,不需要真实数据库
class MockUserRepository(UserRepository):
"""测试专用 Mock,可控制返回值"""
def __init__(self, existing_emails=None):
self.saved_users = []
self.existing_emails = existing_emails or [] # 预设已存在邮件
def save(self, user: User) -> bool:
self.saved_users.append(user) # 记录保存的用户
return True
def find_by_email(self, email: str) -> User:
if email in self.existing_emails:
return User("existing", "existing", email) # 模拟已存在
return None # 模拟不存在
# 单元测试
mock_repo = MockUserRepository(existing_emails=['taken@qq.com'])
use_case = RegisterUserUseCase(mock_repo)
# 正常注册
result = use_case.execute('张三', 'new@qq.com')
assert result['success'] == True
assert len(mock_repo.saved_users) == 1 # 确认用户被保存
# 重复邮件
result = use_case.execute('李四', 'taken@qq.com')
assert result['success'] == False
assert '已被注册' in result['error']
print("所有测试通过!")
面试技巧¶
- 画出依赖箭头:能在白板画出"外层→内层"箭头的候选人加分明显
- 举数据库替换场景:最直观的例子——从 MySQL 换 MongoDB,业务代码不动
- 与 MVC 对比:MVC 没有严格分层,View 可能直接访问数据库;整洁架构强制依赖规则
- 测试是动力:整洁架构最大优点是业务逻辑可纯粹单元测试,不需要启动数据库
- 不要过度设计:小项目不需要全套整洁架构,按需取用
速查表¶
| 层次 | 职责 | 依赖方向 | 可替换性 |
|---|---|---|---|
| Entities | 核心业务规则 | 无依赖 | 最稳定 |
| Use Cases | 业务用例/流程 | 依赖 Entities | 稳定 |
| Interface Adapters | 格式转换 | 依赖 Use Cases | 可替换 |
| Frameworks & Drivers | 具体技术实现 | 依赖 Adapters | 最易替换 |