한참 전에 의존성 주입에 대해 공부하며 포스팅을 했었는데.. 오늘 다시 공부하면서 훨씬 많이 이해한 거 같아서 다시 정리하기 위함.
의존성 주입. DI라고 하는데.. 말이 참 어렵다.
의존성? (Dependency)
난 카페인에 너무 의존적이야. 할 때 그 의존이다.
카페인에 의존적인 사람은 카페인이 없으면 살지 못하겠지?(비유다.)
코드로 본다면
class 커피 {
var bestMenu: String = "아아"
}
class 카페인중독자 {
var myCoffee: 커피 = 커피()
func say() {
print(myCoffee.bestMenu)
}
}
여기서 카페인중독자 클래스는 커피 클래스에 의존하고 있다.
why? 커피 클래스가 없다면 var 커피를 초기화 할 수 없으니까!
이런식으로 코드를 작성하면
한 클래스가 다른 클래스에 너무너무 의존적이다.
커피 클래스에 변화가 생기면 카페인중독자 클래스에도 해당 변화에 대해 다 바꿔줘야 하기 때문이다.
또한 카페인중독자가 커피가 아니라 녹차로 갈아탈 수도 있는데 이런 변화에도 대응하기 힘들다.
"위와 같은 방식은 클래스간 의존성이 너무 강하다."
의존성은 결국 객체 사이의 의존 관계를 말하는 것이다.
주입?(Injection)
주사할 때 그 인젝션 맞다.
주사를 맞으면 주사기가 내 피부를 뚫고 약을 주입한다.
즉 외부의 어떤 것을 내부로 밀어넣는 것이라고 생각하면 좋다.
여기서 키워드는 '생성자' 즉 Initializer이다.
우리는 이미 주입의 개념을 많이 사용해 왔었다.
class 커피 {
var name: String = "아아"
}
class 카페인중독자 {
var myCoffee: 커피
init(필수품: 커피) {
self.myCoffee = 필수품
}
func say() {
print(myCoffee.name)
}
}
let 오늘의커피: 커피 = 커피()
let 김터피: 카페인중독자 = 카페인중독자(필수품: 오늘의커피)
이렇게 myCoffee 프로퍼티에 커피 인스턴스를 바로 할당하는 것이 아니라, 생성자를 통해서 객체를 주입하는 것이다.
외부에서 객체를 생성해서 생성자를 통해 초기화 하는 것이 주입의 개념이다.
그럼 이게 의존성 주입이 아닌가요??
의존성 주입 (Dependency Injection)
뭔가 말만 들으면 의존성을 강하게 할 것 같은데 사실은 반대다.
클래스간 의존관계, 의존성을 약화시키는 개념이라고 이해하는 것이 맞겠다.
의존성, 주입이 뭔지는 알았으니 의존성 주입을 위해서 꼭 필요한 녀석이 있다.
바로 프로토콜.(Protocol)
Protocol 카페인음료 {
var name: String { get set }
}
class 커피: 카페인음료 {
var name: String = "아아"
}
class 녹차: 카페인음료 {
var name: String = "아녹"
}
class 카페인중독자 {
var myBev: 카페인음료
init(음료: 카페인음료) {
self.myBev = 음료
}
}
let 커피중독자 = 카페인중독자(음료: 커피)
let 녹차중독자 = 카페인중독자(음료: 녹차)
'카페인음료' 라는 프로토콜을 만들었다. 즉 추상화가 이루어진 것.
이제 카페인음료를 채택하는 여러 클래스를 만들 수 있다. 커피, 녹차 등..
그리고 카페인중독자 클래스에서는
여전히 생성자를 통한 주입을 하고 있지만
⭐️ 이제 타입이 '커피'가 아니라 '카페인음료'로 바뀌었다. 프로토콜도 타입이니까!
무슨뜻이냐? 이전에는 커피 타입만 들어갈 수 있었는데, 이제 카페인음료 프로토콜을 채택한 무엇이든 들어갈 수 있다는 말!
-> 코드가 더 유연해지고 확장성도 좋아졌고 의존성도 약해졌네!
커피만 마시던 녀석이 녹차, 핫식스, 얼그레이티 뭐든.. 카페인 음료면 다 마실 수 있게 되었다.
의존 관계 역전 원칙(DIP, Dependency Inversion Principle)
원래 카페인 중독자 클래스는는 커피 클래스에 의존했었다.
근데 이렇게 코드를 바꾸면 카페인 중독자는 커피 클래스(프로토콜 채택 전)에 의존하는 것이 아니다.
오히려 커피, 녹차가 '카페인음료'라는 프로토콜에 의존하게 되었다.
즉 우리는 '카페인음료'라는 추상화된 프로토콜을 만들었고
이 프로토콜을 채택하는 구체적인 객체들, 커피와 녹차 등이 프로토콜에 의존하는 것이다.
기존에는
class 카페인 중독자 -> class 커피(프로토콜 채택 전) 이런 의존관계였다면
의존성 주입을 적용하고 나서는
class 카페인 중독자 -> 카페인음료(프로토콜) <- 커피, 녹차
이렇게 화살표의 방향이 바뀐 것이다.
중간에 카페인음료라는 매개체가 생겼다고 이해하면 되겠다.
이걸 어려운 말로 의존관계의 역전, Inversion of Control이라고 한다.
객체 지향 설계 5대원칙 SOLID에서 D에 해당하는 부분이다.
의존 관계를 맺을 때, 구체적인 객체 보다는 추상적인 객체에 의존할 것!
구체화된 클래스(프로토콜 채택 전 class 커피)에 의존하지 말고,
추상화된 클래스나, 프로토콜(ex. protocol 카페인 음료)에 의존해야 한다는 말이다.
결국 의존할거면 최대한 추상적인 것에 의존하라는 뜻.
Swift에서는 Protocol, 안드에서는 인터페이스 같은 것!
(특정 요구사항을 충족시키기 위한 타입)
의존성 주입 그래서 왜 써야 하는가?
의존성 주입은 객체간의 의존성을 줄여주는 것이다.
코드의 확장성, 재활용성이 좋아진다. 유연한 프로그래밍!
유지 보수도 쉽고, 테스트 하기도 훨씬 용이하다.
(ex. 카페인음료 프로토콜을 채택하는 테스트 객체를 주입해서 카페인중독자 클래스를 테스트 할 수 있을테니!)
이전 포스팅 보다는 DI에 대한 이해도가 훨씬 높아진 것 같다!
하지만 아직까지 DI를 제대로 적용한, 필요하다고 느낀 프로젝트를 진행하지 못해서
앞으로는 좀 더 이런 의존관계를 의식하면서 코드를 짜봐야겠다 🤔
'iOS Developer > Swift' 카테고리의 다른 글
Swift - Unit Test/ 유닛테스트 (0) | 2023.07.01 |
---|---|
closure, @escaping, completionHandler 콜백함수 (0) | 2023.05.29 |
ARC, weak self, 캡쳐리스트 (0) | 2023.05.28 |
스위프트와 프로그래밍 패러다임 (0) | 2023.04.08 |
고차함수 ( Higher-order Function)_ map, filter, reduce (0) | 2023.04.04 |