만약 기획자가 정액 할인이 아닌 정률 할인으로 변경을 요구한다면?
RateDiscountPolicy 를 만들면 된다!
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
ctrl + shift + T : Create Test
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 한다.")
void vip_o() {
// given
Member member = new Member(1L, "memberVIP", Grade.VIP);
// when
int discount = discountPolicy.discount(member, 10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
실행 결과 : SUCCESSFUL
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void vip_x() {
// given
Member member = new Member(1L, "memberBASIC", Grade.BASIC);
// when
int discount = discountPolicy.discount(member, 10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
실행 결과 : FAILED
💡 실패 test도 해야 한다!!!
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
새로운 할인 정책을 변경하려는 경우, 할인 정책의 클라이언트인 OrderServiceImpl의 코드가 변경되어야 한다.
OrderServiceImpl에 DiscountPolicy 구현 객체를 대신 생성하고 주입해주어야 한다.
애플리케이션의 전체 동작 방식을 구성하기 위해, 구현 객체를 생성하고 연결하는 책임을 가지는 별도의 설정 클래스
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
Appconfig가 구체 클래스를 선택하여 애플리케이션이 어떻게 동작할지 전체 구성을 책임진다.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
- 중복 제거 : 구현체를 변경할 경우 해당 부분의 코드만 변경
- 역할 구분 : 애플리케이션의 전체 구성을 빠르게 파악 가능
AppConfig가 객체의 생성과 연결을 담당하면서 사용 영역과 구성 영역이 분리됨
▶ 사용 영역의 변경 없이 구성 영역의 변경만으로 작동 방법을 변경할 수 있음
우리는 5가지 원칙 중 SRP, DIP, OCP 적용!
한 클래스는 하나의 책이만 가져야 한다.
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.
소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.
프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것
프레임워크가 코드를 제어하고 대신 실행 (예. JUnit)
코드가 직접 제어 흐름을 담당
애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라 한다.
정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계를 분리해서 생각해야 한다.
AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다.
@Configuration
이 붙은 AppConfig를 설정(구성)정보로 사용한다.@Bean
이라 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록한다. (스프링 빈)👀 스프링 컨테이너에 등록된 객체 (스프링 빈)