클래스가 내부적으로 하나 이상의 자원에 의존하고 그 자원이 클래스 동작에 영향을 줄 때의 경우 자원을 직접 명시하지 말고 의존 객체 주입을 사용하는 것이 좋습니다.
사용하는 자원에 따라 동작이 달라지는 클래스에서 정적 유틸리티 클래스나 싱글턴 방식은 적합하지 않습니다.
static func isValid(word: String) -> Bool { … } static func suggestion(typo: String) -> [String] { … } }
2. 싱글턴을 잘못 사용한 예
```swift
class SpellChecker {
private var dictionary = Lexicon()
private init( ... ) {}
static let sharedInstance = SpellChecker()
func isValid(word: String) -> Bool { ... }
func suggestion(typo: String) -> [String] { ... }
}
static let sharedInstance = SpellChecker() func isValid(word: String) -> Bool { … } func suggestion(typo: String) -> [String] { … } func changeDictionary(to newDictionary: Lexicon) { … } }
* 코드 1과 코드 2는 사전을 단 하나만 사용한다고 가정하고 구현하였습니다. 유연하지 않고 테스트하기 어렵습니다.
* 코드 3은 멀티스레드 환경에서 사용할 수 없습니다.
**따라서 클래스가 여러 인스턴스를 지원해야하며 클라이언트가 원하는 자원을 사용해야하는 경우에는 정적 유틸리티 클래스나 싱글턴 방식을 사용하기 보다 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 것이 좋습니다.(의존 객체 주입의 한 형태로 생성자 주입에 해당)**
### 생성자 주입을 사용한 예
1. 생성자 주입을 사용한 예
```swift
class SpellChecker {
private var dictionary = Lexicon()
private init(newDictionary: Lexicon) {
self.dictionary = newDictionary
}
func isValid(word: String) -> Bool { ... }
func suggestion(typo: String) -> [String] { ... }
}
의존성 주입: 코드에서 두 모듈 간의 연결. 객체지향 언어에서는 두 클래스 간의 관계라고도 한다.
Initializer injection(Constructor Injection)
protocol Drinkable {
var volume: Int { get }
}
struct Pepsi: Drinkable {
var volume: Int {
return 500
}
}
class VendingMachine {
let beverage: Drinkable
init(_ beverage: Drinkable) {
self.beverage = beverage
}
}
let vendingMachine = VendingMachine(Pepsi())
Property Injection
protocol Drinkable {
var volume: Int { get }
}
struct Pepsi: Drinkable {
var volume: Int {
return 500
}
}
class VendingMachine {
var beverage: Drinkable?
}
let vendingMachine = VendingMachine()
vendingMachine.beverage = Pepsi()
Method Injection
protocol Drinkable {
var volume: Int { get }
}
struct Pepsi: Drinkable {
var volume: Int {
return 500
}
}
class VendingMachine {
var beverage: Drinkable?
func setupBeverage(_ beverage: Drinkable) {
self.beverage = beverage
}
}
let vendingMachine = VendingMachine()
vendingMachine.setupBeverage(Pepsi())
class ViewController: UIViewController { var requestManager: RequestManager? }
class newViewController { let viewController = ViewController() viewController.requestManager = RequestManager() }
// Using Protocol protocol Serializer { func serialize(data: AnyObject) -> NSData? }
class RequestSerializer: Serializer { func serialize(data: AnyObject) -> NSData? { … } }
class DataManager { var serializer: Serializer? = RequestSerializer() }
2. Initializer injection
* 네트워크에 이미지 요청
```swift
import UIKit
protocol UserManagable {
func getUser(with userID: Int)
}
protocol ImageManagable {
func getImages(of userID: Int) -> [UIImage]
}
class ImageNetworkManager: ImageManagable {
private let userManager: UserManagable
// 의존성 주입
init(userManager: UserManagable) {
self.userManager = userManager
}
func getImages(of userID: Int) -> [UIImage] {
let user = userManager.getUser(with: userID)
let images = { ... }
return images
}
}
class PhotoGalleryController {
private let imageManager: ImageManagable
// 의존성 주입
init(imageManager: ImageManagable) {
self.imageManager = imageManager
}
func getImagesSortedInRecentOrder(of userID: Int) -> [UIImage] {
let images = imageManager.getImages(of: userID)
return images.sorted { $0.timestamp > $1.timestamp }
}
}
클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋습니다. 이 자원들을 클래스가 직접 만들게 해서도 안됩니다. 대신 필요한 자원을(혹은 그 자원을 만들어주는 팩터리를) 생성자에 (혹은 정적 팩터리나 빌더에) 넘겨주는 것이 좋습니다. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 개선해줍니다.