自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Swift 中風(fēng)味各異的依賴注入

移動開發(fā) iOS
就像大多數(shù)編程技術(shù)一樣,依賴注入有多種“風(fēng)味(Flavors)”,每一種都有自己的優(yōu)點(diǎn)和缺點(diǎn)。本周,讓我們來看看三種不同方式的依賴注入,以及它們?nèi)绾卧赟wift中使用。

前言

在之前的文章中,我們看了一些使用依賴注入的不同方法,以實(shí)現(xiàn)Swift應(yīng)用中更多的解耦和可測試架構(gòu)。例如, 在Swift中使用工廠的依賴注入[1]中把依賴注入和工廠模式結(jié)合起來,以及在Swift中避免使用單例[2] 中利用依賴注入取代單利。

到目前為止,我的大部分文章和例子都使用了基于初始化器的依賴注入。然而,就像大多數(shù)編程技術(shù)一樣,依賴注入有多種“風(fēng)味(Flavors)”,每一種都有自己的優(yōu)點(diǎn)和缺點(diǎn)。本周,讓我們來看看三種不同方式的依賴注入,以及它們?nèi)绾卧赟wift中使用。

基于初始化器

讓我們先快速回顧一下最常見的依賴注入方式——基于初始化器的依賴注入,即對象在被初始化時(shí)應(yīng)該被賦予它所需要的依賴關(guān)系。這種方式的最大好處是,它保證我們的對象擁有它們所需要的一切,以便立即開展工作。

假設(shè)我們正在構(gòu)建一個(gè)從磁盤上加載文件的FileLoader。為了做到這一點(diǎn),它使用了兩個(gè)依賴項(xiàng)——一個(gè)是系統(tǒng)提供的FileManager的實(shí)例,另一個(gè)是Cache。使用基于初始化器的依賴注入,可以這樣實(shí)現(xiàn):

class FileLoader {
private let fileManager: FileManager
private let cache: Cache

init(fileManager: FileManager = .default,
cache: Cache = .init()) {
self.fileManager = fileManager
self.cache = cache
}
}

注意上面是如何使用默認(rèn)參數(shù)的,以避免在使用單例或新實(shí)例時(shí)總是創(chuàng)建依賴關(guān)系。這使我們能夠在生產(chǎn)代碼中使用FileLoader()簡單地創(chuàng)建一個(gè)文件加載器,同時(shí)仍然能夠通過在測試代碼中注入模擬數(shù)據(jù)或顯式實(shí)例進(jìn)行測試。

基于屬性

雖然基于初始化器的依賴注入通常很適合你自己的自定義類,但有時(shí)當(dāng)你必須從系統(tǒng)類繼承時(shí),它就有點(diǎn)難用了。一個(gè)例子是在構(gòu)建視圖控制器時(shí),特別是當(dāng)你使用 XIBs 或 Storyboards 來定義它們時(shí),因?yàn)檫@樣你就無法再控制你的類的初始化器了。

對于這些類型的情況,基于屬性的依賴注入可以是一個(gè)很好的選擇。與其在對象的初始化器中注入對象的依賴關(guān)系,不如在之后簡單地將其分配。這種依賴注入的方式也可以幫助你減少模板文件,特別是當(dāng)有一個(gè)好的默認(rèn)值不一定需要注入的時(shí)候。

讓我們來看看另一個(gè)例子——在這個(gè)例子中,我們要建立一個(gè)PhotoEditorViewController,讓用戶編輯他們庫中的一張照片。為了發(fā)揮作用,這個(gè)視圖控制器需要一個(gè)系統(tǒng)提供的PHPhotoLibrary類的實(shí)例(它是一個(gè)單例),以及一個(gè)我們自己的PhotoEditorEngine類的實(shí)例。為了在沒有自定義初始化器的情況下實(shí)現(xiàn)依賴性注入,我們可以創(chuàng)建兩個(gè)都有默認(rèn)值的可變屬性,就像這樣:

class PhotoEditorViewController: UIViewController {
var library: PhotoLibrary = PHPhotoLibrary.shared()
var engine = PhotoEditorEngine()
}

請注意 *"通過 3 個(gè)簡單的步驟測試使用了系統(tǒng)單例的 Swift 代碼"*中的技術(shù)是如何通過使用協(xié)議來為系統(tǒng)照片庫類提供一個(gè)更抽象的PhotoLibrary接口。這將使測試和數(shù)據(jù)模擬變得更加容易!

上述做法的好處是,我們?nèi)匀豢梢院苋菀椎卦跍y試中注入模擬數(shù)據(jù),只需重新分配視圖控制器的屬性:

class PhotoEditorViewControllerTests: XCTestCase {
func testApplyingBlackAndWhiteFilter() {
let viewController = PhotoEditorViewController()
// 分配一個(gè)模擬照片庫以完全控制里面存儲了哪些照片
let library = PhotoLibraryMock()
library.photos = [TestPhotoFactory.photoWithColor(.red)]
viewController.library = library
// 運(yùn)行我們的測試命令
viewController.selectPhoto(atIndex: 0)
viewController.apply(filter: .blackAndWhite)
viewController.savePhoto()
// 斷言結(jié)果是正確的
XCTAssertTrue(photoIsBlackAndWhite(library.photos[0]))
}
}

基于參數(shù)

最后,讓我們看一下基于參數(shù)的依賴注入。當(dāng)你想輕松地使遺留代碼變得更容易測試且不必過多地改變其現(xiàn)有結(jié)構(gòu)時(shí),這種類型特別有用。

很多時(shí)候,我們只需要一個(gè)特定的依賴關(guān)系一次,或者我們只需要在某些條件下模擬它。我們不需要改變對象的初始化器或?qū)傩员┞稙榭勺兊?這并不總是一個(gè)好方式),而是可以開放某個(gè)API來接受一個(gè)依賴關(guān)系作為參數(shù)。

讓我們來看看一個(gè)NoteManager類,它是一個(gè)記事應(yīng)用程序的一部分。它的工作是管理用戶所寫的所有筆記,并提供一個(gè)API用于根據(jù)查詢來搜索筆記。由于這是一個(gè)可能需要一段時(shí)間的操作(如果用戶有很多筆記的話,這是很有可能的),我們通常在一個(gè)后臺隊(duì)列中執(zhí)行,像這樣:

class NoteManager {
func loadNotes(matching query: String,
completionHandler: @escaping ([Note]) -> Void) {
DispatchQueue.global(qos: .userInitiated).async {
let database = self.loadDatabase()
let notes = database.filter { note in
return note.matches(query: query)
}
completionHandler(notes)
}
}
}

雖然上述方法對我們的生產(chǎn)代碼來說是一個(gè)很好的解決方案,但在測試中,我們通常希望盡可能地避免異步代碼和并行性,以避免片狀現(xiàn)象。雖然使用初始化器或基于屬性的依賴注入來指定NoteManager應(yīng)始終使用的顯式隊(duì)列會很好,但這可能需要對類進(jìn)行大的修改,而我們現(xiàn)在還不能/不愿意這樣做。

這就是基于參數(shù)的依賴性注入的作用。與其重構(gòu)我們的整個(gè)類,不如直接注入要在哪個(gè)隊(duì)列上運(yùn)行l(wèi)oadNotes操作:

class NoteManager {
func loadNotes(matching query: String,
on queue: DispatchQueue = .global(qos: .userInitiated),
completionHandler: @escaping ([Note]) -> Void) {
queue.async {
let database = self.loadDatabase()
let notes = database.filter { note in
return note.matches(query: query)
}
completionHandler(notes)
}
}
}

這使我們能夠在測試代碼中輕松地使用一個(gè)自定義隊(duì)列,我們可以在上面等待。這幾乎可以讓我們在測試中把上述API變成一個(gè)同步的API,這讓事情變得更容易和更可預(yù)測。

基于參數(shù)的依賴注入的另一個(gè)用例是當(dāng)你想測試靜態(tài)API的時(shí)候。對于靜態(tài)API,我們沒有初始化器,而且我們最好也不要靜態(tài)地保持任何狀態(tài),所以基于參數(shù)的依賴注入成為一個(gè)很好的選擇。讓我們看一個(gè)當(dāng)前依賴單例的靜態(tài) MessageSender 類:

class MessageSender {
static func send(_ message: Message, to user: User) throws {
Database.shared.insert(message)
let data: Data = try wrap(message)
let endpoint = Endpoint.sendMessage(to: user)
NetworkManager.shared.post(data, to: endpoint.url)
}
}

雖然理想的長期解決方案可能是重構(gòu)MessageSender,使其成為非靜態(tài)的,并在其使用的任何地方正確注入,但為了方便測試(例如,為了重現(xiàn)/驗(yàn)證一個(gè)錯(cuò)誤),我們可以簡單地將其依賴性作為參數(shù)注入,而不是依賴單例:

class MessageSender {
static func send(_ message: Message,
to user: User,
database: Database = .shared,
networkManager: NetworkManager = .shared) throws {
database.insert(message)
let data: Data = try wrap(message)
let endpoint = Endpoint.sendMessage(to: user)
networkManager.post(data, to: endpoint.url)
}
}

我們再次使用默認(rèn)參數(shù),除去為了方便的原因,但這里更重要的是為了能夠在我們的代碼中添加測試支持,同時(shí)仍然保持100%的向后兼容性。

參考資料

[1]在Swift中使用工廠的依賴注入: https://www.swiftbysundell.com/articles/dependency-injection-using-factories-in-swift。

[2]在Swift中避免使用單例: https://www.swiftbysundell.com/articles/avoiding-singletons-in-swift。

責(zé)任編輯:姜華 來源: Swift社區(qū)
相關(guān)推薦

2022-03-31 09:01:10

Swift類型擦除類型安全性

2022-12-29 08:54:53

依賴注入JavaScript

2015-09-02 11:22:36

JavaScript實(shí)現(xiàn)思路

2011-05-31 10:00:21

Android Spring 依賴注入

2023-07-11 09:14:12

Beanquarkus

2024-05-27 00:13:27

Go語言框架

2017-08-16 16:00:05

PHPcontainer依賴注入

2024-12-30 12:00:00

.NET Core依賴注入屬性注入

2014-07-08 14:05:48

DaggerAndroid依賴

2016-03-21 17:08:54

Java Spring注解區(qū)別

2016-12-28 09:30:37

Andriod安卓平臺依賴注入

2021-07-25 21:13:50

框架Angular開發(fā)

2019-09-18 18:12:57

前端javascriptvue.js

2022-04-30 08:50:11

控制反轉(zhuǎn)Spring依賴注入

2009-09-08 15:22:20

Spring依賴注入

2020-10-28 14:46:48

人工智能醫(yī)療技術(shù)

2020-08-06 00:14:16

Spring IoC依賴注入開發(fā)

2023-06-29 08:32:41

Bean作用域

2024-11-27 00:24:04

2024-04-01 00:02:56

Go語言代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號