스프링 어린이(스린이)로서 프로젝트를 할 때 괜찮은 코드를 참고 위해 구글링하면서 여기 저기 돌아다니다보면
"AppConfig" 이 녀석을 많이 보게 된다. 이전 뭐지? 뭐 설정해주는건가? 하고 넘겼다.
게다가 스린이 입장으로 봤을 때
'한 번의 선언으로 해결할 수 있는데 쓸데없이 코드가 한 번 더 있네? 빼야하는 부분 아닌가? 뭐 가독성은 있는데 빼는 게 더 나을듯 후훗 역시 나는 효율적이야' 라고 생각했다. (바보같은 생각)
하지만 알고보니 이 녀석은 아주 좋은 객체지향 코드가 되기 위한 핵심 역할을 해주는 좋은 녀석(마치 찐빵의 팥)이었다!!
![](https://t1.daumcdn.net/keditor/emoticon/friends2/large/021.png)
비유적으로 접근하자면, 배역은 인터페이스이고 배우는 이를 구현하는 구현체라고 하자.
이때, AppConfig는 "공연 기획자, 캐스팅 담당자"로 배역에 맞는 배우를 연결하는 역할을 하는 것이다.
더 자세히 예시 코드를 보면서 더 알아보자.
(상황은 주문 서비스가 있고 할인 정책이 있는데 손님이 주문을 하면 할인 정책을 적용된 가격을 지불해야 한다)
구조는 다음과 같다.
[클라이언트] → [주문 서비스 구현체 OrderServiceImpl] → 1) 고정 할인 정책 구현체 2) 비율 할인 정책 구현체
✔️[스프링의 핵심 개념]
먼저, 스프링을 관통하는 핵심 개념은 "다형성을 위해 객체 설계 시 역할(인터페이스)을 먼저 부여하고, 그 역할을 수행하는 구현 객체 만들자"이다.(역할을 먼저 → 구현체를 나중에 덧붙이기!)
이러한 개념이 적용된 코드를 짜기 위해서 먼저 추상적으로 인터페이스를 생각하고 (ex discountPolicy 할인 정책이라는 인터페이스) 이를 구체적으로 적용된 것(고정 금액 할인 혹은 비율 할인과 같은 구현체)들을 생각해볼 수 있다.
이렇게 인터페이스와 구현체를 나누게 된다면 정책의 변경 사항에 잘 대응할 수 있다. (마치 애플워치 스트랩 바꾸듯 일부분만 바꿔끼울 수 있다)
[예시]
하지만 이렇게 생각한 구현체를 직접 코드에 적용하기 위해서 다음과 같이 코드를 작성할 수 있다.
public class OrderServiceImpl implements OrderService{
...
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
//DIP 위반! -> 구현체인 RateDiscountPolicy에도 의존하고 있음
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
...
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId,itemName, itemPrice, discountPrice );
}
}
OrderServiceImpl에서 할인 정책에 적용하기 위해서 할인 정책 구현체를 선언했다.
하!지!만! 여기서 2가지 문제점이 있다.
❓[문제점 1]
첫 번째 문제점은 바로바로바로 OrderServiceImpl가 지금 discountPolicy 와 이의 구현체인 RateDicountPolicy에도 동시에 의존하고 있다는 점이다. 이는 DIP 위반이다.
참고 : 좋은 객체지향의 5가지 원칙 중 DIP(의존관계 역전 원칙)은 “추상화에 의존해야지, 구체화에 의존하면 안된다”라는 것이며5가지 원칙 중 중요한 원칙에 속한다.
❓[문제점 2]
만약 할인 정책의 구현체를 바꾼다고 한다면(고정 금액 -> 비율로 변경)
할인 정책을 바꾸는 순간 OrderServiceImpl 의 코드를 변경했다 .
하지만 변경이 있는 순간 구현체의 코드가 바뀌면 안 된다. 이는 전기차로 바꾸었는데 따로 면허를 다시 따는 격이라고 할 수 있다.
이는 OCP 위반이다.
참고 : 좋은 객체지향의 5가지 원칙 중 OCP(개방 폐쇄 원칙)은 "소프트웨어 요소는 확장에는 열려 있으나, 변경에는 닫혀있어야 한다. "라는 것이다. 기능을 확장할 때 기존 코드를 변경하는 것이 아니라 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현한다.
💡[해결]
자자 그럼 추상화에 의존하도록 바꿔보자
추상화에만 의존하도록 바꿔보면
--- 변경 전 ---
private DiscountPolicy discountPolicy;
--- 변경 후 ---
private DiscountPolicy discountPolicy;
하지만 실행하면 지금 선언만 된 상태이고 아무 것도 할당되어 있지 않기 때문에 NullPointerException 오류가 난다.
(으아아 그러면 어쩌자는거임? 뭔가 구체적인 객체가 있어야하잖아?? 다시 DIP 원칙을 어길 수도 없고 이게 뭐람 → 하지만 ! 다른 해결 방법이 있음!)
=> 💡 누군가가 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해야 한다.
그 "누군가가"가 AppConfig 이다.
한편, 위의 문제는 비유적으로 설명하자면 배우가 직접 캐스팅하는 꼴이라고 할 수 있다. → 캐스팅하는 역할을 본인이 한다. 하지만 공연을 올릴 때 배역과 배우만 있는 것이 아니라 공연 기획자 (캐스팅 담당자)도 있어야 한다. 캐스팅과 연기를 따로 분리하자! (배우는 연기만 하도록 공연 기획자를 추가하고 배우와 공연 기획자의 책임을 확실히 분리하자.)
어떤 인터페이스에 할당이 될 지에 대한 결정을 하는 부분도 추가하자 ⇒ AppConfig의 등장!
![](https://t1.daumcdn.net/keditor/emoticon/niniz/large/043.gif)
AppConfig
: 애플리케이션의 전체 동작 방식을 구성(config) 혹은 설정하기 위해, 선택된 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스를 만들자!
즉, 전체적으로 사용 영역(OrderServiceImpl, DiscountPolicy 등) + 구성 영역(AppCofig)으로 나뉘게 된 것이다.
public class AppConfig {
...
public OrderService orderService()
{
return new OrderServiceImpl(discountPolicy());
}
private RateDiscountPolicy discountPolicy() { //따로 함수를 빼면 더 역할이 잘 보인다.
//return new FixDiscountPolicy(); 이렇게 쉽게 다른 구현체 변경 가능
return new RateDiscountPolicy();
}
}
- AppConfig 는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
- MemberServiceImpl
- OrderServiceImpl
- FixDiscountPolicy
- AppConfig는 생성된 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결) 해준다.
- OrderServiceImpl → RateDiscountPolicy, FixDiscountPolicy
✔️ DIP 위반 문제 해결
- OrderServiceImpl 어떤 구현 객체가 들어올지는 알 수 없다. 의존 관계를 외부에 맡긴 것이다.
- 그리고 클라이언트인 OrderServiceImpl 입장에서도면 의존관계를 마치 외부( AppConfig)에서 주입해주는 것 같다고 해서 “DI(Dependency Injection), 의존 관계 주입”라고 한다.
- 즉, 추상에만 의존할 수 있는 환경을 제공해줌 -> DIP 위반 문제 해결
✔️ OCP 위반 문제 해결
- 애플리케이션을 사용 영역과 구성 영역으로 나눔
- AppConfig가 의존 관계를 Fix→ Rate로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 된다.
- 즉 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있게 된 것이다.( = 사용 영역의 수정은 필요없게 되었다)
- 만약 구현체를 바꿀 일이 있다면 사용 영역의 코드의 변경 없이 AppConfig 영역만 바꾸면 된다.
- 예를 들어 할인 정책이 Fix → Rate로 바뀐다고 한다면 구성영역인 AppConfig 부분만 변경하면 된다. -> OCP 위반 문제 해결
참고로 , AppConfig 를 보면 역할과 구현을 나누었기 때문에 역할과 구현 클래스가 한 눈에 들어와 전체 구성을 빠르게 파악할 수 있다. 역할 = OrderService 부분, 구현 = discountPolicy 부분
-이 글은 김영한님의 인프런 강의를 듣고 정리한 글입니다.
'WEB > Spring' 카테고리의 다른 글
싱글톤 패턴과 스프링 컨테이너 (0) | 2022.08.22 |
---|---|
스프링 컨테이너와 스프링 빈 (0) | 2022.08.18 |
웹 애플리케이션의 이해 - WAS와 서블릿 (0) | 2022.08.13 |
객체 지향과 스프링(다형성과 SOLID) (0) | 2022.08.12 |
[🔐 스프링 시큐리티] 스프링 시큐리티 용어와 흐름 (2) | 2022.08.11 |