๐ฃ DI (Dependency Injection) — ์์กด์ฑ ์ฃผ์
๊ฐ๋
- ๊ฐ์ฒด๊ฐ ์์ ์ด ์ฌ์ฉํ ๊ฐ์ฒด(์์กด์ฑ)๋ฅผ ์ง์ ์์ฑํ์ง ์๊ณ ์ธ๋ถ์์ ์ฃผ์
๋ฐ๋ ๋ฐฉ์.
- ๊ฐ์ฒด ๊ฐ์ ๊ฒฐํฉ๋๋ฅผ ๋ฎ์ถ๊ณ , ํ
์คํธ๋ ํ์ฅ์ฑ์ ๋์ผ ์ ์์.
Swift ์์
protocol NetworkService {
func fetchRandomNumber() async -> Int?
}
class RealNetworkService: NetworkService {
func fetchRandomNumber() async -> Int? {
let urlString = "http://www.randomnumberapi.com/api/v1.0/random"
guard let url = URL(string: urlString) else { return nil }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let numbers = try JSONDecoder().decode([Int].self, from: data)
return numbers.first
} catch {
return nil
}
}
}
class MockNetworkService: NetworkService {
func fetchRandomNumber() async -> Int? {
// ํญ์ ๊ณ ์ ๋ ๊ฐ ๋ฐํ (์: 42)
return 42
}
}
// ์์กด์ฑ ์ฃผ์
์ ๋ฐ๋ ์ชฝ
class RandomNumberViewModel: ObservableObject {
private let networkService: NetworkService
@Published var number: Int?
init(service: NetworkService) {
self.networkService = service
}
@MainActor
func loadNumber() async {
self.number = await networkService.fetchRandomNumber()
}
}
// ์ค์ ์ฌ์ฉ
let realVM = ViewModel(networkService: RealNetworkService())
let mockVM = ViewModel(networkService: MockNetworkService())
์ข
๋ฅ
- ์์ฑ์ ์ฃผ์
(Constructor Injection) → ์ ์์
- ์์ฑ ์ฃผ์
(Property Injection)
- ๋ฉ์๋ ์ฃผ์
(Method Injection)
๐ฃ DIP (Dependency Inversion Principle) — ์์กด ์ญ์ ์์น
๊ฐ๋
(SOLID ์์น ์ค ํ๋)
- ์์ ๋ชจ๋(๋น์ฆ๋์ค ๋ก์ง)์ด ํ์ ๋ชจ๋(๊ตฌํ์ฒด)์ ์์กดํ์ง ์๊ณ ,
- ๋ ๋ค ์ถ์ํ(์ธํฐํ์ด์ค, ํ๋กํ ์ฝ)์ ์์กดํด์ผ ํ๋ค.
์ฆ, ๊ตฌ์ฒด์ ์ธ ํด๋์ค๊ฐ ์๋๋ผ ์ถ์ํ๋ ํ์
(Protocol)์ ์์กดํ๋ผ๋ ์์น.
Swift์์์ ์ ์ฉ
// ViewModel์ ๊ตฌ์ฒด์ ์ธ RealNetworkService์ ์์กดํ์ง ์์
// NetworkService๋ผ๋ ์ถ์ํ์ ์์กด
protocol NetworkService {
func fetchRandomNumber() async -> Int?
}
class RealNetworkService: NetworkService {
func fetchRandomNumber() async -> Int? {
let urlString = "http://www.randomnumberapi.com/api/v1.0/random"
guard let url = URL(string: urlString) else { return nil }
do {
let (data, _) = try await URLSession.shared.data(from: url)
let numbers = try JSONDecoder().decode([Int].self, from: data)
return numbers.first
} catch {
return nil
}
}
}
class RandomNumberViewModel: ObservableObject {
private let networkService: NetworkService
@Published var number: Int?
init(service: NetworkService) {
self.networkService = service
}
@MainActor
func loadNumber() async {
self.number = await networkService.fetchRandomNumber()
}
}
- ์ด๋ ๊ฒ ํ๋ฉด ViewModel์ NetworkService์ ๊ตฌํ์ด ๋ญ์ง ์ ํ์ ์์
- ํ
์คํธ ์ MockNetworkService๋ฅผ ์ฃผ์
ํด๋ ๋ฌธ์ ์์
- DIP๋ฅผ ๋ง์กฑํ๋ฉด DI๊ฐ ๊ฐ๋ฅํด์ง๊ณ , DI๋ DIP๋ฅผ ์คํํ๋ ์๋จ์ด ๋๊ธฐ๋ ํจ
DI, DIP ์์ฝ
DI |
"์์กด์ฑ ์ฃผ์
" – ๊ฐ์ฒด๊ฐ ํ์ํ ์์กด์ฑ์ ์ธ๋ถ๋ก๋ถํฐ ์ฃผ์
๋ฐ์ |
DIP |
"์์กด ์ญ์ ์์น" – ๊ณ ์์ค ๋ชจ๋์ด ์ ์์ค ๊ตฌํ์ด ์๋ ์ถ์ํ์ ์์กดํด์ผ ํจ |
๊ด๊ณ |
DIP๋ฅผ ์คํํ๊ธฐ ์ํด DI๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ |
๐ ์ค์ ์ฑ ๊ตฌ์กฐ์์ ์ด๋ป๊ฒ ์ฐ์ผ๊น?
์: MVVM ์ํคํ
์ฒ์์
class SomeView: View {
@StateObject var viewModel: SomeViewModel
init(service: NetworkService) {
_viewModel = StateObject(wrappedValue: SomeViewModel(service: service))
}
}
- SomeViewModel์ NetworkService๋ผ๋ ์ถ์ํ์ ์์กด
- ์์กด์ฑ์ View ๋๋ ์ธ๋ถ DI Container์์ ์ฃผ์
- ํ
์คํธ์์๋ MockNetworkService ์ฃผ์
๊ฐ๋ฅ