-
[Spring] 좋은 객체 지향 설계를 위한 5원칙, SOLIDBackend Dev/Spring Framework 2022. 3. 3. 16:07728x90
이전 포스팅에서 스프링과 객체 지향 프로그래밍의 컨셉과 핵심에 대해 알아보았는데 이번에는 스프링 프레임워크가 객체 지향 설계를 하는 데 있어서 어떤 이점과 관계가 있는지 알아보려 한다.
스프링에서 지원하는 제어의 역전(IoC)이나 의존관계 주입(DI)과 같은 기술은 다형성과 함께 역할과 구현을 편리하게 다룰 수 있도록 지원한다. 역할과 구현의 분리에 대한 이해가 부족하다면 아래 글을 통해 내용을 숙지하고 보는 게 좋을 것 같다.
스프링이 다형성을 극대화하여 이용할 수 있게 도움을 주는 프레임워크라는 것을 설명하기 위해서 먼저, 좋은 객체 지향 설계를 하는 데 적용되는 5원칙인 SOLID에 대한 이해가 필요하다.
SRP(Single Responsibility Priciple) : 단일 책임 원칙
- 한 클래스는 하나의 책임만 가져야 한다.
- 책임의 크기는 문맥과 상황에 따라 다르다.
- 중요한 기준은 변경이며, 변경이 일어났을 때 파급 효과가 적으면 SRP를 잘 따른 것이라 할 수 있다.
Ex. UI 변경, 객체의 생성과 사용을 분리
OCP(Open/Closed Principle) : 개방-폐쇄 원칙
- 소프트웨어 요소에서 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
- 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현하여 다형성을 활용하는 것이다.
- 역할과 구현의 분리memberService를 확장하여 memberRepository에서 가벼운 메모리 저장소와 DB에 접속해 데이터들을 저장해두는 JDBC 리포지토리로 나뉘어 있는 것을 볼 수 있다. 그림만 확인해보았을 때는 인터페이스의 역할만 하는 클라이언트에는 영향을 가하지 않는 것으로 보인다.
그러나, 아래의 코드를 확인해보면 결국 memberService라는 클라이언트에서도 새롭게 만든 구현체를 사용하기 위해 코드가 변경되어야 함을 알 수 있다.
public class MemberService { // private MemberRepository memberRepository = new MemoryMemberRepository(); private MemberRepository memberRepository = new JdbcMemberRepository(); }
클라이언트가 구현 클래스를 직접 선택을 해야하므로 결국 새로운 기능에 대한 구현 객체를 변경하기 위해서 클라이언트 코드를 변경해야 한다. 이는 다형성을 사용했으나 OCP 원칙을 지킬 수 없는 것이다.
이 문제를 해결하려면 객체를 생성하고, 연관관계를 맺어주는 별도의 설정자가 필요한데, 이를 스프링에서 DI, IoC 컨테이너와 같은 요소로 해결이 가능하다.
LSP(LisKov Substitution Principle) : 리스코프 치환 원칙
- 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야한다.
- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것이고, 이는 다형성을 지원하기 위한 원칙이다. 인터페이스를 구현한 구현체를 신뢰하려면 이 원칙이 필요하다.
Ex. 자동차 인터페이스의 엑셀은 앞으로 가라는 기능인데 뒤로 가게 구현을 한다면 LSP 위반이고, 느리더라도 앞으로 가야한다.
ISP(Interface Segregation Principle) : 인터페이스 분리 원칙
- 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다는 원칙.
Ex. 자동차 인터페이스를 운전과 정비 인터페이스로 분리시킴으로써 클라이언트도 사용자에서 운전자, 정비사 클라이언트로 분리시켜 정비 인터페이스 자체가 변하더라도 운전자 클라이언트에는 영향을 주지 않는다.
- 인터페이스가 명확해지고, 대체 가능성이 높아진다.
DIP(Dependency Inversion Principle) : 의존관계 역전 원칙
- "추상화에 의존해야 하며, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나이다. 즉, 구현 클래스에 의존을 하는 게 아니라 인터페이스에 의존하라는 의미이다.
- 역할에 의존하게 해야한다는 것과 같은 맥락이고, 객체 지향 설계를 할 때도 클라이언트가 인터페이스에 의존해야 유연하게 구현체를 변경할 수 있다.
- 하지만 OCP에서는 인터페이스에 의존하나 구현 클래스도 동시에 의존하기에 DIP 원칙 위반이 된다.
Ex. MemberService 클라이언트가 구현 클래스를 직접 선택public class MemberService { private MemberRepository m = new MemoryMemberRepository(); }
결론적으로, 객체 지향의 핵심은 다형성이지만 다형성만으로는 스프링없이 구현 객체를 변경할 때 클라이언트 코드도 함께 변경이 되고 인터페이스, 구현에 동시에 의존을 하여 OCP, DIP 원칙에 어긋나기에 좋은 객체 지향 설계를 하기 위해서 이를 해결해주는 스프링 기술을 추가적으로 사용해야 한다.
스프링은 아래의 기술로 객체 지향 설계를 함에 있어 다형성을 적용했을 때 OCP, DIP 에서 직면했던 문제점을 해결해준다.
- DI(Dependency Injection), 의존관계 및 의존성 주입
- DI 컨테이너 제공
- 클라이언트 코드의 변경없이 기능 확장이 가능하다.
이러한 기술들을 통해서 모든 설계에 역할과 구현을 명확히 분리시키고, 구현체는 언제든지 유연하게 변경할 수 있도록 만드는 게 좋은 객체 지향 설계이다. 이상적으로는 모든 설계에 인터페이스를 부여해야 한다는 것이다.본 내용은 인프런-스프링 핵심원리 기본편 김영한님의 강의를 참고하였습니다.
728x90'Backend Dev > Spring Framework' 카테고리의 다른 글
[Spring] 컴포넌트 스캔과 의존관계 자동 주입 (0) 2022.03.15 [Spring] 싱글톤 컨테이너 (0) 2022.03.08 [Spring] IoC, DI 그리고 컨테이너 (0) 2022.03.08 [Spring] 객체 지향 설계의 적용 (0) 2022.03.08 [Spring] 스프링과 객체 지향 설계 (0) 2022.03.03