회사에서 'dependency injection 라이브러리 도입해서 리팩토링하는게 좋을거 같다' 라고 하셔서 부리나캐 공부했다.
그런데 이곳저곳 친구들한테 물어보니까 의존성주입은 고사하고 보일러플레이트도 처음 본다다고 하더라..ㅎㅎ
그래서 새삼 내가 많은걸 배울수 있는 좋은 환경에 있구나..! 라고 우리팀과 팀원분들에게 감사함을 느꼈다!
1. 배경 : 객체지향 개발 5대 원리 (SOLID)
시작은 의존성 주입이였다..
왜 필요한가? 라는 단순한 질문으로 찾아보다가..'의존성 역전'때문이라는걸 알게 되었고..
이게 뭐지? 라고 찾아보다가..'객체지향개발 5대 원리'까지 와버렸다..
프로그래밍 설계의 근본 중에 근본인데 아직까지 모르고 있다니 조금 부끄러워서 이렇게 정리를 시작하게 되었다.
죄송한데 초면이네요..객체지향 개발 5대 원리, SOLID..?
SOLID라고 부르는 객체지향 5대 원칙은 아래와 같다.
1. SRP (Single Responsibility Principle) : 단일 책임 원칙
"함수, 클래스는 단 하나의 기능을 가져야한다"
새로운 요구사항에 영향을 적게 받아서 나중에 유지보수에 용이하도록 응집도가 높고 결합도가 낮게 설계를 해야한다.
2. OCP (Open Closed Priciple) : 개방 폐쇄 원칙
"기존 코드를 변경하지 않고, 기능을 수정하거나 추가할수 있도록 설계해야한다."
인터페이스를 통해 자주변경되는 내용은 수정하기 쉽게 설계하고, 그렇지 않은 내용은 수정되는 내용에 영향을 받지 않게 해야한다.
3. LSP(Listov Substitution Priciple): 리스코프 치환 원칙
"부모클래스와 자식 클래스 사이의 행위에는 일관성이 있어야한다"
상속관계에서 일반화 관계가 성립되도록 부모클래스의 인스턴스 대신 자식 클래스의 인스턴스를 사용해도 문제없도록 해야한다.
4. ISP(Interface Segregation Principle): 인터페이스 분리 원칙
"사용하지 않는 인터페이스에는 영향을 받지 말아야한다"
하나의 일반적인 인터페이스 보다는 여러개의 구체적인 인터페이스로 독립시켜서, 서로에게 영향을 받지 않도록 설계해야한다.
5. DIP(Dependency Inversion Principle): 의존 역전 원칙
"의존관계를 맺을 때, 변화하기 쉬운것(클래스) 보다 변화하기 어려운것 (추상클래스,인터페이스)에 의존해야한다"
추상적 개념이 구체적인 객체에 의존하면 안되고, 구체적인 객체가 추상화된 개념에 의존하도록 설계해야한다.
그래서 객체는 객체보다 추상화된 인터페이스에 의존하도록 구체적인 객체들을 추상화된 개념으로 분리한다.
이해를 돕기 위해 아래의 예시를 생각해보자.
만약, 자동차가 스노우 타이어에 의존하면?
스노우타이어가 바뀔때마다 자동차가 영향을 받는다.
그래서 자동차 자신보다 더 자주 변하는 구체적인 스노우 타이어에 의존하는것은 좋지 않다.
반대로, 자동차가 추상화된 타이어 인터페이스에만 의존한다면?
어떤 경우에는 스노우 타이어가 있을수도 있고, 일반 타이어가 있을수도 있고, 광폭 타이어가 있을수 있어서 변하기 쉽다.
반면 자동차에 타이어가 있다는 사실(인터페이스)은 변하기 어렵다.
그래서 추상화된 타이어에 의존 할 경우 스노우 자동차가 바뀌어도 자동차는 영향을 받지 않는다.
결국 자동자 자신보다 자주 변화하기 어려운 추상 클래스 또는 인터페이스에 의존할 경우 의존역전 원칙을 만족시킨다.
또한 코드상으로는 이곳의 예제를 참고해서 이해했다.
우선 의존역전 원칙을 만족하지 않은 코드를 보면,
Character 클래스에서 무기이름을 OneHandSword객체로 받아서 Character 객체를 생성한다.
이럴 경우 Character 객체가 OneHandSword 객체를 의존하고 있기 때문에 항상 해당 무기만 쓸 수 있다.
만약 다른 무기를 사용하려면 각각 별도의 객체를 만들어서 Character 클래스에서 접근하는 부분을 각각 전부 수정해야한다.
이런 방식은 올바르지 않은 유지보수 방법이며, 의존역전 원칙을 만족하지 않았기 때문에 이런 번거로운 작업이 생긴것이다.
의존역전 원칙을 만족하도록 어떻게 리팩토링했는지 코드 예시를 보면,
Attackable 인터페이스를 생성했다.
그리고 OneHandSword 객체를 비롯한 모든 공격 가능한 무기객체들은 Attackable 인터페이스를 상속받도록 했다.
OneHandSword 객체는 자주 변하는 모듈이지만, Attackable 인터페이스는 변화하기 어려운 추상적인 개념이다.
Character 객체가 조금더 추상적인 개념인 Attackable을 의존하도록 변경했다.
그래서 Character 클래스에서 별도의 코드수정 없이 모든 무기들을 쓸 수 있다.
SOLID원칙을 설명하다가 이렇게 길어졌는데, 결국 5대 원칙 중 의존 역전 원칙(DIP)을 만족하는걸 권장한다.
그럼 이 원칙을 어떻게 만족시킬것인가?에 대한 해답이 이 글의 본론, 의존성 주입(Dependency Injenction)이다
2. 의존성 주입 (DI: Dependency Injenction)
2.1 의존성 주입이란?
의존성 역전을 구현시켜주는 기법인데
사용하는 객체가 아닌 외부의 독립적인 객체(의존객체)가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결해준다.
예를들어 A 클래스가 B 클래스를 의존할때 B 객체를 A가 직접 생성하지 않고, 외부에서 생성해서 넘겨준다.
왼쪽처럼 A에서 B를 생성하는게 일반적인 의존형태이고,
오른쪽처럼 외부에서 의존객체를 생성하고 주입하는형태가 의존성주입니다.
2.2 의존성 주입이 필요한 이유?
근본적으로 의존성 역전을 만족시켜준다는 의미인데,
결국 코드상의 결합도를 낮춰서 유지보수가 용이하도록 유연한 코드를 만드는 것이다.
2.3 의존성 주입 구현방법
대부분 스프링에서는 DI를 사용하는것이 일반적이라 스프링 프레임워크의 Annotation에서도 제공해주고 있다.
하지만 파이썬에서는 몇몇 의존성 주입 라이브러리로 제공해주고 있다.
dependency-injector : https://python-dependency-injector.ets-labs.org/
simple-injection: https://github.com/BradLewis/simple-injection
deadsimple : https://github.com/mastern2k3/deadsimple
그 중에 dependency-injector 가 가장 유명한듯 싶어서 사용하게 되었다.
특히 이 라이브러리는 테스트코드만 보고도 어떻게 사용해야하는지 명확하게 알 수 있다는게 장점이다.
참고자료
객체지향 5대원리 : https://dev-momo.tistory.com/entry/SOLID-%EC%9B%90%EC%B9%99
의존 역전 원칙 예시 이론 : https://devlog-wjdrbs96.tistory.com/380
의존 역전 원칙 예시 코드 : https://blog.itcode.dev/posts/2021/08/17/dependency-inversion-principle
의존성 주입 설명 : https://faith-developer.tistory.com/147
파이썬 의존성주입 구현 예제 : https://www.hides.kr/1053
'🌱 Computer Science > Programming' 카테고리의 다른 글
🧐 파이썬 코드를 잘 짜는 법 : 병렬처리 라이브러리 비교분석 (3) | 2022.09.30 |
---|---|
🧐 파이썬 코드를 잘 짜는 법 : 메모리 구조와 메모리 할당방식 이해 (0) | 2022.08.01 |
[Git] 실무에서 은근히 유용한 git stash와 git squash (1) | 2022.07.10 |
[Git] merge말고 rebase를 사용해야하는 이유, Rebase vs Merge (1) | 2022.05.29 |
[Test] mypy로 python 타입 검사하기 (0) | 2022.03.24 |
[Test] python 테스트코드의 필요성(feat.pytest) (0) | 2022.03.23 |