dependency-injection

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dependency 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 container
The 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 fine
Rule 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 = .medium
Exception: 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 service

Service 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

各模式适用场景

PatternUse WhenAvoid When
Constructor injectionDefault choiceNever avoid
Default parametersConvenience with testabilityDependency changes at runtime
Property injectionFramework requires it (rare)You have control over init
FactoryObject needs runtime parametersSimple object creation
ContainerMany cross-cutting dependenciesSmall 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警示信号

SmellProblemFix
Protocol for every classOver-engineeringOnly where needed
Service LocatorHidden dependenciesConstructor injection
> 5 constructor paramsClass does too muchSplit responsibilities
Property injectionObject can be invalidConstructor injection
Mock does real workTests are slow/flakyReturn stubbed data
1:1 protocol:class ratioUnnecessary abstractionRemove unused protocols
代码异味问题修复方案
每个类都对应一个协议过度设计仅在必要时使用协议
服务定位器依赖隐藏使用构造函数注入
构造函数参数>5个类职责过多拆分职责
属性注入对象可能处于无效状态使用构造函数注入
模拟对象带有真实副作用测试缓慢/不稳定返回桩数据
协议与类1:1对应不必要的抽象移除未使用的协议

SwiftUI DI Comparison

SwiftUI DI对比

PatternScopeUse For
@Environment
View hierarchySystem/app services
@EnvironmentObject
View hierarchyObservable shared state
@StateObject
init injection
Single viewView-specific ViewModel
Container factoryApp-wideComplex dependency graphs
模式作用范围适用场景
@Environment
视图层级系统/应用级服务
@EnvironmentObject
视图层级可观察的共享状态
@StateObject
初始化注入
单个视图视图专属ViewModel
容器工厂全局应用复杂依赖图