-
Dependency Invsersion Principle 이해하기 with SwiftCS/OOP 2022. 2. 19. 17:43
모든 내용은 레퍼런스를 보며 혼자 공부한 내용이므로 잘못된 내용이 포함될 수 있습니다.
내용에 대한 피드백은 감사히 받겠습니다.서론
이번 글에서는 Dependency Invsersion Principle 이하 DIP에 대해서 이해해 보려고 한다.
5가지나 되는 SOLID원칙 중 단번에 DIP에 대해서 설명하는 이유는 객체지향의 핵심 원칙이라고 생각하기 때문이다.
단순히 DIP을 이해하는 것만으로도 사고의 확장이 가능할 것이라고 기대한다.
DIP이란?
In object-oriented programming, the Dependency Inversion Principle refers to a specific form of decoupling software modules. When following this principle, the conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. - Wikipedia-
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.이렇게 정의만으로는 이해하기 힘들 것이다. 하지만 이 글을 보고 나서 다시 돌아왔을 때에는 쉽게 이해가 갈 것이다.
의존성이란?
왼쪽 그림의 경우, Specific Receiver의 내용이 바뀌면 Sender의 내용도 바뀌어야 한다.
오른쪽 코드에서, SpeficReceiver의
frequency
프로퍼티의 이름이f
로 바뀐다면,
Sender에서receiver.frequency = 10
이라는 코드도receiver.f = 10
으로 바뀌어야 한다.이 경우 Sender는 SpecificReciever에 의존성이 있다고 하고,
Sender -> SpecificReceiver
로 표현한다.예시상황으로 이해
Hue가 동네 편의점인 CU에 가서 물건을 구매하는 상황이다.
CU에는 Jack이라는 알바가 일을 하고 있고, Hue가 물건을 살 때 Jack이 여러 가지 행동을 거쳐 계산해준다.Hue는 CU를 프로퍼티로 갖고 있으므로 CU에 의존성이 있고, CU는 Jack을 프로퍼티로 갖고 있으므로 Jack에 의존성이 있다.
우리가 평소에 작성하는 코드에서 지금까지는 아무런 문제점도 보이지 않는다.하지만 다음과 같이 상황이 변한다면 어떨까?
Hue가 이사를 갔다. CU는 어디에도 보이지 않는다..
새로 발견한 편의점은 미니스탑이고, 그 편의점에서는 Dustin이 일을 하고 있었다.Hue가 의존하는 편의점은 Mini로 바뀌었고, 또한 편의점에서 일하는 직원도 Dustin으로 바뀌었다.
그에 따라서 빨간색으로 표시한 것처럼 많은 코드 수정이 필요했다.
해당 예시는 간단하기 때문에 코드수정을 조금만 할 수 있었다.
하지만 코드의 양이 많아질수록, 더 많은 수정이 필요할 것이고 리펙토링은 그만큼 어려워지게 된다.
DIP의 적용
추상화
CU와 Mini는
편의점
이라는 테마로 묶을 수 있고, Jack과 Dustin은알바
라는 테마로 묶일 수 있다.이제 Hue는
CU
에 가서Jack
에게 물건을 구매하는 것이 아니라,
Hue는편의점
에 가서알바
에게 물건을 구매하는 것이다.역전된 의존성
추상화를 통해서 바뀐 의존성 관계를 슬라이드 하단의 그림처럼 표현할 수 있다.
High-level -> Low-level
로의 의존성 관계에서,Low-level -> High-level
의 의존성 관계로 역전된 것이 보일 것이다.Hue(High-level)는
편의점
(Abstraction) 에 의존성을 갖고 있고, CU와 Mini(Low-level Detail)는편의점
(Abstraction)을 채택해 구체적인 내용을 구현하고 있다.또한 CU나 Mini(High-level)는
알바
(Abstraction)에 의존성을 갖고 있고, Jack과 Dustin(Low-level Detail)은알바
(Abstraction)를 채택해 구체적인 내용을 구현하고 있다.이것이 바로 DIP의 정의에서 이야기하는 내용이다.
이제 다음의 정의가 이해가 될 것이다.
High level modules should not depend upon low level modules. Both should depend upon abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.High-level과 Low-level의 분리
왼쪽 그림에서, 이제는 CU나 Mini(High-level)가 Jack이나 Dustion(Low-level)에 의존하지 않는다. 알 필요가 없다.
CU나 Mini는
알바
에 의존한다. 그저알바
를 호출해 일을 시키면 되는것이다. 그알바
가 누가 되든 달라질 것은 없다.오른쪽 그림에서, 이제는 Hue(High-level)가 CU나 Mini(Low-level)에 의존하지 않는다. 그저 동네에 아무
편의점
에 가서 물건을 구매하면 된다.즉, High-level은 Low-level을 몰라도 된다.
각 level이
Layer
로 분리되었고,Layer
간의 의존관계는 한 방향으로만 생겨난다.DIP의 효과 Decoupling
DIP을 통해서
Decoupling
이 가능하다.Decoupling이란 강력한 의존관계 -> 느슨한 의존관계가 되었다는 뜻이다.
Expandable, Reusable
이를 통해서 확장 가능하고, 재사용 가능한 코드 구현이 가능하다.
확장 가능하다는 것은 이런 것이다.
편의점
의 구체적인 구현체에 CU, Mini, 7 eleven, GS25, ... 무엇이든 들어갈 수 있다.알바
의 구체적인 구현체에 Jack, Dustin뿐만 아니라 IU든 UI든 ... 무엇이든 들어갈 수 있다.재사용 가능하다는 것은 이런 것이다.
Hue
는 이 동네(현재 파일)에서아무것도 생각하지 않고 다른 동네(다른 파일)로 이사 갈 수 있다.그곳에서 또 다른 편의점에 방문하면 되는 것이다.
Testable
편의점
,알바
의 구체적인 구현체에 무엇이든 들어갈 수 있다는 말은 임의로 만든 객체도 가능하다는 것이다.임의로 만든 객체를 넣음으로써 결과를 예상할 수 있고, 이것을 실제 값과 비교해볼 수 있다.
테스트가 가능한 것이다.
DIP을 통해서 Architecture의 이해
Clean Architecture의 원칙은 "High-level은 Low-level에 영향을 받지 않고, 의존성의 방향은 Low-level에서 High-level로 향한다"는 것이다.
위에서 반복했던 DIP의 효과인 것이다.
또한 Ribs는 SOLID원칙을 기반으로 만들어졌고, 강력하게 DIP을 지키도록 고안되었다.
이렇게 현대의 주요한 Architecture들을 DIP을 통해서 쉽게 이해할 수 있게 된다.
또한 Module, Framework로의 분리에서도 DIP을 적용한다.
위의 Ribs또한 매우 큰 프로젝트를 위해서 만들어진 것이기도 하다.
아마도, 요즘 핫한 키워드인 Micro-service 또한 비슷한 원칙이 적용되어 설계될 것이다.
UIKit에서도...
UIKit이 어떻게 구현되었는지 우리는 모른다.
이렇게 우리(High-level)가 UIKit(Low-level)이 어떻게 구현되어 있는지 모른 채 코드를 작성할 수 있다는 것이, DIP이 완벽히 적용되어 있다는 것을 반증하는 것이다.
TableViewDatasource를 예를 들면 이렇다.
Apple에서 TableView 랜더링을 이전 버전으로 바꿔도, 다른 C++라이브러리를 사용한다고 해도,
나는 여전히 같은 방식으로 TableView를 그릴 수 있다.
주의
런타임에서는
DIP은 컴파일 타임에서만 일어난다. 런타임에서는 의존관계가 바뀌지 않는다.
프로그래머가 코드를 더 유연하게 작성하기 위해 DIP이 적용되는 것일 뿐이다.
남용 금지
항상 Over-Engineering을 조심해야 한다.
DIP을 구현하기 위해서는 Protocol이 구현되어야 하고, 그에 따라서 코드베이스는 커질 수밖에 없다.
모든 클래스에 Protocol을 구현하는 것은 매우 매우 매우 Over-Engineering이다.
필요할 때에만 DIP을 적용할 수 있어야 한다.
YAGNI
기억해두자. you ain't gonna need it.
"DIP이 필요 없을 것이다" 가 기본 가정이다.
필요할 때 구현해주면 된다.
third-party library를 추가할 때에는 DIP을 적용하는 것이 좋다고 한다.
레퍼런스
https://clean-swift.com/dependency-inversion-a-little-swifty-architecture/
https://mobidevtalk.com/swift-dependency-inversion-through-protocol/#Dependency-Inversion
https://developers.soundcloud.com/blog/dependency-inversion-as-a-driver-to-scale-mobile-development
http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html