今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

簡單的Swift函數的依賴注入

簡單的 Swift 函數的依賴注入#

本文是翻譯,原文連結:Simple Swift dependency injection with functions

依賴注入是一種很好的解耦程式碼的手段,使程式碼變得易於測試。比起依賴自行創建自己的對象,從外部注入,使得我們可以設定不同的場景 ———— 例如在生產中 vs 在測試中。

在 Swift 中,大多數時候,我們用協議來實現依賴注入。例如,我們寫一個簡單的卡片遊戲,用 Randomizer (隨機性發生器) 畫一個隨機的卡片,如下所示:

class CardGame {
    private let deck: Deck
    private let randomizer: Randomizer

    init(deck: Dec, randomizer: Randomizer = DefaultRandomizer()) {
        self.deck = deck
        self.randomizer = randomizer
    }

    func drawRandomCard() -> Card {
        let index = randomizer.randomNumber(upperBound: deck.count)
        let card = deck[index]
        return card
    }

}

上面的例子中,可以看到,我們在 CardGame 的初始化中,注入了一個 Randomizer,用於在繪製時生成一個隨機的 index。為了使 API 易於使用,在沒有給定 randomizer 時,我們還給它賦值了一個默認值 ————DefaultRandomizer。協議和默認實現如下:

protocol Randomizer {
    func randomNumber(upperBound: UInt32) -> UInt32
}

class DefaultRandomizer: Randomizer {
    func randomNumber(upperBound: UInt32) -> UInt32 {
        return arc4random_uniform(upperBound)
    }
}

當我們設計的 API 非常複雜時,用協議實現依賴注入是非常好的。但是,當只有簡單的目的(只需要一個簡單的方法),用函數來實現可以減少複雜度。

上面的 DefaultRandomizer 本質上是 arc4random_uniform 的封裝,所以為什麼不試著通過傳遞一個函數類型來實現依賴注入,如下所示:

class CardGame {
    typealias Randomizer = (UInt32) -> UInt32

    private let deck: Deck
    private let randomizer: Randomizer

    init(deck: Deck, randomizer: @escaping Randomizer = arc4random_uniform) {
        self.deck = deck
        self.randomizer = randomizer
    }

    func drawRandomCard() -> Card {
        let index = randomizer(deck.count)
        let card = deck[index]
        return card
    }
}

我們把 Randomizer 從協議變為簡單的 typealias,並且把 arc4random_uniform 函數直接做為randomizer 的默認參數。再也不需要默認實現的類,同時還可以輕易 mock 測試 randomizer:

class CardGameTests: XCTestCase {
    func testDrawingRandomCard() {
        var randomizationUpperBound: UInt32?

        let deck = Deck(cards: [Card(value: .ace, suite: .spades)])

        let game = Cardgame(deck: deck, randomizer: { upperBound in
            // Capture the upper bound to be able to assert it later
            randomizationUpperBound = upperBound

            // Return a constant value to remove randomness from out test, making it run consistently
            return 0
        })

        XCTAssertEqual(randomizationUpperBound, 1)
        XCTAssertEqual(game.drawRandomCard(), Card(value: .ace, suite: .spades))
    }
}

我個人特別喜歡這種技術,因為可以寫更少的程式碼,易於理解(直接把函數放在初始化方法中),同時還能實現依賴注入。

你怎麼看?

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。