이 시리즈는 인프런 강의(김영한 님의 ‘스프링 핵심 원리 - 기본편’)로 공부하며 혼자 기록하고, 사람들과도 공유할 수 있도록 작성하는 글이다. 최대한 추가적인 정보는 공식 홈페이지, 문서를 보며 얻을 예정이다.
(개인적인 생각과 이해가 들어가 있기 때문에 저의 ‘무식함’이 있을 수 있습니다😜 혹시라도 이 글을 보게 되시는 분이 계시다면 잘못된 부분 댓글로 많이 알려주시면 너무 감사하겠습니다!!)
GitHub Repository : https://github.com/jcw1031/spring-core-study
지금까지 우리가 만든 AppConfig
는 중복되는 부분도 있고, 역할에 따른 구현이 눈에 잘 보이지 않는다. 다시 말해, 역할에 따른 구현을 어떤 구현체가 하는지 한 그림에 확실하게 드러나야 하지만 그렇지 않다는 뜻이다. 그래서 그 부분을 보완하도록 수정해 보자.
MemberService
역할의 구현체는 MemberServiceImpl
로 구현한다는 것이 어느 정도 보이지만, MemberRepository
역할의 구현체는 확실하게 보이지 않는다.(new MemoryMemberRepository()
부분) 그래서 그 부분을 메서드로 만들어 주도록 하겠다.
new MemoryMemberRepository
를 드래그하고, 아래 단축키를 사용하면 빠르게 메서드를 생성할 수 있다. 단축키를 사용하면 아래와 같은 화면처럼 변하는데, 톱니바퀴 모양을 클릭한 후에 More option 버튼을 클릭한다.
MacOS : ⌘ + ⌥ + M
Windows : Ctrl + Alt + M
그럼 Extract Method 창에서 메서드를 세부적으로 설정할 수 있다.
Declare static은 메서드가 static
으로 선언되는 것이니 체크를 풀어준다. Name은 memberRepository
로 하고 return
타입을 MemberRepository
인터페이스로 해주자! (이 메서드는 외부에서 사용할 것이 아니기 때문에 public
으로 선언하지 않아도 된다.) 설정을 마치면 Refactor를 누른다.
그러면 다른 부분에 중복된 부분도 수정할 것인지 물어보는데, Replace 버튼을 눌러 중복된 다른 부분도 변경해 준다.
그럼 아래와 같이 메서드가 생성되고, 두 개의 부분이 메서드를 호출하는 형식으로 변경된 것을 확인할 수 있다.
남은 DiscoutPolicy
역할의 구현체(new FixDiscountPolicy()
부분)도 마찬가지로 메서드로 주입해 주도록 변경한다.
리팩터링을 거친 AppConfig
는, 메서드를 보면 한눈에 역할에 대한 구현이 잘 보인다. ‘MemberService
는 MemberServiceImpl
을 사용할 것이다’, ‘MemberRepository
는 MemoryMemberRepository
를 사용할 것이다.’라는 것이 확실하게 보인다.
이로써
AppConfig
에 역할과 구현 클래스가 한눈에 들어온다. 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있는 것이다❗️
이제 정액 할인 정책에서 정률 할인 정책으로 변경해 보자. 예전의 코드였다면 클라이언트에서 변경이 일어났겠지만, 우리는 객체를 생성하고 구성하는 AppConfig
를 통해 클라이언트에 영향을 주지 않고 변경할 수 있게 되었다. AppConfig
클래스의 코드만 변경하면 되는 것이다!
AppConfig
의 DiscoutPolicy
역할의 구현체를 FixDiscountPolicy
에서 RateDiscountPolicy
로 변경하면 끝이다. 간단하다.
실행해 보자. 너무 잘 동작한다😃
이로써 OCP와 DIP를 지킬 수 있게 되었다!
AppConfig
리팩터링전체 도메인을 개발하고, 다형성을 사용해 새로운 할인 정책을 개발하는 것까지 아무 문제 없이 잘 진행했다. 하지만 새로운 할인 정책을 적용할 때 ‘클라이언트 객체인 주문 서비스 구현체도 수정’을 해야 하는 문제점이 있었다. 주문 서비스 구현체가 인터페이스인 DiscountPolicy
도 의존하고, 구현체인 FixDiscountPolicy
도 의존하고 있었다. DIP를 위반한 것이다.
그래서 우리는 이 문제를 해결하기 위해 관심사를 분리하여 애플리케이션의 전체 동작 방식을 구성하기 위해 구현 객체를 생성하고 연결하는 책임을 가진 AppConfig
를 개발했다. 이제 클라이언트 객체들은 자신의 역할을 수행하는 데 집중할 수 있게 되었다.
그러나 AppConfig
에서 역할에 따른 구현을 확실하게 파악하기 힘들어, 역할과 구현을 명확하게 분리하여 잘 드러날 수 있도록 리팩터링을 거쳤다.
그래서 문제 발생의 시작인 새로운 할인 정책 적용을 객체 지향 설계 원칙을 지키며 변경할 수 있게 되었다.
지금까지 예제를 만들어 보면서 총 3가지, SRP • OCP • DIP를 적용했다.
하나의 클래스는 하나의 책임만 가져야 한다.
처음에는 클라이언트 객체가 직접 구현 객체를 생성하고 연결, 실행하는 역할까지 다양한 책임을 가지고 있었다. 그래서 SRP를 지키기 위해, 구현 객체를 생성하고 연결하는 책임을 가진 AppConfig
를 추가하였다. 이로써 클라이언트 객체는 실행하는 책임만 담당할 수 있게 되어 SRP를 준수할 수 있었다.
프로그래머는 ‘추상화’에 의존해야 하고, ‘구체화’에 의존하면 안 된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.
새로운 할인 정책을 개발하여 적용하고자 할 때 클라이언트 코드도 함께 변경해야 했다. 기존 클라이언트 코드(OrderServiceImpl
)가 DiscountPolicy
추상화 인터페이스 뿐만 아니라 FixDiscountPolicy
구체화 구현 클래스에도 의존했기 때문이다. 그래서 클라이언트 코드가 DiscountPolicy
추상화 인터페이스에만 의존하도록 코드를 변경했다. 구현체는 AppConfig
가 구현 객체를 대신 생성하여 클라이언트 코드에 의존관계를 주입해 주었다. 이로써 DIP를 준수할 수 있게 되었다.
소프트웨어 요소(개체)는 확장에는 열려있으나 변경에는 닫혀 있어야 한다.
다형성을 사용하고 클라이언트가 DIP를 지키며, 애플리케이션을 사용 영역과 구성 영역(AppConfig
)으로 나누었다. 그래서 할인 정책을 FixDiscountPolicy
에서 RateDiscountPolicy
로 변경할 때, AppConfig
가 의존관계를 변경하여 클라이언트에 코드를 주입하면 되기 때문에 클라이언트 코드는 변경을 하지 않아도 된다. 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있다. 이로써 OCP도 준수할 수 있게 되었다.
그동안 스프링을 사용하지 않고 순수 자바 코드만으로 개발을 했다. 다음 시간에는 지금까지 좋은 객체 지향 설계 5가지 원칙이 어떻게 반영되었는지 살펴보고, DI와 IoC 그리고 컨테이너에 대해 이론적인 공부를 해본다.