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.4 实战案例:重构一个耦合严重的模块

在实际开发中,耦合严重的代码往往难以维护和测试。通过协议化编程(POP)和依赖注入(DI),我们可以将紧耦合的模块重构为松耦合、可测试的结构。本节将通过一个实战案例,展示如何识别耦合问题,并利用协议重构一个耦合严重的模块,逐步将其转化为模块化、可扩展的设计。

案例背景

假设我们有一个电商应用的支付模块,初始实现如下:

class CheckoutManager {
    private let paymentProcessor = StripeProcessor()
    private let logger = FileLogger()
    
    func checkout(amount: Double, userId: String) -> Bool {
        logger.log("Starting checkout for user \(userId) with amount \(amount)")
        let success = paymentProcessor.process(amount: amount)
        if success {
            logger.log("Checkout succeeded for user \(userId)")
        } else {
            logger.log("Checkout failed for user \(userId)")
        }
        return success
    }
}

class StripeProcessor {
    func process(amount: Double) -> Bool {
        print("Processing \(amount) via Stripe")
        return true // 模拟支付成功
    }
}

class FileLogger {
    func log(_ message: String) {
        print("File log: \(message)") // 模拟写入文件
    }
}

let checkout = CheckoutManager()
checkout.checkout(amount: 99.99, userId: "u123")
// 输出:
// File log: Starting checkout for user u123 with amount 99.99
// Processing 99.99 via Stripe
// File log: Checkout succeeded for user u123
问题分析
  1. 紧耦合:
    • CheckoutManager 直接依赖具体类 StripeProcessor 和 FileLogger。
    • 无法切换支付方式或日志记录方式。
  2. 不可测试:
    • 测试时无法隔离支付和日志逻辑,依赖真实实现。
  3. 扩展性差:
    • 添加新支付方式(如 PayPal)或日志方式(如控制台)需要修改 CheckoutManager。

重构目标

  • 使用协议抽象依赖。
  • 通过依赖注入解耦。
  • 提高测试性和扩展性。

第一步:定义协议

提取支付和日志的接口:

protocol PaymentProcessor {
    func process(amount: Double) -> Bool
}

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

将现有实现改为遵循协议:

class StripeProcessor: PaymentProcessor {
    func process(amount: Double) -> Bool {
        print("Processing \(amount) via Stripe")
        return true
    }
}

class FileLogger: Logger {
    func log(_ message: String) {
        print("File log: \(message)")
    }
}

第二步:注入依赖

修改 CheckoutManager,通过构造函数注入依赖:

class CheckoutManager {
    private let paymentProcessor: PaymentProcessor
    private let logger: Logger
    
    init(paymentProcessor: PaymentProcessor, logger: Logger) {
        self.paymentProcessor = paymentProcessor
        self.logger = logger
    }
    
    func checkout(amount: Double, userId: String) -> Bool {
        logger.log("Starting checkout for user \(userId) with amount \(amount)")
        let success = paymentProcessor.process(amount: amount)
        if success {
            logger.log("Checkout succeeded for user \(userId)")
        } else {
            logger.log("Checkout failed for user \(userId)")
        }
        return success
    }
}

let checkout = CheckoutManager(paymentProcessor: StripeProcessor(), logger: FileLogger())
checkout.checkout(amount: 99.99, userId: "u123")
// 输出与之前相同
  • 依赖从硬编码变为外部注入,解耦完成。

第三步:扩展功能

添加新的支付和日志实现,验证扩展性:

class PayPalProcessor: PaymentProcessor {
    func process(amount: Double) -> Bool {
        print("Processing \(amount) via PayPal")
        return true
    }
}

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

let paypalCheckout = CheckoutManager(paymentProcessor: PayPalProcessor(), logger: ConsoleLogger())
paypalCheckout.checkout(amount: 50.0, userId: "u456")
// 输出:
// Console log: Starting checkout for user u456 with amount 50.0
// Processing 50.0 via PayPal
// Console log: Checkout succeeded for user u456
  • 无需修改 CheckoutManager,即可支持新实现。

第四步:添加单元测试

为测试创建 Mock 对象:

struct MockPaymentProcessor: PaymentProcessor {
    let shouldSucceed: Bool
    
    func process(amount: Double) -> Bool {
        print("Mock payment of \(amount)")
        return shouldSucceed
    }
}

struct MockLogger: Logger {
    var messages: [String] = []
    
    func log(_ message: String) {
        messages.append(message)
    }
}

编写测试用例:

import XCTest
@testable import YourModule

class CheckoutManagerTests: XCTestCase {
    
    func testCheckoutSuccess() {
        let mockPayment = MockPaymentProcessor(shouldSucceed: true)
        let mockLogger = MockLogger()
        let checkout = CheckoutManager(paymentProcessor: mockPayment, logger: mockLogger)
        
        let result = checkout.checkout(amount: 25.0, userId: "u789")
        XCTAssertTrue(result)
        XCTAssertEqual(mockLogger.messages, [
            "Starting checkout for user u789 with amount 25.0",
            "Checkout succeeded for user u789"
        ])
    }
    
    func testCheckoutFailure() {
        let mockPayment = MockPaymentProcessor(shouldSucceed: false)
        let mockLogger = MockLogger()
        let checkout = CheckoutManager(paymentProcessor: mockPayment, logger: mockLogger)
        
        let result = checkout.checkout(amount: 10.0, userId: "u999")
        XCTAssertFalse(result)
        XCTAssertEqual(mockLogger.messages, [
            "Starting checkout for user u999 with amount 10.0",
            "Checkout failed for user u999"
        ])
    }
}
  • 测试验证了成功和失败场景,检查逻辑和日志交互。

重构后的优势

  1. 松耦合:
    • CheckoutManager 只依赖协议,不关心具体实现。
  2. 可测试性:
    • Mock 对象隔离了支付和日志依赖,测试简单高效。
  3. 可扩展性:
    • 添加新支付方式或日志方式只需实现协议并注入。
  4. 可维护性:
    • 模块职责清晰,修改某一实现不影响其他部分。

进一步优化

  1. 协议扩展:为 Logger 添加默认实现:
    extension Logger {
        func logError(_ message: String) {
            log("ERROR: \(message)")
        }
    }
    
    checkout.logger.logError("Payment issue") // 可直接使用
    
  2. 依赖容器:在大型项目中,可使用 DI 容器管理依赖:
    struct Dependencies {
        let paymentProcessor: PaymentProcessor
        let logger: Logger
    }
    
    let deps = Dependencies(paymentProcessor: StripeProcessor(), logger: FileLogger())
    let checkout = CheckoutManager(paymentProcessor: deps.paymentProcessor, logger: deps.logger)
    

注意事项

  • 重构成本:初始重构需投入时间,但长期收益显著。
  • 协议粒度:避免过于细化,保持接口简洁。
  • 测试覆盖:确保测试涵盖所有关键路径。

小结

通过重构一个耦合严重的支付模块,我们展示了协议如何将紧耦合代码转化为松耦合、可测试的设计。这一过程体现了 POP 和 DI 的核心价值,为开发者提供了一个实用的重构模板。下一节将探讨协议在模块化架构中的应用,进一步扩展这些实践。


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