Clean Architecture(整洁架构/干净架构)是由 Robert C. Martin(Bob 大叔) 在 2017 年提出的软件架构范式 ,其核心目标是通过分层设计和严格的依赖规则,构建高可维护性、高可测试性且与技术细节解耦的系统。
核心思想
同心圆分层结构
分层结构、关注点分离
将系统划分为独立的模块,每个模块负责单一职责(如业务逻辑、数据访问、界面展示)。通常分为四层:实体(Entities)、用例(Use Cases)、接口适配器(Interface Adapters)、框架与驱动(Frameworks & Drivers)。
架构由四个同心圆组成,从中心向外依次是:
| 层级 | 名称 | 职责 |
|---|---|---|
| 最内层 | Entities(实体层) | 核心业务对象、企业级业务规则,独立于应用 |
| 第二层 | Use Cases(用例层或者应用层) | 应用特定的业务逻辑,协调实体完成具体业务流程,如“创建订单”用例。调用领域模型的方法,但不直接操作数据库或 UI。 |
| 第三层 | Interface Adapters(接口适配层) | 将数据转换为最内层或最外层所需的格式(如 MVC 中的 Presenters、Controllers、Gateways) |
| 最外层 | Frameworks & Drivers(框架与驱动层) | 具体技术实现:UI 框架、数据库、Web 框架、外部设备等 |
依赖规则(The Dependency Rule)
代码依赖只能向内指向,外层依赖内层,内层绝不依赖外层。 核心业务逻辑(实体和用例)完全独立于UI、数据库、第三方服务等外部细节。
- 内层(实体、用例)不知道外层的存在
- 外层通过依赖倒置原则(DIP) 使用内层定义的接口
- 跨层传递的数据结构必须简单(DTO、基本类型),避免违反依赖规则
独立性
业务逻辑应完全独立于外部依赖(如数据库、网络),便于单元测试。
业务规则不依赖于UI、数据库、框架等,使得系统可以轻松更换技术栈,也便于测试和演进。
Demo
下面通过一个简单的用户注册功能示例,展示 Clean Architecture 的分层结构和依赖倒置原则。示例使用 Java 编写,不依赖任何框架,以便清晰体现核心思想。
项目结构
src/
├── entity/ // 企业级业务实体
│ └── User.java
├── usecase/ // 应用业务用例
│ ├── RegisterUserInput.java
│ ├── RegisterUser.java
│ └── RegisterUserInteractor.java
├── repository/ // 仓储接口(核心层定义)
│ └── UserRepository.java
├── data/ // 数据实现(外层,依赖接口)
│ └── InMemoryUserRepository.java
├── web/ // 接口适配器(外层)
│ └── RegisterUserController.java
└── Main.java // 组合启动类代码实现
实体层(Entity)
package entity;
public class User {
private String id;
private String username;
private String password; // 已加密的密码
public User(String id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
// 业务规则:密码加密(简化版,实际应使用BCrypt等)
public static String encryptPassword(String rawPassword) {
return "encrypted_" + rawPassword; // 仅为演示
}
// getters...
public String getId() { return id; }
public String getUsername() { return username; }
public String getPassword() { return password; }
}仓储接口(核心层定义)
package repository;
import entity.User;
import java.util.Optional;
public interface UserRepository {
void save(User user);
Optional<User> findByUsername(String username);
}用例层(Use Case)
输入模型(简单的数据传输对象)
package usecase;
public class RegisterUserInput {
private String username;
private String password;
public RegisterUserInput(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() { return username; }
public String getPassword() { return password; }
}用例接口(输入边界)
package usecase;
public interface RegisterUser {
void execute(RegisterUserInput input) throws Exception;
}用例实现(核心业务逻辑)
package usecase;
import entity.User;
import repository.UserRepository;
public class RegisterUserInteractor implements RegisterUser {
private final UserRepository userRepository;
// 依赖注入:只依赖接口,不依赖具体实现
public RegisterUserInteractor(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public void execute(RegisterUserInput input) throws Exception {
// 1. 检查用户名是否已存在
if (userRepository.findByUsername(input.getUsername()).isPresent()) {
throw new Exception("Username already exists");
}
// 2. 加密密码(业务规则)
String encryptedPassword = User.encryptPassword(input.getPassword());
// 3. 创建用户实体(通常ID由基础设施生成,这里简化用时间戳)
User user = new User(String.valueOf(System.currentTimeMillis()),
input.getUsername(),
encryptedPassword);
// 4. 保存
userRepository.save(user);
}
}数据实现(外层)
package data;
import entity.User;
import repository.UserRepository;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class InMemoryUserRepository implements UserRepository {
private final Map<String, User> store = new ConcurrentHashMap<>();
@Override
public void save(User user) {
store.put(user.getUsername(), user);
}
@Override
public Optional<User> findByUsername(String username) {
return Optional.ofNullable(store.get(username));
}
}接口适配器(Controller)
package web;
import usecase.RegisterUser;
import usecase.RegisterUserInput;
public class RegisterUserController {
private final RegisterUser registerUser;
public RegisterUserController(RegisterUser registerUser) {
this.registerUser = registerUser;
}
// 模拟HTTP POST /register 请求
public String handleRequest(String username, String password) {
try {
RegisterUserInput input = new RegisterUserInput(username, password);
registerUser.execute(input);
return "User registered successfully!";
} catch (Exception e) {
return "Error: " + e.getMessage();
}
}
}组合启动(Main)
import data.InMemoryUserRepository;
import repository.UserRepository;
import usecase.RegisterUser;
import usecase.RegisterUserInteractor;
import web.RegisterUserController;
public class Main {
public static void main(String[] args) {
// 1. 创建外部依赖(数据实现)
UserRepository userRepository = new InMemoryUserRepository();
// 2. 创建用例(注入依赖)
RegisterUser registerUser = new RegisterUserInteractor(userRepository);
// 3. 创建控制器(注入用例)
RegisterUserController controller = new RegisterUserController(registerUser);
// 4. 模拟请求
System.out.println(controller.handleRequest("alice", "secret123"));
System.out.println(controller.handleRequest("alice", "another")); // 应报错
}
}运行输出:
User registered successfully!
Error: Username already exists与 DDD 的关系
Clean Architecture 与领域驱动设计(DDD) 天然契合:
- 领域层(Domain) 对应 Clean Architecture 的 Entities 层
- 应用层(Application) 对应 Use Cases 层
- 基础设施层(Infrastructure) 对应 Frameworks & Drivers 层
在严格分层架构中,每层只能依赖其直接下方的层,避免核心业务逻辑外泄 。
在 Android/Kotlin 中的实践
现代 Android 开发中,Clean Architecture 通常与 MVVM 结合使用:
┌─────────────────────────────────────┐
│ Presentation Layer (UI) │ ← Activity/Fragment/Compose
│ - ViewModel │
│ - UI State │
├─────────────────────────────────────┤
│ Domain Layer (核心业务逻辑) │ ← 纯 Kotlin,零 Android 依赖
│ - Entities (数据类) │
│ - Use Cases (业务用例) │
│ - Repository Interfaces │
├─────────────────────────────────────┤
│ Data Layer (数据实现) │ ← Repository 实现、数据源
│ - RepositoryImpl │
│ - Remote Data Source (API) │
│ - Local Data Source (DB) │
└─────────────────────────────────────┘关键实践要点
- Domain 层保持纯净:不依赖 Android SDK、协程、RxJava 等框架
- Use Case 原子化:每个用例只做一件事(如
GetUserProfileUseCase) - 依赖注入:使用 Hilt/Koin 管理跨层依赖
- Repository 模式:Domain 层定义接口,Data 层实现,实现依赖倒置
- 模块化:按功能分层(
:domain、:data、:presentation),提升编译速度
优势对比
| 特性 | 传统 MVC/MVP | Clean Architecture |
|---|---|---|
| 业务逻辑独立性 | 与 UI/框架耦合 | 完全独立,可跨平台复用 |
| 可测试性 | 需 Android 环境 | Domain 层纯单元测试,无需模拟器 |
| 技术栈迁移 | 重构成本高 | 只需替换外层适配器 |
| 维护成本 | 随时间递增 | 长期稳定,适应变化 |
QA
Q:MVC 也是一种 Clean Architecture?
A:MVC 是“平面分离”,Clean Architecture 是“分层嵌套”
- MVC 的结构:
用户 → Controller → Model → Database
↑ ↓
View ← (数据展示)- Model、View、Controller 是平级关系,直接相互调用(例如 Controller 可能直接操作 Model,View 可能直接读取 Model)。
- 问题:如果 View 直接访问 Model,或 Controller 包含太多业务逻辑,会导致耦合度高,修改一处可能影响其他部分。
- Clean Architecture 的结构:
用户 → UI Layer (如 Web/App)
↓
Interface Adapters (如 Controller/Presenter)
↓
Use Cases (业务逻辑)
↓
Entities (核心业务模型)- 分层依赖:外层依赖内层,内层不知道外层的存在(例如 Use Cases 只依赖 Entities,不依赖 UI 或数据库)。
- 依赖倒置:通过接口(如`UserRepository`)隔离变化,外层实现接口,内层调用接口。
- 优势:修改 UI 或数据库时,只要接口不变,核心业务逻辑无需改动。
Q:怎么才算是怎么才算 Clean Architecture?
A:一个系统被认为是 Clean Architecture,通常需要满足以下特征(可看作“验收标准”):
- ✅ 业务规则不依赖任何外部框架:你的核心代码(Entities、Use Cases)不能出现 import 任何数据库驱动、Web 框架、第三方库(除标准库外)。
- ✅ 依赖倒置:外层(如 Controller、Repository 实现)依赖内层定义的接口,而非具体实现。内层定义接口,外层提供实现。
- ✅ 独立于 UI:用户界面可以随意更换(Web、命令行、移动 App),而不影响业务逻辑。
- ✅ 独立于数据库:你可以随时更换数据库(SQL、NoSQL、文件系统),业务逻辑不变。
- ✅ 独立于任何外部服务:第三方服务(如支付网关、消息队列)都被适配到外层,业务层通过接口调用它们。
- ✅ 可测试性:你可以用简单的单元测试覆盖所有业务用例,无需启动数据库或 Web 服务器。
- ✅ 清晰的边界:每一层都有明确定义的职责,层与层之间通过简单的数据结构或接口通信,没有“漏层”现象。
Q:Clean Architecture 和 DDD 什么区别?
A:
- Clean Architecture 是一种架构模式,它关注的是如何组织代码的层次,以及如何控制依赖方向(内层不依赖外层)。它提供了一种将业务逻辑与技术细节分离的通用架构骨架。
- 领域驱动设计(DDD) 是一种设计方法论,它关注的是如何对复杂的业务领域进行建模,并通过一系列战术模式(实体、值对象、聚合、仓储、领域服务、领域事件等)来表达业务逻辑。
两者可以完美结合:Clean Architecture 为 DDD 提供了理想的架构支撑——它的内层(实体、用例)正好可以容纳 DDD 的领域模型和应用层,外层负责技术细节。这样既能保持领域模型的纯粹性,又能获得架构的灵活性。
更新: 2026-03-08 22:25:27
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/hql3g409nunpn0ql