OOP 码风 OOP Coding Style DDD
在面向对象编程中,代码通常会涉及两个核心问题:
- 数据放在哪里?
- 业务逻辑放在哪里?
不同项目、框架和团队,会形成不同的代码组织风格。下面总结三种常见风格:
- 贫血模型
- 充血模型
- 流式接口 / 链式调用
1. 贫血模型 Anemic Domain Model
贫血模型指的是:对象本身主要只保存数据,真正的业务逻辑写在外部的 Service 层里。
也就是说,对象像一个“数据容器”,而不是一个有行为能力的对象。
典型结构
class User {
private String name;
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
````
然后业务逻辑写在外部:
class UserService {
public void upgrade(User user) {
user.setLevel(user.getLevel() + 1);
}}
### 什么是 getter / setter?
`getter` 和 `setter` 是访问对象属性的方法。
因为很多语言不推荐直接访问对象内部变量,所以会写成方法:
user.name
改成:
user.getName()
user.setName("Alice")
其中:
- `getName()`:读取 `name`
- `setName(...)`:修改 `name`
所以一个只有属性和 `getter / setter` 的类,本质上只是一个“数据盒子”。
### 常见相关术语
|术语|含义|
|---|---|
|POJO|普通 Java 对象,通常指没有复杂框架依赖的简单对象|
|Entity|实体类,通常对应数据库中的一张表|
|DTO|数据传输对象,用来在接口、服务之间传递数据|
|Service|服务层,通常写业务流程和业务逻辑|
|Transaction Script|事务脚本,把业务流程按步骤写在一个函数或服务方法里|
### 特点
贫血模型的核心是:
> 数据和逻辑分离。
对象只负责保存数据,Service 负责处理逻辑。
### 优点
- 简单直观
- 适合 CRUD 项目
- 和数据库表结构容易对应
- 在 Spring、MyBatis 等后端框架中很常见
### 缺点
- 对象缺少行为,只是数据容器
- 复杂业务会导致 Service 层越来越臃肿
- 代码容易变成“面向对象语法包装下的面向过程代码”
---
## 2. 充血模型 Rich Domain Model
充血模型指的是:对象不仅保存数据,还包含与自身相关的业务行为。
也就是说,对象不只是“数据盒子”,而是一个真正能处理自己业务逻辑的领域对象。
### 典型结构
class User {
private int level;
public void upgrade() {
this.level += 1;
}}
调用时:
user.upgrade();
这里的 `upgrade` 逻辑写在 `User` 自己内部,而不是外部的 `UserService` 里。
### 核心思想
充血模型的核心是:
> 数据和逻辑合一。
谁拥有数据,谁就更适合处理相关逻辑。
### 相关设计原则
|原则|含义|
|---|---|
|Encapsulation 封装|对象隐藏内部细节,只暴露必要行为|
|Tell, Don't Ask|不要把对象的数据拿出来自己判断,而是直接让对象执行行为|
|Information Expert|逻辑应该放在最了解相关数据的对象中|
|Domain Entity|领域实体,既有状态,也有行为|
### Tell, Don't Ask 示例
不推荐:
if (user.getLevel() < 10) {
user.setLevel(user.getLevel() + 1);}
更推荐:
user.upgrade();
因为升级规则属于 `User` 自己,外部代码不应该到处读取它的属性再自己判断。
### 优点
- 更符合面向对象思想
- 业务规则集中在领域对象中
- 复杂业务更容易维护
- 能减少 Service 层过度膨胀
### 缺点
- 设计难度更高
- 对简单 CRUD 来说可能显得过度设计
- 和数据库表、ORM 框架配合时需要更多设计经验
---
## 3. 流式接口 Fluent Interface / 链式调用 Method Chaining
流式接口是一种让代码读起来更连贯的调用风格。
它的典型形式是连续调用方法:
user.setName("Alice")
.setAge(18)
.setEmail("alice@example.com");
或者:
QueryBuilder
.select("name")
.from("user")
.where("age > 18")
.orderBy("id");
### 核心原理
链式调用通常依赖一个简单机制:
> 每个方法执行完之后,返回对象本身 `this`。
例如:
class UserBuilder {
private String name;
private int age;
public UserBuilder name(String name) {
this.name = name;
return this;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}}
这样就可以写成:
new UserBuilder()
.name("Alice")
.age(18);
### 相关术语
|术语|含义|
|---|---|
|Method Chaining|链式调用,连续调用多个方法|
|Fluent Interface|流式接口,让代码像自然语言一样流畅|
|Builder Pattern|构建者模式,用链式方法一步步构造复杂对象|
|DSL|领域特定语言,让代码更接近某个业务领域的表达方式|
### 适用场景
流式接口常见于:
- 配置类
- 构建对象
- SQL 查询构造器
- 测试代码
- UI 构造
- API 调用参数组装
### 优点
- 代码简洁
- 可读性强
- 适合表达连续配置或构造过程
- 适合做内部 DSL
### 缺点
- 如果链条过长,可读性反而下降
- 调试时不一定方便
- 不适合所有业务逻辑
- 可能隐藏中间状态变化
---
## 三者对比
|风格|核心思想|数据和逻辑关系|典型代码|
|---|---|---|---|
|贫血模型|对象保存数据,Service 处理逻辑|数据与逻辑分离|`userService.upgrade(user)`|
|充血模型|对象自己处理与自身相关的行为|数据与逻辑合一|`user.upgrade()`|
|流式接口|通过连续方法调用提高表达力|关注调用形式,不直接决定业务模型|`.name().age().build()`|
---
## 需要注意的区别
贫血模型和充血模型讨论的是:
> 业务逻辑应该放在哪里。
而流式接口讨论的是:
> 方法调用应该如何组织得更流畅。
所以它们不是同一维度的概念。
一个项目可以同时使用这些风格:
- 简单 CRUD:使用贫血模型
- 复杂业务规则:使用充血模型
- 配置、构造、查询:使用流式接口
---
## 简单理解
可以这样记:
|类型|类比|说明|
|---|---|---|
|贫血模型|表格 + 管理员|对象只是数据,Service 负责处理|
|充血模型|有职责的人|对象既有数据,也知道自己该怎么做|
|流式接口|连续填表 / 点餐|方法一个接一个调用,表达更顺滑|
---
## 我的理解
贫血模型并不是完全错误。它在很多后端项目中非常常见,尤其适合简单业务、数据库驱动的 CRUD 系统。
充血模型更适合复杂业务,因为它能把业务规则封装进对象内部,减少外部 Service 对对象内部状态的随意操作。
流式接口本质上是一种 API 设计风格,它不直接决定业务逻辑放在哪里,而是让调用代码更自然、更容易组织。
因此,判断一种代码风格好不好,不能只看它是不是“面向对象”,还要看业务复杂度、团队习惯、框架限制和维护成本。
核心可以记成一句话:
**贫血 / 充血是在讨论“逻辑放哪”;流式接口是在讨论“调用怎么写得顺”。**
---