简单的Swift函数的依赖注入 本文是翻译,原文链接:Simple Swift dependency injection with functions
依赖注入是一种很好的解耦代码的手段,使代码变得易于测试。比起来对象自己创建自己的依赖,从外部注入,使得我们可以设置不同的场景————例如在生产中 vs 在测试中。
在Swift中,大多数时候,我们用协议来实现依赖注入。例如,我们写一个简单的卡片游戏,用Randomizer(随机性发生器)画一个随机的卡片,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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。协议和默认实现如下:
1 2 3 4 5 6 7 8 9 protocol Randomizer { func randomNumber (upperBound : UInt32 ) -> UInt32 } class DefaultRandomizer : Randomizer { func randomNumber (upperBound : UInt32 ) -> UInt32 { return arc4random_uniform(upperBound) } }
当我们设计的API非常复杂时,用协议实现依赖注入是非常好的。但是,当只有简单的目的(只需要一个简单的方法),用函数来实现可以减少复杂度。
上面的DefaultRandomizer本质上是arc4random_uniform的封装,所以为什么不试着通过传递一个函数类型来实现依赖注入,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 randomizationUpperBound = upperBound return 0 }) XCTAssertEqual (randomizationUpperBound, 1 ) XCTAssertEqual (game.drawRandomCard(), Card (value: .ace, suite: .spades)) } }
我个人特别喜欢这种技术,因为可以写更少的代码,易于理解(直接把函数放在初始化方法中),同时还能实现依赖注入。
你怎么看?