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.pswp.cn/bicheng/86035.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/86035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为OD机试_2025 B卷_矩形相交的面积(Python,100分)(附详细解题思路)

题目描述 给出3组点坐标(x, y, w, h)&#xff0c;-1000<x,y<1000&#xff0c;w,h为正整数。 (x, y, w, h)表示平面直角坐标系中的一个矩形&#xff1a; x, y为矩形左上角坐标点&#xff0c;w, h向右w&#xff0c;向下h。 (x, y, w, h)表示x轴(x, xw)和y轴(y, y-h)围成…

17、Rocket MQ快速实战以及核⼼概念详解

⼀ 、MQ简介 MQ&#xff1a;MessageQueue&#xff0c;消息队列。是在互联⽹中使⽤⾮常⼴泛的—系列服务中间件。 这个词可以分两个部分来看&#xff0c; —是Message&#xff1a;消息。消息是在不同进程之间传递的数据。这些进程可以部署在同—台机器上&#xff0c;也可以 分…

设计模式之手写策略模式实现动态支付(Java实现)

首先&#xff0c;定义一个接口类 import java.util.Map;public interface PayInterface {/*** 支付方法* param amount 支付金额* param paymentInfo 支付信息&#xff08;如卡号、密码等&#xff09;* return 支付结果*/boolean pay(double amount, Map<String, String>…

Spring Boot 虚拟线程 vs WebFlux:谁更胜一筹?

Spring Boot 作为构建现代 Java 应用程序的强大框架,为开发者提供了多种处理并发和可扩展性的解决方案。其中最受关注的两种方案是 Spring Boot 虚拟线程(Java 21 引入)和 Spring Boot WebFlux(基于响应式编程)。虽然两者都致力于优化资源利用率和提升高并发处理能力,但在…

淘宝商品搜索接口|关键字获取商品列表API接入指南

在电商领域&#xff0c;淘宝作为中国最大的电子商务平台之一&#xff0c;拥有海量的商品资源。对于开发者而言&#xff0c;通过淘宝开放平台提供的 API 接口&#xff0c;能够实现与淘宝平台的深度整合&#xff0c;其中关键字搜索商品 API 接口尤为重要。它允许开发者根据特定的…

Centos 离线部署(MQTT)EMOX脚本并设置开机自启

文件结构 install_emqx.sh #!/bin/bash # Filename: install_emqx.sh # Description: EMQX离线一键部署脚本 (针对特殊目录结构)# 检查root权限 if [[ $EUID -ne 0 ]]; thenecho "请使用root权限运行此脚本&#xff01;" exit 1 fi# 定义依赖包和安装路径 DEP_RPM&…

机器学习基础:从概念到应用的全面解析

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…

【机器学习1】线性回归与逻辑回归

‌逻辑回归与线性回归的主要区别在于理论基础、应用场景和数学模型。 1 线性回归 1.1 理论基础 线性回归主要用于建模自变量与连续性因变量之间关系的统计方法&#xff0c;试图利用一条线来拟合自变量与因变量之间的线性关系。 1.2 应用场景 从应用场景来说&#xff0c;适…

小程序 顶部栏标题栏 下拉滚动 渐显白色背景

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3164fd0e6d6848efaa1e87e02c35179e.png 下拉 100px 后 变成渐变成白色 显示原理 <wd-navbar fixed safeAreaInsetTop :bordered"false":custom-style"background-color: rgba(255, 255, 255, op…

Java底层原理:深入理解类加载机制与反射

一、Java类加载机制 Java类加载机制是Java运行时环境的重要组成部分&#xff0c;它负责将字节码文件加载到JVM内存中&#xff0c;并将其转换为可执行的类。类加载机制的实现涉及类加载器&#xff08;ClassLoader&#xff09;、类加载过程和类加载器的层次结构。 &#xff08;…

Android 中查看数据库内容方式

一、背景 创建的db数据库&#xff0c;有时候需要查看数据库中的数据内容,或者查看数据是否有更新到数据等等。这时候就需要查看数据库的内容。 二、数据库路径 博主用的是第三方的greendao数据库框架,生成的.db文件路径如下:(路径仅供参考) /data/data/app_package/database…

unity实现浮动组件

目录 前言方法后言组件代码 前言 在unity中&#xff0c;要让一个物体变得让人感到轻飘飘的&#xff0c;就可以给一个物体添加上浮动组件。今天我们就来实现它。 方法 我们先来看一下 sin ⁡ \sin sin函数的曲线。 在这条曲线上&#xff0c;随着 x x x向右移动&#xff0c; y…

Cisco Nexus93240接口带宽显示异常高故障- bug

hardware: cisco N93240 software: 9.3(10) 1个万兆接口&#xff0c;显示的rate超出几万倍 开case查询&#xff0c;告知是bug&#xff0c;需要版本升级解决。

pyhton基础【15】函数进阶一

目录 一. 函数进阶 1. 默认参数&#xff1a; 2. 关键字参数&#xff1a; 3. 可变参数&#xff1a; 4. 装饰器&#xff1a; 5. 匿名函数lambda&#xff1a; 6. 高阶函数&#xff1a; 7. 递归函数&#xff1a; 8. 类型注解&#xff1a; 二.函数参数的高级使用 缺…

【软考高级系统架构论文】论企业应用系统的数据持久层架构设计

论文真题 数据持久层 (Data Persistence Layer) 通常位于企业应用系统的业务逻辑层和数据源层之间,为整个项目提供一个高层、统一、安全、并发的数据持久机制,完成对各种数据进行持久化的编程工作,并为系统业务逻辑层提供服务。它能够使程序员避免手工编写访问数据源的方法…

ubuntu使用 Conda 安装 pyseer详细教程

pyseer 是一个用于 微生物全基因组关联分析(GWAS) 的生物信息学工具。它可以帮助研究者识别微生物(如细菌)中与表型(如耐药性、毒力、致病性)相关的遗传变异。 一、安装mamba conda install -n base -c conda-forge mamba二、创建虚拟环境 conda create -n pyseer-env …

Redis04

redis 一、redis的作用和使用场景 redis是一个内存级的高速缓存数据库。&#xff08;对比磁盘IO&#xff09; 使用场景&#xff1a;1、并发访问量大的 2、数据量小 3、修改不频繁 项目中&#xff1a;1、验证码 2、登录成功用户信息 3、首页&#xff08;模块数据 轮播图&…

计算机网络学习笔记:TCP可靠传输实现、超时重传时间选择

文章目录 一、TCP可靠传输实现二、TCP超时重传时间选择 一、TCP可靠传输实现 TCP可靠传输的实现&#xff0c;主要基于发送方和接收方的滑动窗口&#xff0c;以及确认机制&#xff1a; 发送方在未收到确认&#xff08;ACK&#xff09;前&#xff0c;可以将序号落在发送窗口内的…

Perl 正则表达式

Perl 正则表达式 引言 Perl 正则表达式&#xff08;Regular Expressions&#xff09;是Perl编程语言中一个强大且灵活的工具&#xff0c;用于字符串处理和模式匹配。正则表达式在文本处理、数据验证、搜索和替换等任务中发挥着至关重要的作用。本文将深入探讨Perl正则表达式的…

Security: RSA: 1024 bit 长度已经变得不安全了

文章目录 参考推荐限制RHEL相关配置man crypto-policies包含的应用使用方法是配置文件include参考 https://csrc.nist.gov/pubs/sp/800/57/pt1/r2/final https://www.linuxquestions.org/questions/linux-security-4/1024-bit-dsa-vs-2048-bit-rsa-4175439131/ https://csrc.n…