dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDependency Injection — Expert Decisions
依赖注入(Dependency Injection)—— 专家决策指南
Expert decision frameworks for dependency injection choices. Claude knows DI basics — this skill provides judgment calls for when and how to apply DI patterns.
为依赖注入选择提供专家级决策框架。Claude 掌握DI基础知识——本技能针对何时及如何应用DI模式提供判断依据。
Decision Trees
决策树
Do You Need DI?
是否需要使用DI?
Is the dependency tested independently?
├─ NO → Is it a pure function or value type?
│ ├─ YES → No DI needed (just call it)
│ └─ NO → Consider DI for future testability
│
└─ YES → How many classes use this dependency?
├─ 1 class → Simple constructor injection
├─ 2-5 classes → Protocol + constructor injection
└─ Many classes → Consider lightweight containerThe trap: DI everything. If a helper function has no side effects and doesn't need mocking, don't wrap it in a protocol.
Is the dependency tested independently?
├─ NO → Is it a pure function or value type?
│ ├─ YES → No DI needed (just call it)
│ └─ NO → Consider DI for future testability
│
└─ YES → How many classes use this dependency?
├─ 1 class → Simple constructor injection
├─ 2-5 classes → Protocol + constructor injection
└─ Many classes → Consider lightweight container误区:给所有内容都做DI。如果一个辅助函数没有副作用且不需要模拟,就不要用协议去包装它。
Which Injection Pattern?
选择哪种注入模式?
Who creates the object?
├─ Caller provides dependency
│ └─ Constructor Injection (most common)
│ init(service: ServiceProtocol)
│
├─ Object creates dependency but needs flexibility
│ └─ Default Parameter Injection
│ init(service: ServiceProtocol = Service())
│
├─ Dependency changes during lifetime
│ └─ Property Injection (rare, avoid if possible)
│ var service: ServiceProtocol?
│
└─ Factory creates object with dependencies
└─ Factory Pattern
container.makeUserViewModel()Who creates the object?
├─ Caller provides dependency
│ └─ Constructor Injection (most common)
│ init(service: ServiceProtocol)
│
├─ Object creates dependency but needs flexibility
│ └─ Default Parameter Injection
│ init(service: ServiceProtocol = Service())
│
├─ Dependency changes during lifetime
│ └─ Property Injection (rare, avoid if possible)
│ var service: ServiceProtocol?
│
└─ Factory creates object with dependencies
└─ Factory Pattern
container.makeUserViewModel()Protocol vs Concrete Type
协议 vs 具体类型
Will this dependency be mocked in tests?
├─ YES → Protocol
│
└─ NO → Is it from external module?
├─ YES → Protocol (wrap for decoupling)
└─ NO → Is interface likely to change?
├─ YES → Protocol
└─ NO → Concrete type is fineRule of thumb: Network, database, analytics, external APIs → Protocol. Date formatters, math utilities → Concrete.
Will this dependency be mocked in tests?
├─ YES → Protocol
│
└─ NO → Is it from external module?
├─ YES → Protocol (wrap for decoupling)
└─ NO → Is interface likely to change?
├─ YES → Protocol
└─ NO → Concrete type is fine经验法则:网络、数据库、分析、外部API → 使用协议。日期格式化器、数学工具类 → 使用具体类型。
DI Container Complexity
DI容器复杂度
Team size?
├─ Solo/Small (1-3)
│ └─ Default parameters + simple factory
│
├─ Medium (4-10)
│ └─ Simple manual container
│ final class Container {
│ lazy var userService = UserService()
│ }
│
└─ Large (10+)
└─ Consider Swinject or similar
(only if manual wiring becomes painful)Team size?
├─ Solo/Small (1-3)
│ └─ Default parameters + simple factory
│
├─ Medium (4-10)
│ └─ Simple manual container
│ final class Container {
│ lazy var userService = UserService()
│ }
│
└─ Large (10+)
└─ Consider Swinject or similar
(only if manual wiring becomes painful)NEVER Do
绝对不要做的事
Protocol Design
协议设计
NEVER create protocols with only one implementation:
swift
// ❌ Protocol just for the sake of it
protocol DateFormatterProtocol {
func format(_ date: Date) -> String
}
class DateFormatterImpl: DateFormatterProtocol {
func format(_ date: Date) -> String { ... }
}
// ✅ Just use the type directly
let formatter = DateFormatter()
formatter.dateStyle = .mediumException: When wrapping external dependencies for decoupling or testing.
NEVER mirror the entire class interface in a protocol:
swift
// ❌ 1:1 mapping is a code smell
protocol UserServiceProtocol {
var users: [User] { get }
var isLoading: Bool { get }
func fetchUser(id: String) async throws -> User
func updateUser(_ user: User) async throws
func deleteUser(id: String) async throws
// ...20 more methods
}
// ✅ Minimal interface for what's actually needed
protocol UserFetching {
func fetchUser(id: String) async throws -> User
}NEVER put mutable state requirements in protocols:
swift
// ❌ Forces implementation details
protocol CacheProtocol {
var storage: [String: Any] { get set } // Leaks implementation
}
// ✅ Behavior-focused
protocol CacheProtocol {
func get(key: String) -> Any?
func set(key: String, value: Any)
}绝对不要创建只有一个实现的协议:
swift
// ❌ Protocol just for the sake of it
protocol DateFormatterProtocol {
func format(_ date: Date) -> String
}
class DateFormatterImpl: DateFormatterProtocol {
func format(_ date: Date) -> String { ... }
}
// ✅ Just use the type directly
let formatter = DateFormatter()
formatter.dateStyle = .medium例外情况:为解耦或测试目的包装外部依赖时。
绝对不要在协议中镜像整个类的接口:
swift
// ❌ 1:1 mapping is a code smell
protocol UserServiceProtocol {
var users: [User] { get }
var isLoading: Bool { get }
func fetchUser(id: String) async throws -> User
func updateUser(_ user: User) async throws
func deleteUser(id: String) async throws
// ...20 more methods
}
// ✅ Minimal interface for what's actually needed
protocol UserFetching {
func fetchUser(id: String) async throws -> User
}绝对不要在协议中加入可变状态要求:
swift
// ❌ Forces implementation details
protocol CacheProtocol {
var storage: [String: Any] { get set } // Leaks implementation
}
// ✅ Behavior-focused
protocol CacheProtocol {
func get(key: String) -> Any?
func set(key: String, value: Any)
}Constructor Injection
构造函数注入
NEVER use property injection when constructor injection works:
swift
// ❌ Object can be in invalid state
class UserViewModel {
var userService: UserServiceProtocol! // Can be nil!
func loadUser() async {
let user = try? await userService.fetchUser(id: "1") // Crash if not set!
}
}
// ✅ Guaranteed valid state
class UserViewModel {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol) {
self.userService = userService // Never nil
}
}NEVER create objects with many dependencies (> 5):
swift
// ❌ Too many dependencies — class does too much
init(
userService: UserServiceProtocol,
authService: AuthServiceProtocol,
analyticsService: AnalyticsProtocol,
networkManager: NetworkManagerProtocol,
cacheManager: CacheProtocol,
configService: ConfigServiceProtocol,
featureFlagService: FeatureFlagProtocol
) { ... }
// ✅ Split into smaller, focused classes
// Or create a composite service绝对不要在构造函数注入可行时使用属性注入:
swift
// ❌ Object can be in invalid state
class UserViewModel {
var userService: UserServiceProtocol! // Can be nil!
func loadUser() async {
let user = try? await userService.fetchUser(id: "1") // Crash if not set!
}
}
// ✅ Guaranteed valid state
class UserViewModel {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol) {
self.userService = userService // Never nil
}
}绝对不要创建具有过多依赖(>5个)的对象:
swift
// ❌ Too many dependencies — class does too much
init(
userService: UserServiceProtocol,
authService: AuthServiceProtocol,
analyticsService: AnalyticsProtocol,
networkManager: NetworkManagerProtocol,
cacheManager: CacheProtocol,
configService: ConfigServiceProtocol,
featureFlagService: FeatureFlagProtocol
) { ... }
// ✅ Split into smaller, focused classes
// Or create a composite serviceService Locator (Anti-Pattern)
服务定位器(反模式)
NEVER use Service Locator pattern:
swift
// ❌ Hidden dependencies, runtime errors, untestable
class UserViewModel {
func loadUser() async {
let service = ServiceLocator.shared.resolve(UserServiceProtocol.self)!
// Crashes if not registered
// Dependency is hidden
// Can't see what this class needs
}
}
// ✅ Explicit constructor injection
class UserViewModel {
private let userService: UserServiceProtocol // Visible dependency
init(userService: UserServiceProtocol) {
self.userService = userService
}
}绝对不要使用服务定位器模式:
swift
// ❌ Hidden dependencies, runtime errors, untestable
class UserViewModel {
func loadUser() async {
let service = ServiceLocator.shared.resolve(UserServiceProtocol.self)!
// Crashes if not registered
// Dependency is hidden
// Can't see what this class needs
}
}
// ✅ Explicit constructor injection
class UserViewModel {
private let userService: UserServiceProtocol // Visible dependency
init(userService: UserServiceProtocol) {
self.userService = userService
}
}Testing
测试
NEVER create mocks with real side effects:
swift
// ❌ Mock does real work
class MockNetworkManager: NetworkManagerProtocol {
func request<T>(_ endpoint: Endpoint) async throws -> T {
// Actually makes network call!
return try await URLSession.shared.data(from: endpoint.url)
}
}
// ✅ Mocks return stubbed data
class MockNetworkManager: NetworkManagerProtocol {
var stubbedResult: Any?
var stubbedError: Error?
func request<T>(_ endpoint: Endpoint) async throws -> T {
if let error = stubbedError { throw error }
return stubbedResult as! T
}
}NEVER test mocks instead of real code:
swift
// ❌ Testing the mock, not the system
func testMockReturnsUser() {
let mock = MockUserService()
mock.stubbedUser = User(name: "John")
XCTAssertEqual(mock.fetchUser().name, "John") // Tests mock, not app
}
// ✅ Test the system under test
func testViewModelLoadsUser() async {
let mock = MockUserService()
mock.stubbedUser = User(name: "John")
let viewModel = UserViewModel(userService: mock) // SUT
await viewModel.loadUser(id: "1")
XCTAssertEqual(viewModel.user?.name, "John") // Tests ViewModel
}绝对不要创建带有真实副作用的模拟对象:
swift
// ❌ Mock does real work
class MockNetworkManager: NetworkManagerProtocol {
func request<T>(_ endpoint: Endpoint) async throws -> T {
// Actually makes network call!
return try await URLSession.shared.data(from: endpoint.url)
}
}
// ✅ Mocks return stubbed data
class MockNetworkManager: NetworkManagerProtocol {
var stubbedResult: Any?
var stubbedError: Error?
func request<T>(_ endpoint: Endpoint) async throws -> T {
if let error = stubbedError { throw error }
return stubbedResult as! T
}
}绝对不要测试模拟对象而非真实代码:
swift
// ❌ Testing the mock, not the system
func testMockReturnsUser() {
let mock = MockUserService()
mock.stubbedUser = User(name: "John")
XCTAssertEqual(mock.fetchUser().name, "John") // Tests mock, not app
}
// ✅ Test the system under test
func testViewModelLoadsUser() async {
let mock = MockUserService()
mock.stubbedUser = User(name: "John")
let viewModel = UserViewModel(userService: mock) // SUT
await viewModel.loadUser(id: "1")
XCTAssertEqual(viewModel.user?.name, "John") // Tests ViewModel
}Essential Patterns
核心模式
Default Parameter Injection
默认参数注入
swift
// Production uses real, tests inject mock
@MainActor
final class UserViewModel: ObservableObject {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol = UserService()) {
self.userService = userService
}
}
// Production
let viewModel = UserViewModel()
// Test
let viewModel = UserViewModel(userService: MockUserService())swift
// Production uses real, tests inject mock
@MainActor
final class UserViewModel: ObservableObject {
private let userService: UserServiceProtocol
init(userService: UserServiceProtocol = UserService()) {
self.userService = userService
}
}
// Production
let viewModel = UserViewModel()
// Test
let viewModel = UserViewModel(userService: MockUserService())Simple Manual Container
简单手动容器
swift
@MainActor
final class Container {
static let shared = Container()
// Singletons (lazy initialized)
lazy var networkManager: NetworkManagerProtocol = NetworkManager()
lazy var authService: AuthServiceProtocol = AuthService(network: networkManager)
lazy var userService: UserServiceProtocol = UserService(network: networkManager)
// Factory methods (new instance each time)
func makeUserViewModel() -> UserViewModel {
UserViewModel(userService: userService)
}
func makeLoginViewModel() -> LoginViewModel {
LoginViewModel(authService: authService)
}
}swift
@MainActor
final class Container {
static let shared = Container()
// Singletons (lazy initialized)
lazy var networkManager: NetworkManagerProtocol = NetworkManager()
lazy var authService: AuthServiceProtocol = AuthService(network: networkManager)
lazy var userService: UserServiceProtocol = UserService(network: networkManager)
// Factory methods (new instance each time)
func makeUserViewModel() -> UserViewModel {
UserViewModel(userService: userService)
}
func makeLoginViewModel() -> LoginViewModel {
LoginViewModel(authService: authService)
}
}SwiftUI Environment Injection
SwiftUI环境注入
swift
// Custom environment key
private struct UserServiceKey: EnvironmentKey {
static let defaultValue: UserServiceProtocol = UserService()
}
extension EnvironmentValues {
var userService: UserServiceProtocol {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
// Inject at app level
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.userService, Container.shared.userService)
}
}
}
// Consume in any view
struct UserView: View {
@Environment(\.userService) var userService
var body: some View {
// Use userService
}
}swift
// Custom environment key
private struct UserServiceKey: EnvironmentKey {
static let defaultValue: UserServiceProtocol = UserService()
}
extension EnvironmentValues {
var userService: UserServiceProtocol {
get { self[UserServiceKey.self] }
set { self[UserServiceKey.self] = newValue }
}
}
// Inject at app level
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.userService, Container.shared.userService)
}
}
}
// Consume in any view
struct UserView: View {
@Environment(\.userService) var userService
var body: some View {
// Use userService
}
}Mock with Verification
带验证的模拟对象
swift
final class MockUserService: UserServiceProtocol {
// Stubbed returns
var stubbedUser: User?
var stubbedError: Error?
// Call tracking
private(set) var fetchUserCallCount = 0
private(set) var fetchUserLastId: String?
func fetchUser(id: String) async throws -> User {
fetchUserCallCount += 1
fetchUserLastId = id
if let error = stubbedError { throw error }
guard let user = stubbedUser else {
throw MockError.notStubbed
}
return user
}
}
// Test with verification
func testFetchesCorrectUser() async {
let mock = MockUserService()
mock.stubbedUser = User(id: "123", name: "John")
let viewModel = UserViewModel(userService: mock)
await viewModel.loadUser(id: "123")
XCTAssertEqual(mock.fetchUserCallCount, 1)
XCTAssertEqual(mock.fetchUserLastId, "123")
}swift
final class MockUserService: UserServiceProtocol {
// Stubbed returns
var stubbedUser: User?
var stubbedError: Error?
// Call tracking
private(set) var fetchUserCallCount = 0
private(set) var fetchUserLastId: String?
func fetchUser(id: String) async throws -> User {
fetchUserCallCount += 1
fetchUserLastId = id
if let error = stubbedError { throw error }
guard let user = stubbedUser else {
throw MockError.notStubbed
}
return user
}
}
// Test with verification
func testFetchesCorrectUser() async {
let mock = MockUserService()
mock.stubbedUser = User(id: "123", name: "John")
let viewModel = UserViewModel(userService: mock)
await viewModel.loadUser(id: "123")
XCTAssertEqual(mock.fetchUserCallCount, 1)
XCTAssertEqual(mock.fetchUserLastId, "123")
}Quick Reference
快速参考
When to Use Each Pattern
各模式适用场景
| Pattern | Use When | Avoid When |
|---|---|---|
| Constructor injection | Default choice | Never avoid |
| Default parameters | Convenience with testability | Dependency changes at runtime |
| Property injection | Framework requires it (rare) | You have control over init |
| Factory | Object needs runtime parameters | Simple object creation |
| Container | Many cross-cutting dependencies | Small app, few dependencies |
| 模式 | 适用场景 | 避免场景 |
|---|---|---|
| 构造函数注入 | 默认选择 | 无(绝对不要避免) |
| 默认参数 | 兼顾便捷性与可测试性 | 依赖在运行时会变化 |
| 属性注入 | 框架强制要求(罕见) | 你能控制初始化过程 |
| 工厂模式 | 对象需要运行时参数 | 简单对象创建 |
| 容器模式 | 存在大量跨领域依赖 | 小型应用、依赖较少 |
Protocol Checklist
协议检查清单
- Will it be mocked? If no, skip protocol
- Interface is minimal (only needed methods)
- No mutable state requirements
- No implementation details leaked
- Single responsibility
- 是否需要被模拟?如果不需要,跳过协议
- 接口是否最小化(仅包含所需方法)
- 没有可变状态要求
- 未泄露实现细节
- 单一职责
DI Red Flags
DI警示信号
| Smell | Problem | Fix |
|---|---|---|
| Protocol for every class | Over-engineering | Only where needed |
| Service Locator | Hidden dependencies | Constructor injection |
| > 5 constructor params | Class does too much | Split responsibilities |
| Property injection | Object can be invalid | Constructor injection |
| Mock does real work | Tests are slow/flaky | Return stubbed data |
| 1:1 protocol:class ratio | Unnecessary abstraction | Remove unused protocols |
| 代码异味 | 问题 | 修复方案 |
|---|---|---|
| 每个类都对应一个协议 | 过度设计 | 仅在必要时使用协议 |
| 服务定位器 | 依赖隐藏 | 使用构造函数注入 |
| 构造函数参数>5个 | 类职责过多 | 拆分职责 |
| 属性注入 | 对象可能处于无效状态 | 使用构造函数注入 |
| 模拟对象带有真实副作用 | 测试缓慢/不稳定 | 返回桩数据 |
| 协议与类1:1对应 | 不必要的抽象 | 移除未使用的协议 |
SwiftUI DI Comparison
SwiftUI DI对比
| Pattern | Scope | Use For |
|---|---|---|
| View hierarchy | System/app services |
| View hierarchy | Observable shared state |
| Single view | View-specific ViewModel |
| Container factory | App-wide | Complex dependency graphs |
| 模式 | 作用范围 | 适用场景 |
|---|---|---|
| 视图层级 | 系统/应用级服务 |
| 视图层级 | 可观察的共享状态 |
| 单个视图 | 视图专属ViewModel |
| 容器工厂 | 全局应用 | 复杂依赖图 |