当前位置: 首页 > news >正文

Domain 层完全指南(面向 iOS 开发者)


目录

  1. 为什么需要 Domain 层
  2. 清晰的三层架构
  3. 核心概念:Entity / Value Object / Use Case / Repository
  4. Swift 代码实战
  5. 测试策略
  6. 在旧项目中落地的步骤
  7. 结语

1 为什么需要 Domain 层

在传统 MVC / MVVM 中,我们往往把业务规则写进 ViewController 或 ViewModel。
问题随规模放大而爆发:

痛点具体表现
可测试性差单元测试必须启动 UIKit,跑真机或模拟器
业务难复用同样的计费、权限逻辑被多处复制
维护成本高UI 改版常常误伤业务代码

Domain 层 = 把“业务世界”的概念模型用例流程抽离出来,形成纯 Swift 代码;UI 与外部数据存取只依赖它,却不影响它。


2 三层架构速览

层级依赖方向关键词
Presentation⬇︎ 调用 UseCaseUIKit / SwiftUI / Combine / Bloc
Domain纯 SwiftEntity•ValueObject•UseCase•Repository协议
Data / Infrastructure⬆︎ 实现 RepositoryURLSession / CoreData / Realm / BLE

依赖只允许由外向内,Domain 不感知任何框架。


3 关键概念

角色职责要点
Entity有唯一标识 + 生命周期,如 Order行为应遵守不变式
Value Object无标识,靠值判等,如 Money必须不可变
Use Case (Interactor)满足用户故事的业务流程,如 PlaceOrder只依赖协议
Repository 协议Domain 访问数据的抽象不关心具体存储方式

Place Order 意思是:下单 / 提交订单


4 Swift 代码实战

场景:展示并更新聊天未读数

4.1 Entity 与 Value Object

// Value Object
struct UnreadCount: Equatable {let value: Intinit(_ raw: Int) {precondition(raw >= 0, "Unread cannot be negative")value = raw}
}// Entity
struct Conversation: Identifiable, Equatable {let id: UUIDprivate(set) var unread: UnreadCountmutating func markAllRead() {unread = .init(0)}
}

4.2 Repository 协议

protocol ConversationRepository {/// 从缓存或网络获取未读数func unreadCount() async throws -> UnreadCount/// 将未读数持久化func save(_ count: UnreadCount) async throws
}

4.3 Use Case

/// 单一职责:获取并缓存未读数
struct GetUnreadCountUseCase {private let repo: ConversationRepositoryinit(repo: ConversationRepository) { self.repo = repo }func execute() async throws -> UnreadCount {let count = try await repo.unreadCount()try await repo.save(count)      // 读完即写缓存return count}
}

4.4 Data 层实现(摘录)

final class ConversationApiDataSource: ConversationRepository {private let api: URLSessionprivate let cache: UserDefaultsfunc unreadCount() async throws -> UnreadCount {let (data, _) = try await api.data(from: URL(string: "/unread")!)let json = try JSONDecoder().decode(UnreadDTO.self, from: data)return .init(json.total)}func save(_ count: UnreadCount) async throws {cache.set(count.value, forKey: "unread_total")}
}

4.5 Presentation 层集成

final class UnreadCubit: Cubit<UnreadState> {private let getCount: GetUnreadCountUseCaseinit(getCount: GetUnreadCountUseCase) {self.getCount = getCountsuper.init(Initial())}@MainActorfunc fetch() {Task {emit(Loading())do {let count = try await getCount.execute()emit(Loaded(count))} catch {emit(Failed(error))}}}
}
  • UI 只感知 UnreadState,不关心 Repository 具体实现。
  • 想改用 Realm 缓存?仅替换 ConversationApiDataSource,Domain 与 UI 零改动。

5 单元测试策略

final class FakeConversationRepo: ConversationRepository {var next: UnreadCount = .init(3)func unreadCount() async throws -> UnreadCount { next }func save(_ count: UnreadCount) async throws { /* no-op */ }
}func testGetUnreadCount() async throws {let repo = FakeConversationRepo()let useCase = GetUnreadCountUseCase(repo: repo)let result = try await useCase.execute()XCTAssertEqual(result, .init(3))
}
  • 无需启动 App、无需网络;执行速度毫秒级。
  • Entity 的不变式可直接覆盖极端值(负数、溢出等)。

6 如何在旧项目落地

  1. 挑出最稳定的业务规则(如价格计算、权限判断)。
  2. 抽成纯 Swift 类型,斩断 UIKit / CoreData 依赖。
  3. 对 UI 暴露 Use Case 协议,用 DI 容器(例:Swinject)注入实现。
  4. 渐进式替换:新功能强制走 Domain;旧代码按需迁移。
  5. 持续加测试,确保迁移未破坏行为。

7 结语

Domain 层让 iOS 项目的业务核心脱离平台细节,既提高可测试性,又带来长久可维护性
掌握它,你将在大型团队协作与多端共享逻辑(watchOS / visionOS / server Swift)时,享受显著的工程收益。

Happy refactoring!

http://www.lqws.cn/news/494317.html

相关文章:

  • 【数据结构初阶】--顺序表(一)
  • FPGA基础 -- Verilog 验证平台
  • 《哈希表》K倍区间(解题报告)
  • 论文略读:ASurvey on Intent-aware Recommender Systems
  • 13-MCP4725-带 EEPROM 存储器的 12 位数模转换器
  • DeepSeek中的提示库及其用法示例
  • AI编程再突破,文心快码发布行业首个多模态、多智能体协同AI IDE
  • leetcode543-二叉树的直径
  • Flink SQL执行流程深度剖析:从SQL语句到分布式执行
  • 【Linux学习笔记】进程间通信之共享内存
  • Kimi“新PPT助手” ,Kimi全新自研的免费AI生成PPT助手
  • 金融行业B端系统布局实战:风险管控与数据可视化的定制方案
  • 深入理解PHP中的面向对象编程
  • 电脑的虚拟内存对性能影响大吗
  • FPGA基础 -- Verilog 竞争/竞态(Race Condition)
  • ubuntu安装postman教程并中文汉化详细教程
  • Anaconda虚拟环境
  • flutter TabBar左边间隔问题
  • 【android bluetooth 框架分析 04】【bt-framework 层详解 8】【DeviceProperties介绍】
  • Java数据结构第二十四期:探秘 AVL 树,当二叉搜索树学会 “自我调节”
  • 2025再升级:医疗数智立体化体系V2.0架构简介
  • 布瑞琳BRANEW:高端洗护领航者,铸就品质生活新典范
  • 以产教协同推进老年生活照护实训室虚拟仿真建设策略
  • 物联网的全球布局与未来趋势
  • 在Ubuntu上设置Firefox自动化测试环境:指定Marionette端口号
  • MySQL 事务实现机制详解
  • DMDIS表抽取到文件
  • 3、结合STM32CubeMX学习FreeRTOS实时操作系统——队列
  • Spring Boot 使用 ElasticSearch
  • SpringSecurity6(认证-前后端分离)