- 해당 게시물은 인프런 "스프링 핵심 원리 - 기본편" 강의를 참고하여 작성한 글입니다.
- 자세한 코드 및 내용은 강의를 참고해 주시길 바랍니다.
강의링크 -> 스프링 핵심 원리 - 기본편 (김영한)
서비스 오픈 직전에 할인 정책을 지금처럼 고정 금액 할인이 아니라 좀 더 합리적인 주문 금액당 할인하는 정률% 할인으로 변경하고 싶어요.
예를 들어서 기존 정책은 VIP가 10000원을 주문하든 20000원을 주문하든 항상 1000원을 할인했는데,
이번에 새로 나온 정책은 10%로 지정해두면 고객이 10000원 주문시 1000원을 할인해주고, 20000원 주문시에 2000원을 할인해주는 거에요.
새로운 요구사항이 주어졌습니다. 기존의 고정 할인 정책에서 비율 할인 정책으로 변경해야 하는데 우리는 DiscountPolicy
인터페이스를 이미 만들어놨으므로 구현체만 개발하면 되겠네요. 구현체 클래스명은 RateDiscountPolicy
로 하겠습니다.
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10; //10% 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
RateDiscountPolicy
구현체를 작성했으니 새로운 할인 정책을 애플리케이션에 적용해 보겠습니다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
새로운 할인 정책을 적용하기 위해서는 OrderServiceImpl
클래스의 코드를 변경해야 하는데 여기서 2가지 문제점을 발견할 수 있습니다.
=> DIP 위반: OrderServiceImpl은 DiscountPolicy 뿐 아니라 RateDiscountPolicy에도 의존하고 있다
=> OCP 위반: 코드의 기능을 확장하는데 OrderServiceImpl에 영향을 준다
이를 해결하기 위해 OrderServiceImpl이 인터페이스에만 의존하도록 의존관계를 변경해주면 됩니다.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
그리고 누군가 대신 OrderServiceImpl
에 DiscountPolicy
의 구현 객체를 생성하고 주입해주어야 할 것 같네요.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스 AppConfig
입니다. AppConfig
는 구현 객체를 생성하고, 생성한 객체 인스턴스의 레퍼런스를 생성자를 통해서 주입합니다.
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
}
MemberServiceImpl
는 이제 생성자를 통해서 구현체를 주입받습니다.
MemberServiceImpl
은 이제 MemberRepository
에만 의존하게 되었으므로 DIP 를 준수하게 되었습니다. 또, 기능 확장시 AppConfig
만 수정하면 되므로 OCP 도 준수하게 되었네요.
public class AppConfig {
...
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
이제 코드의 변경없이 AppConfig
의 수정만으로 기능을 확장할 수 있습니다.
OrderServiceImpl
이 DiscountPolicy
직접 생성한 후 구현체 주입)하는 것이 아니라 외부 (AppConfig
파일)에서 관리하는 것지금까지 순수 자바 코드로 DI를 적용했습니다. 코드를 스프링으로 전환해 보겠습니다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
@Configuration
: AppConfig에 설정을 구성
@Bean
: 스프링 컨테이너에 스프링 빈으로 등록
public class MemberServiceTest {
MemberService memberService;
@BeforeEach
public void beforeEach() {
// AppConfig appConfig = new AppConfig();
// memberService = appConfig.memberService();
ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
memberService = ac.getBean("memberService", MemberService.class);
}
@Test
void join() {
...
}
이제부터는 AppConfig
를 통한 직접 조회가 아닌 스프링 컨테이너를 통해서 필요한 스프링 빈을 찾아야 합니다. 스프링 빈은 ac.getBean()
메서드를 사용해서 찾을 수 있습니다.
자바 코드를 스프링으로 전환했지만 뭔가 코드만 복잡해진 것 같고 크게 달라진 점을 찾기 어렵습니다. 다음 시간부터 스프링 컨테이너를 사용했을 때의 장점에 대해 알아보겠습니다.