객체지향의 원칙알고 보면 좋다.
완벽한 OCP, DIP를 수행하기는 쉽지 않다.
수업을 따라오면서 지금까지 작성한 코드는 다음과 같다.
서비스 = Member, Order, Grade
도메인 = MemberServiceImpl, OrderServiceImpl
리포지토리 = MemoryMemberRepository
이를 통해 우리는 회원을 등록할 수 있고, 주문을 생성할 수 있고 이를 메모리에 저장할 수 있다.
그리고 등급에 따른 고정할인을 제공하는 FixDiscount를 작성하였다. 이 discountpolicy는 orderServiceImpl에 적용된다.
다음의 코드를 보자
public class OrderServiceImpl implements OrderService {
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
}
이 경우, 만약 discountPolicy가 새로 개발한 RateDiscount로 변경된다면, orderImpl에서 해당 부분을 바꿔치기하면될것이다.
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
이때, orderServiceImpl은 DisocountPolicy라는 인터페이스 (추상화), 그리고 동시에 Rate / Fix 라는 구체적 클래스(구현)에 의존하고 있다.
이는 원칙에 위배되는 일이다. 따라서 우리는 이를 해결해줘야한다.
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
이렇게 수정하면 뭐가 나올까?
이 인터페이스는 아무것도 포인팅하지않는다. 이는 NPE를 야기한다.
이를 해결하기 위해서 애플리케이션 작동방식을 총괄하는 Appconfig를 사용해보자.
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 RateDiscountPolicy();
}
}
MemberServicesms memberRepository를 쓰는 memberServiceImpl을 불러온다.
OrderService는 memberRepository, discountPolicy를 쓰는 OrderServiceImpl을 불러온다.
MemberRepository는 MemoryMemberRepository를 불러온다.
DiscountPolicy는 RateDiscountPolicy를 불러온다.
이런식으로 별도의 Config 클라스를 통해서 각 메소드들의 호출관계를 조정해줄 수 있다.
이는 기존의 객체디자인 패턴에서도 종종 보이던 패턴이다.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
이런식으로 인터페이스에만 의존하도록 변경된 각 클래스들은, 생성자를 통해서 값을 주입받으면 된다.
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
실행순서
1. MemberService 객체에 앱 컨피그.memberService를 호출한다.
2. config의 memberService는 MemberServiceImpl(memberRepository(new MemoryMemberRepository))를 호출한다.
3. 이는 MemberServiceImpl의 생성자를 통해 MemberService안의 memberRepository와 연동된다.
예시)
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
이런식으로 Configuration, Bean Annotation을 붙이는것으로 이를 해결 할 수 있다. 그리고 실제 적용은 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
이런식으로 ApplicationContext로 호출하고, 세부값들을 membberService에 넣어주는식으로 처리하면 된다.
이러면 스프링에서 각 클래스들을 Bean화 하여 컨테이너에 등록하고 알아서 관리해준다.