簡單的 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))
}
}
我個人特別喜歡這種技術,因為可以寫更少的程式碼,易於理解(直接把函數放在初始化方法中),同時還能實現依賴注入。
你怎麼看?