📌 김영한 선생님의 스프링 핵심 원리 - 기본편 강의를 들으면서 공부한 내용을 정리한 게시물입니다.
예제를 마저 따라해보자.
지난 시간에 구현했던 주문과 할인 정책을 다시 떠올려보자!
할인 정책은 모든 VIP는 1,000원을 할인해주는 고정 금액 할인을 적용해달라. (나중에 변경될 수 있다.)
지난 시간에 고정 금액 할인을 적용하기 위해서 FixDiscountPolicy
을 구현했다.
하지만 갑자기 기획자가 고정 금액 할인이 아닌 정률 금액 할인으로 변경해달라고 한다면..?
👩🏻💻 개발자: 안 들린다~~~
라~고~ 하지 말고 우리의 코드를 다시 한 번 살펴보자!
사실 우리는 지금까지 변화에 유연하게 대응할 수 있게끔 객체 지향 설계 원칙을 준수하여 개발을 해왔다.
우리 코드가 객체 지향 설계 원칙을 준수했는지 확인도 해보고 기획자가 원하는 정률 금액 할인으로 코드도 수정해보자!
RateDiscountPolicy
에 대한 코드를 작성하고 테스트까지 해보자!
🔗 코드 확인하기
기존 정책인 FixDiscountPolicy
대신, 위에서 추가한 RateDiscountPolicy
를 적용해보자!
RateDiscountPolicy
를 적용하려면 OrderServiceImpl
코드를 FixDiscountPolicy
에서 RateDiscountPolicy
로 직접 고쳐주자!
🔗 코드 확인하기
오잉,,, 이상한 점을 못 느끼셨나요,,?
우리는 SOLID
원칙을 지키면서 개발을 하려고 했는데 RateDiscountPolicy
를 적용하려면 클라이언트인 OrderServiceImpl
를 직접 수정해야 하기 때문에 SOLID
를 위반해버렸다! 😥
문제점을 제대로 정리해보자면 다음과 같다.
📌 문제점 발견
- 역할과 구현을 충실하게 분리했다. → ⭕
- 다형성을 활용하고, 인터페이스와 구현 객체를 분리헀다. → ⭕
- 객체 지향 설계 원칙(
OCP
,DIP
)을 충실하게 구현했다. → ❌
DIP
: 인터페이스(FixDiscountPolicy
,RateDiscountPolicy
) 뿐만 아니라 구현 클래스(DiscountPolicy
)에도 의존하고 있다.OCP
: 현재 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 주기 때문에 위반!
왜 지금의 코드는 SOLID
를 위반할 수 밖에 없을까?
👩🏻💻 개발자: 이제
OrderServiceImpl
가DiscountPolicy
만 의존하게 되겠지? 😀
💻
OrderServiceImpl
: 헤헤 사실 아니지롱! 나는FixDiscountPolicy
에도 의존해~😽
OrderServiceImpl
가 FixDiscountPolicy
에 의존하고 있기 때문에 정책을RateDiscountPolicy
로 바꾸려면 어쩔 수 없이 OrderServiceImpl
의 코드를 바꿀 수 밖에 없다😥
👩🏻💻 개발자: 이러면
DIP
와OCP
를 위반해버리잖아..😞
👩🏻💻 개발자: 안되겠다
OrderServiceImpl
가FixDiscountPolicy
나RateDiscountPolicy
에 의존하지 않게 코드를 바꿔버리자!
인터페이스에만 의존하도록 바꾸자!
문제가 되는 코드는 바로 여기였다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
👩🏻💻 개발자: 인터페이스에만 의존하게 하려면 이렇게 하면 되겠지?
public class OrderServiceImpl implements OrderService {
private DiscountPolicy discountPolicy;
}
✋🏻 멈춰!!!
이렇게 하면 무시무시한NPE
(널포인트 에러)가 발생합니다😥 (구현체가 없기 때문 ㅠㅠ)
이렇게 하지 말고 OrderServiceImpl
가 아닌 다른 객체에서 할인 정책의 구현 객체를 대신 생성해주고 주입해주자!
지금까지 상황을 정리해보자.
우리는 OCP
, DIP
원칙을 지키기 위해서 노력해왔지만 여전히 해결하지 못하고 있다.
OrderServiceImpl
가 할인 정책이 바뀔 때마다 직접!FixDiscountPolicy
나 RateDiscountPolicy
의 구현체들의 생성자들까지 호출해왔기 때문이다.
💻
OrderServiceImpl
: 살려줘..
이렇게 OrderServiceImpl
에게 과도하게 일을 시키지 말고 다른 친구를 데려오자😢
AppConfig
🙇🏻♀️
AppConfig
: 안녕하세요 새로 온 일꾼입니다!
위에서 말했던 "OrderServiceImpl
가 아닌 다른 객체에서 할인 정책의 구현 객체를 대신 생성해주고 주입하는 역할"을 해줄 친구가 바로 AppConfig
이다.
📌
AppConfig
- 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
- 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입(연결)한다.
🔗 코드 확인하기
AppConfig
친구를 통해서 이제 모든 구현체는 다른 구현체에 의존하지 않게 되었다!
AppConfig
가 담당한다.DIP
완성: MemberServiceImpl
는 MemberRepository
인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.appConfig
객체는 memoryMemberRepository
객체를 생성하고 그 참조값을 memberServiceImpl
을 생성하면서 생성자로 전달한다.memberServiceImpl
입장에서 보면 마치 외부에서 주입해주는 것 같다고 해서 DI
(의존관계 주입 또는 의존성 주입)라고 한다.💡 정리
AppConfig
를 이용하여 관심사를 확실하게 분리했다.AppConfig
는 구체 클래스를 선택한다. 애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.
현재 AppConfig
를 보면 중복도 있고, 역할에 따른 구현이 잘 안보인다😥
🔗 코드 확인하기
new MemoryMemberRepository()
부분의 중복이 제거되면서, MemoryMemberyRepository
를 다른 구현체로 변경할 때 손쉽게 바꿀 수 있다!AppConfig
를 보면 역할과 구현 클래스가 한눈에 보여서 애플리케이션의 전체 구성을 빠르게 파악할 수 있다!드!디!어! 정말로 SOLID
원칙에 위배되지 않는, 객체 지향 설계 원칙을 준수하며 개발을 하였다.
만세!