객체 지향 원리 적용
- 애자일 소프트웨어에 인상적인 글귀 - "계획에 따르기 보다는 변화에 대응하기를"
- 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 |