Tailwind CSSTailwind CSS
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain
Home
  • Tailwind CSS 书籍目录
  • Vue 3 开发实战指南
  • React 和 Next.js 学习
  • TypeScript
  • React开发框架书籍大纲
  • Shadcn学习大纲
  • Swift 编程语言:从入门到进阶
  • SwiftUI 学习指南
  • 函数式编程大纲
  • Swift 异步编程语言
  • Swift 协议化编程
  • SwiftUI MVVM 开发模式
  • SwiftUI 图表开发书籍
  • SwiftData
  • ArkTS编程语言:从入门到精通
  • 仓颉编程语言:从入门到精通
  • 鸿蒙手机客户端开发实战
  • WPF书籍
  • C#开发书籍
learn
  • Java编程语言
  • Kotlin 编程入门与实战
  • /python/outline.html
  • AI Agent
  • MCP (Model Context Protocol) 应用指南
  • 深度学习
  • 深度学习
  • 强化学习: 理论与实践
  • 扩散模型书籍
  • Agentic AI for Everyone
langchain

第5章:协议的依赖注入与解耦

5.1 依赖注入的基本概念

依赖注入(Dependency Injection,简称 DI)是一种设计模式,旨在通过将对象的依赖从外部传入而非内部创建,来降低模块间的耦合度并提升代码的可测试性。在 Swift 中,协议化编程(POP)为依赖注入提供了天然支持,通过抽象依赖关系,开发者可以轻松实现灵活、可维护的系统。本节将介绍依赖注入的基本概念、原理和优势,为后续探讨协议在 DI 中的应用奠定基础。

什么是依赖注入?

依赖注入是指在对象创建时,将其所需的依赖(其他对象或服务)通过外部提供,而不是由对象自身负责创建或查找。例如:

class UserManager {
    let database: Database
    
    init(database: Database) {
        self.database = database
    }
    
    func saveUser(_ user: String) {
        database.save(user)
    }
}

protocol Database {
    func save(_ data: String)
}

struct LocalDatabase: Database {
    func save(_ data: String) {
        print("Saved to local: \(data)")
    }
}

let db = LocalDatabase()
let manager = UserManager(database: db)
manager.saveUser("Alice") // 输出: Saved to local: Alice
  • UserManager 不直接创建 Database,而是通过构造函数注入。
  • 依赖关系从内部“硬编码”转为外部“传入”。

依赖注入的类型

依赖注入有三种常见形式:

  1. 构造函数注入(Constructor Injection):
    • 通过初始化方法传入依赖,如上例所示。
    • 优点:依赖明确,强制性强。
  2. 属性注入(Property Injection):
    • 通过属性设置依赖:
      class UserManager {
          var database: Database?
          
          func saveUser(_ user: String) {
              database?.save(user)
          }
      }
      
      let manager = UserManager()
      manager.database = LocalDatabase()
      
    • 优点:灵活性高;缺点:依赖可选,可能未初始化。
  3. 方法注入(Method Injection):
    • 在方法调用时传入依赖:
      class UserManager {
          func saveUser(_ user: String, database: Database) {
              database.save(user)
          }
      }
      
      let manager = UserManager()
      manager.saveUser("Alice", database: LocalDatabase())
      
    • 优点:按需注入;缺点:调用时需反复传递。

构造函数注入因其清晰性和强制性,在 Swift 中最为常用。

依赖注入的原理

依赖注入的核心思想是控制反转(Inversion of Control,IoC):

  • 传统方式:对象自己创建依赖(控制权在对象内部)。
  • DI 方式:依赖由外部容器或调用者提供(控制权反转到外部)。

例如,没有 DI 的代码:

class UserManager {
    private let database = LocalDatabase() // 硬编码依赖
    
    func saveUser(_ user: String) {
        database.save(user)
    }
}

这种方式将 UserManager 与 LocalDatabase 紧耦合,难以替换或测试。使用 DI 后,依赖被抽象为协议,外部注入具体实现,解除了耦合。

依赖注入的优势

  1. 降低耦合度:
    • 模块间通过协议交互,具体实现可随时替换。
  2. 提高可测试性:
    • 可以注入模拟(Mock)对象,方便单元测试。
    struct MockDatabase: Database {
        func save(_ data: String) {
            print("Mock save: \(data)")
        }
    }
    
    let mockManager = UserManager(database: MockDatabase())
    mockManager.saveUser("Test") // 输出: Mock save: Test
    
  3. 增强灵活性:
    • 支持运行时动态切换依赖,例如切换本地存储到云存储。
  4. 提升可维护性:
    • 依赖关系清晰,修改一个模块不会影响其他模块。

依赖注入与协议化编程

Swift 的协议为 DI 提供了理想支持:

  • 抽象接口:协议定义依赖的接口,不关心实现。
  • 类型安全:编译器确保注入的类型符合协议要求。
  • 灵活实现:结构体、类等都可以遵循协议,注入方式多样。

例如,一个更复杂的场景:

protocol Logger {
    func log(_ message: String)
}

struct ConsoleLogger: Logger {
    func log(_ message: String) {
        print("[Console] \(message)")
    }
}

class AppService {
    let database: Database
    let logger: Logger
    
    init(database: Database, logger: Logger) {
        self.database = database
        self.logger = logger
    }
    
    func processUser(_ user: String) {
        database.save(user)
        logger.log("User \(user) processed")
    }
}

let app = AppService(database: LocalDatabase(), logger: ConsoleLogger())
app.processUser("Bob")
// 输出:
// Saved to local: Bob
// [Console] User Bob processed
  • AppService 依赖 Database 和 Logger,通过构造函数注入。
  • 协议抽象了依赖的具体实现。

注意事项

  1. 依赖管理:手动注入可能在大型项目中变得繁琐,可考虑依赖注入框架(如 Swinject)。
  2. 初始化顺序:确保依赖在对象使用前被正确注入。
  3. 设计权衡:DI 增加了一些复杂性,需在简单性和灵活性间平衡。

小结

依赖注入通过将依赖从内部创建转为外部传入,显著降低了模块间的耦合度,提升了代码的可测试性和可维护性。Swift 的协议为 DI 提供了强大的支持,使其成为 POP 的重要实践手段。下一节将探讨如何使用协议实现松耦合,进一步深化这一设计理念的应用。


Last Updated:: 3/10/25, 3:28 PM