Spring 핵심 원리 기본편 (2) - SOLID / 객체지향 설계와 Spring
좋은 객체 지향 설계의 5가지 원칙(SOLID)
- 클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리
- 5가지 목록
- SRP(Single responsibility Principle)
: 단일 책임 원칙
- OCP(Open/Closed Principle)
: 개방-폐쇄 원칙
- LSP(Liskov Substitution Principle)
: 리스코프 치환 원칙
- ISP(Interface Segregation Principle)
: 인터페이스 분리 원칙
- DIP(Dependency Inversion Principle)
: 의존 관계 역전 원칙
- OCP / DIP가 가장 중요!
1. SRP(Single responsibility Principle)
- 단일 책임 원칙
- 한 클래스는 하나의 책임만 가져야 한다
('하나의 책임'이라는 것은 모호한 면이 있음
--> 문맥과 상황에 따라 다름)
- 즉, 핵심은 변경이 있을 때 파급 효과가 적어야 한다는 것!
(UI변경 / 객체의 생성과 사용 분리 등)
2. OCP(Open/Closed Principle)
- 개방-폐쇄 원칙
- S/W 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다
- 핵심은 다형성을 활용하는 것
- 새로운 구현체를 사용한 확장은 열어두면서, 기존 코드의 변경은 닫혀있게 해야 한다
- 예시
(이러한 문제를 해결하기 위해 Spring이 필요한 것 !)
3. LSP(Liskov Substitution Principle)
- 리스코프 치환 원칙
- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 함
- 자동차 인터페이스에서
액셀
은 앞으로 가라는 기능
이라는 정확성을 만족시켜야 우리가 믿고 쓸 수 있음
- 즉, 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다!
- 다형성을 지원하기 위한 원칙
- 인터페이스를 구현한 구현체를 믿고 사용하려면 필요한 원칙
4. ISP(Interface Segregation Principle)
- 인터페이스 분리 원칙
- 특정 클라이언트를 위한 인터페이스 여러개가 범용 인터페이스 하나보다 낫다
- 자동차 인터페이스 -> 운전 / 정비 인터페이스로 분리!
- 인터페이스가 명확해지고, 대체 가능성이 높아진다.
5. DIP(Dependency Inversion Principle)
- 의존관계 역전 원칙
- 코드가 구현클래스에 의존하지 말고, 인터페이스에 의존해야함
- 즉, 코드는 역할(Role)에 의존해야 한다!
- 프로그래머는 "추상화에 의존해야지, 구체화에 의존하면 안된다"
- 앞에서 다룬 예시는 OCP를 위반했지만 DIP도 위반한 예시이다
: MemberRepository라는 인터페이스만 의존하는 것이 아니라, 그 구현체들도 new해서 사용하게 된다
즉, 구현체에 의존을 하는 것이므로 DIP에 위배!
--> OCP / DIP를 위배한 것은 앞으로 Spring의 특성을 통해 맞추어 나갈 것임
정리
- 객체 지향의 핵심은 다형성
- 하지만, 다형성 만으로는 쉽게 부품을 갈아 끼우듯이 할 수 없음
- 즉, 다형성 만으로 OCP / RIP를 만족하는 코드를 짤 수 없음
--> 추가적인 무언가가 필요함!! (이것이 바로 Spring의 역할)
객체지향 설계와 Spring
Spring Framework
- Spring이라는 Framework는 다음 기술로 다형성 + OCP/DIP 가능하게 지원
- DI(Dependency Injection) : 의존관계, 의존성 주입
- DI 컨테이너 제공
- 클라이언트 코드의 변경 없이 기능 확장
객체지향 설계
- 모든 설계에 역할 / 구현을 분리
- 이상적으로는 모든 설계에 인터페이스(interface)를 부여하는 것
하지만, 인터페이스는 추상화
라는 비용이 발생한다!!
추상화 비용
: 추상화를 하게 되면 1차원 적으로 코드가 추가되는 것도 있지만, 그것보다는 프로그래머가 코드를 봤을 때 인터페이스 만으로는 어떤 구현체를 사용했는지 알 수 없기에 한번 더 들어가서 확인해야 하는 것이 바로 '추상화 비용' 이다!
- 정리
1) 기능을 확장할 가능성이 없으면 --> 구현체 클래스 직접 사용
2) 확장 할 가능성 있으면 --> 인터페이스 사용
3) 애매하면 --> 일단 구현체로 하고 향후 리팩토링 하는 것도 방법!