• 애자일 소프트웨어에 인상적인 글귀 - "계획에 따르기 보다는 변화에 대응하기를"
  • Mac 기준으로 Method쪽에 CMD+SHIFT+T 를 누르면 Test 코드를 작성할 수 있다. 
  • Test 코드 작성 시 꿀팁은 given, when, then 
    • given 에 객체를 만들어주고
    • when 에 테스트 로직을 추가하고
    • then 에서 마지막으로 검증해준다. 
  • Test 코드에서 고려해야할 것은 성공, 실패 코드 두가지 이다.
  • 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);
     }
    }
  • 일반적으로 Java 코드로 작성하여 다형성을 이용해 의존관계를 구현하다보면 OCP, DIP 를 위반하는 케이스를 마주하게 된다.
    • OCP - 클라이언트의 코드 변경이 이루어지는것
    • DIP - 역할과 구현을 나누지 못하고 클라이언트가 역할과 구현 모두 의존하고 있는것 (추상화에 의존해야지 구체화에 의존하면 안된다.)
    • 단순히, 다형성으로 구현해주지 않고 인터페이스만 구현해주면 NullPointException이 발생한다. 
  • public class OrderServiceImpl implements OrderService {
     //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
     private DiscountPolicy discountPolicy;
    }
  • 이 문제를 해결하기 위해서 클라이언트에 DiscountPolicy의 구현체를 주입해줘야 하는데 그것이 Spring Container 역할이 된다.
  • 여기서는 Spring Container 대신 AppConfig 라고 하는 설정자를 지정해보자.
  • AppConfig 역할은 구현 객체를 책임지고 연결한다. DIP 를 지켜주기 위함이다.
  • 기존에 어떻게 구현되었는지 조금 더 생각해보자. 
    • 아래 코드를 보았을 때 OderServiceImpl은 DiscountPolicy, RateDiscountPolicy 두개 다 모두 의존하게 된다. (OCP, DIP 위반)
  • public class OrderServiceImpl implements OrderService {
     private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    }
  • 이 문제를 해결하는 방법으로 설정자를 지정한다. 즉, 관심사를 분리하는 방법이다.
    • 아래 코드처럼 설정자에 구현객체를 연결하는 책임만 전달하면 된다. 
    • 실제 MemberServiceImpl에서는 인터페이스만 의존하게 되므로 기존의 객체지향 설계 OCR, DIP 를 만족하게 된다.
    • 클라이언트 입장(MemberServiceImpl)에서 보면 외부에서 의존관계를 다형성으로 주입하고 이러한 상태를 DI(Dependency Injection) 이라고 한다. 더이상 구현 클래스는 클라이언트의 관심사가 아니다.
  • public class AppConfig {
     public MemberService memberService() {
     return new MemberServiceImpl(new MemoryMemberRepository());
     }
    }
  • //생성자로 넣어준다. 여기서는 더이상 MemoryMemberRepository는 찾아볼 수 없다. 
    //생성자 주입, injection 이라고 한다. 
    public class MemberServiceImpl implements MemberService {
     private final MemberRepository memberRepository;
     public MemberServiceImpl(MemberRepository memberRepository) {
     this.memberRepository = memberRepository;
     }
    }
  • 관련된 테스트 코드를 작성해보자. 
  • MemberService memberService;
    
    @BeforeEach
     public void beforeEach() {
     AppConfig appConfig = new AppConfig();
     memberService = appConfig.memberService();
     }

AppConfig 리팩토링

  • AppConfig 에서 역할과 구현 클래스가 보이도록 구성하는것이 좋다. (중복을 제거하는 의미이다. 따로 함수로 extract 하여 구현 클래스로 둔다.)
  • 확장성에 좋아진다. 
  • AppConfig 영역을 구성영역 그리고 일반적인 로직을 담당하는 클라이언트 관련되서는 사용영역으로 나눌 수 있다. 구성영역만 변경하면 사용영역을 변경하지 않아도 된다. 서로 코드적인 영향을 미치지 않는다.
  • SRP 관심사 분리 
  • // 한줄만 바꿔주면 기존 사용영역은 변경 필요가 없다. 
    public DiscountPolicy discountPolicy() {
    // return new FixDiscountPolicy();
     return new RateDiscountPolicy();
     }
  • 아래 사진 처럼! 

김영한님의 스프링 핵심 원리 강의 노트

IoC / DI

  • 프레임워크 vs 라이브러리
    • 프레임워크 
      • 작성한 코드를 대신 작성하는것을 대신 실행시켜준다. JUnit도 프레임워크라고 볼 수 있다. 
    • 라이브러리
      • 내가 작성한 코드를 직접 제어의 흐름을 담당한다면 라이브러리이다. 
  • 의존관계는 정적인 클래스 의존관계 그리고 Run Time에 결정되는 동적인 객체(인스턴스) 의존 관계를 볼 수 있다.
    • 아래 코드에서 실제 어떤 객체가 OrderServiceImpl 에 주입 될지 알 수 없다.
    • 애플리케이션 실행 시점에 생성된 객체 인스턴스의 참조가 연결된 의존 관계이다. 
    •  private final MemberRepository memberRepository;
       private final DiscountPolicy discountPolicy;
      //memberRepository에 JDBC 디비 객체일지 H2일지 뭐가 들어올지 모른다. 
       public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy
      discountPolicy) {
       this.memberRepository = memberRepository;
       this.discountPolicy = discountPolicy;
       }
  • 그래서 정적인 코드는 일반적으로 애플리케이션 레벨의 코드로 표현할 수 있다. 반대로 동적인 객체의 인스턴스 의존관계는 IoC 를 통해 제어권을 가져가게 된다. 마치 설정자에 의해 책임이 분리된것이다. 
  • 그래서, AppConfig 처럼 객체를 생성하고 관리하여 의존관계를 연결해주는 것을 IoC 컨테이너 (or DI 컨테이너) 라고 한다. 

Spring으로 전환

  • Spring Container 를 기반으로 소스를 관리해보자.
  • @Configuration 을 어노테이션으로 붙이면 설정 파일이 된다. 
  • @Bean 을 메서드 별로 처리하면 Spring Container 에 등록된다. 여기서 의미하는 Spring Container 는 ApplicationContext 이다. 
  • Spring Container 를 등록하는 방법은 아래 코드 처럼 사용한다.
  • 주목해서 보아야 할 부분은 아래 부분인데 @Bean 처리되어 Spring Container에 관리되어 getBean을 통해 꺼내주게 된다. 
    MemberService memberService =
    applicationContext.getBean("memberService", MemberService.class); //메서드이름, 타입 
    OrderService orderService = applicationContext.getBean("orderService",
    OrderService.class);
  • public class OrderApp {
     public static void main(String[] args) {
    // AppConfig appConfig = new AppConfig();
    // MemberService memberService = appConfig.memberService();
    // OrderService orderService = appConfig.orderService();
     ApplicationContext applicationContext = new
    AnnotationConfigApplicationContext(AppConfig.class);
     MemberService memberService =
    applicationContext.getBean("memberService", MemberService.class); //메서드이름, 타입 
     OrderService orderService = applicationContext.getBean("orderService",
    OrderService.class);
     long memberId = 1L;
     Member member = new Member(memberId, "memberA", Grade.VIP);
     memberService.join(member);
    Order order = orderService.createOrder(memberId, "itemA", 10000);
     System.out.println("order = " + order);
     }
    }

'Spring' 카테고리의 다른 글

컴포넌트 스캔  (0) 2021.12.12
싱글톤 컨테이너  (0) 2021.12.12
Spring 예제  (0) 2021.12.10
객체지향 설계와 스프링  (0) 2021.12.09
Spring 프로젝트 설정 & 테스트 코드 작성하기  (0) 2021.05.25