✔ Inflearn 강의 수강 내용 정리글입니다!
본 강의에서는 이전에 구현한 프로젝트 예제에 객체 지향 원리를 적용한다
public class RateDiscountPolicy implements DiscountPolicy {
@Override
public int discount(Member member, int price) {
}
}
할인 정책 DiscountPolicy 인터페이스의 새로운 구현 클래스 RateDiscountPolicy 생성
마찬가지로 테스트 코드를 작성하여 새로운 할인 정책 RateDiscountPolicy 이 제대로 동작하는지 확인
class RateDiscountPolicyTest {
RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 한다.")
void vip_o() {
//given
Member member = new Member(1L, "memberVIP", Grade.VIP);
//when
int discount = discountPolicy.discount(member, 10000);
//then
assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void vip_x() {
//given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member, 10000);
//then
assertThat(discount).isEqualTo(0);
}
}
@DisplayName 어노테이션을 사용하면 클래스와 메서드에 이름을 붙여줄 수 있다
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
할인 정책 변경 시 클라이언트인 OrderServiceImpl 이 인터페이스와 구현 클래스 두가지에 모두 의존하고 있다
문제 해결을 위하여 인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy 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();
}
}
코드 중복을 제거하고 역할이 잘 보이도록 리팩터링 한 코드
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
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();
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
➡ AppConfig를 통해 관심사 분리!
➡ AppConfig가 구현 클래스 선택
➡ MemberServiceImpl , OrderServiceImpl는 기능 실행 책임만!
구성 영역만 영향을 받고 사용 영역은 전혀 영향을 받지 않는다
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
SRP, DIP, OCP 적용
한 클래스는 하나의 책임만 져야한다
프로그래머는 "추상"에 의존해야지, 구체화에 의존하면 안된다." 의존성 주입은 이 원칙을 따르는 방법 중 하나다
소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다
정적인 클래스 의존관계
동적인 객체 인스턴스 의존 관계
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); // 생성자 주입
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy(); // 할인 정책 변경
}
}
@Configuration : 설정을 구성한다
@Bean : 스프링 컨테이너에 스프링 빈으로 등록
public class MemberApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
}
public class OrderApp {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
}
📍 코드가 좀 더 복잡해진 것 같은데, 스프링 컨테이너를 사용하면 어떤 이점이 있을까?
[출처 - Inflearn : 스프링 핵심 원리 - 기본편] https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard