架构
单例留在组合根里
全局入口可以有,但只在 App 启动时创建一次。功能代码不要自己去拿 .shared。
单例难测,是因为调用点自己决定了依赖。Feed 里写 Account.shared,测试就要准备一整套登录。把创建收到启动路径上,功能只接收已经做好的对象,这个问题就小很多。
组合根
AppDelegate 或一个 AppComposition 类型是唯一可以 new 具体服务的地方。它创建网络、账号、数据库,再交给各个流程。功能模块的初始化方法只收协议。
final class AppComposition {
let session: AccountSession
let feed: FeedStore
init() {
let api = URLSessionAPI()
session = AccountService(api: api)
feed = FeedStore(session: session, api: api)
}
}
URLSessionAPI 这种具体类型不要出现在 Feed 的文件里。Feed 只认识自己声明的协议。换一套假的 API,列表逻辑不用动。
还是会有的全局
- 日志和崩溃上报可以是窄的全局函数,但不要让它们再去拉业务单例。
- 当前用户不要做成谁都能改的可变全局。账号服务改,别人只读。
- 为了少写参数,把十个服务塞进一个 ServiceLocator,调用方仍然在自己找依赖,测试还是重。
检查方法:在功能目录里搜 .shared。出现业务单例,就改成从外面传进来。搜不出,说明这层边界是立住的。