在 Android 分层架构中,数据模型根据其职责和使用层级分为 5 种核心类型,每种都有明确的定位和转换规则:
1. DTO (Data Transfer Object) - 数据传输对象
-
定位 :数据层(网络通信子层)
-
核心职责:
- 映射 API 返回的原始 JSON/XML 结构
- 处理网络协议特有字段(如状态码、错误消息)
-
特点:
- 字段命名与 API 完全一致
- 包含网络通信专用注解(如 Retrofit 的
@SerializedName
) - 无业务逻辑,纯数据结构
-
示例:
lessdata class UserDTO( @SerializedName("user_id") val id: String, @SerializedName("full_name") val name: String, @SerializedName("created_at") val createTime: String // 原始时间字符串 )
-
转换方向:DTO → Domain Model
2. PO (Persistent Object) / Entity - 持久化对象
-
定位 :数据层(本地存储子层)
-
核心职责:
- 映射数据库表结构(Room/SQLite)
- 定义本地存储方案(如字段类型、主键、索引)
-
特点:
- 包含数据库框架注解(如 Room 的
@Entity
,@PrimaryKey
) - 数据类型适配存储引擎(如 Date 存为 Long)
- 可能包含数据库优化字段(如版本号、时间戳)
- 包含数据库框架注解(如 Room 的
-
示例:
less@Entity(tableName = "users") data class UserPO( @PrimaryKey val id: String, @ColumnInfo(name = "display_name") val name: String, @ColumnInfo(name = "last_update_time") val updateTime: Long // 时间戳格式 )
-
转换方向:PO ↔ Domain Model
3. Domain Model - 领域模型
-
定位 :领域层(架构核心)
-
核心职责:
- 表达业务核心概念和规则
- 封装业务校验逻辑
- 作为各层数据流转的中枢
-
特点:
- 纯 Kotlin/Java 对象(无任何框架依赖)
- 包含业务方法(如
isVIP()
) - 字段命名符合业务术语
- 不可变(推荐使用
val
)
-
示例:
kotlindata class User( val id: String, val name: String, val joinDate: LocalDate, val creditPoints: Int ) { // 业务逻辑方法 fun isVIP() = creditPoints > 1000 }
-
转换关系:
- ← 来自 DTO/PO
- → 流向 VO
4. VO (View Object) - 视图对象
-
定位 :UI 层
-
核心职责:
- 为界面展示优化的数据结构
- 包含 UI 特有的字段和格式
- 适配视图组件需求
-
特点:
- 包含展示专用字段(如资源 ID、格式化文本)
- 扁平化结构便于 UI 直接使用
- 可能组合多个 Domain Model
-
示例: data class UserVO( val name: String, val avatarResId: Int, // 本地资源ID val joinInfo: String, // 格式化文本:"加入3年" val isVip: Boolean, val vipBadgeColor: Int // VIP专属颜色 )
-
转换位置:ViewModel 中转换 Domain → VO
5. Request Model - 请求对象(特殊场景)
-
定位 :数据层(网络请求子层)
-
核心职责:
- 封装向 API 发送的请求体
- 处理请求特殊要求(如加密字段)
-
特点:
- 结构对应 API 要求的请求格式
- 可能包含鉴权 token 等临时字段
-
示例:
kotlindata class LoginRequest( val username: String, val password: String, val device_id: String, val sign: String // 加密签名 )
模型转换关系图

关键设计原则
-
单一职责原则
每类模型只处理单一层次的问题(如 DTO 只处理 JSON 映射)
-
隔离变化
- API 字段变更只需修改 DTO 转换逻辑
- UI 改版只需调整 VO 结构
-
安全边界
敏感字段(如密码)仅在 Request 中出现,不会泄漏到 VO
-
性能优化
- VO 预计算展示数据减少 UI 线程负担
- PO 包含数据库优化字段
为何需要多层模型?实际案例
假设用户个人页需要展示:
css
[头像] 张三(VIP徽章)
加入时间:2021年(3年前)
数据流转过程:
-
网络层 收到 DTO:
json{ "user_id": "123", "avatar_url": "https://...", "full_name": "张三", "create_time": "2021-03-12T00:00:00Z", "point_balance": 1500 }
-
数据层 转换为 Domain Model:
iniUser( id = "123", name = "张三", joinDate = LocalDate.of(2021, 3, 12), // 转换为日期对象 creditPoints = 1500 )
-
ViewModel 转换为 VO:
iniUserVO( name = "张三", avatarResId = R.drawable.vip_avatar, // 根据URL预加载 joinInfo = "加入3年", // 计算时间差 isVip = true, vipBadgeColor = Color.RED )
常见问题解决方案
问题1:模型转换代码冗余?
👉 方案:使用扩展函数集中管理
kotlin
// DTO转Domain
fun UserDTO.toDomain() = User(
id = user_id,
name = full_name,
joinDate = parseDate(created_at)
)
// Domain转VO
fun User.toVO(resProvider: ResourceProvider) = UserVO(
name = name,
avatarResId = resProvider.loadAvatar(id),
joinInfo = "加入${LocalDate.now().year - joinDate.year}年"
)
问题2:小型项目是否需要完整分层?
👉 精简方案:
数据层:DTO 直接转换为 VO
但保留 Domain Model 预留扩展点
问题3:多数据源合并?
👉 在 Repository 中处理:
kotlin
fun getUserData(): User {
val remote = api.getUser().toDomain()
val local = db.getUserPreferences()
return remote.copy(settings = local) // 合并网络和本地数据
}
严格区分模型类型
