코드 그라데이션

관심사의 분리 본문

Spring/핵심 원리 구현

관심사의 분리

완벽한 장면 2024. 1. 16. 16:33

상황 가정

애플리케이션을 하나의 공연이라 생각해보자. 각각의 인터페이스를 배역(배우 역할)이라 생각하자. 

그런데! 실제 배역 맞는 배우를 선택하는 것은 누가 하는가?

로미오와 줄리엣 공연을 하면 로미오 역할을 누가 할지 줄리엣 역할을 누가 할지는 배우들이 정하는게 아니다.

 

이전 코드는 마치 로미오 역할(인터페이스)을 하는 레오나르도 디카프리오(구현체, 배우)가 줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우)을 직접 초빙하는 것과 같다. 

디카프리오는 공연도 해야하고 동시에 여자 주인공도 공연에 직접 초빙해야 하는 **다양한 책임**을 가지고 있다.

 

 

관심사 분리

 

AppConfig 등장

  • 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만든다.

AppConfig

package inflearn.spring_core.config;

import inflearn.spring_core.discount.FixDiscountPolicy;
import inflearn.spring_core.member.MemberService;
import inflearn.spring_core.member.MemberServiceImpl;
import inflearn.spring_core.member.MemoryMemberRepository;
import inflearn.spring_core.order.OrderService;
import inflearn.spring_core.order.OrderServiceImpl;

// 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스
public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}

 

 

 

 

MemberServiceImpl - 생성자 주입

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository; // 수정

    // 수정 후 생성자 추가
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Override
    public void join(Member member) {
        memberRepository.save(member);
    }

    @Override
    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }

}

=> MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 **실행에만 집중**하면 된다.

    (이제 DIP를 지키게 됨)

 

 

그림 - 클래스 다이아그램

 

 

그림 - 회원 객체 인스턴스 다이어그램

 

 

OrderServiceImpl - 생성자 주입

package inflearn.spring_core.order;

import inflearn.spring_core.discount.DiscountPolicy;
import inflearn.spring_core.member.Member;
import inflearn.spring_core.member.MemberRepository;

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); step1. 이거 삭제
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); step2. 이거 삭제
    private final DiscountPolicy discountPolicy; // step3. 인터페이스에만 의존하도록 설계와 코드를 변경
    // 실제 실행을 해보면 NPE(null pointer exception)가 발생한다.

    // 생성자 추가
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
        /*
        discountPolicy 얘는 어떤 discountPolicy 구현체를 넣어줄 지 전혀 모른다.(클래스 정보x)
         */
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findById(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice);

        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}

 


AppConfig 실행

사용 클래스 - MemberApp

package inflearn.spring_core;

import inflearn.spring_core.config.AppConfig;
import inflearn.spring_core.member.Grade;
import inflearn.spring_core.member.Member;
import inflearn.spring_core.member.MemberService;

public class MemberApp {

    public static void main(String[] args) {

//        MemberService memberService = new MemberServiceImpl();//직접 객체생성하던걸
        AppConfig appConfig = new AppConfig(); // AppConfig를 통해 생성받고 받아오는 걸로 바꿈.
        MemberService memberService = appConfig.memberService();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberService.join(member); // 회원가입이 됨.

        Member findMember = memberService.findMember(1L); // 조회

        // 찍어보기
        System.out.println("new member = " + member.getName());
        System.out.println("find Member = " + findMember.getName());
    }
}

 

사용 클래스 - OrderApp

package inflearn.spring_core;

import inflearn.spring_core.config.AppConfig;
import inflearn.spring_core.member.Grade;
import inflearn.spring_core.member.Member;
import inflearn.spring_core.member.MemberService;
import inflearn.spring_core.order.Order;
import inflearn.spring_core.order.OrderService;

public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.orderService();

        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);
    }
}

 

테스트 코드 오류 수정

MemberServiceTest

class MemberServiceTest {

    private MemberService memberService; // 수정

    //추가
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }

    @DisplayName("회원가입 테스트")
    @Test
    void join() {
        //given
        Member member = new Member(1L, "memberA", Grade.VIP); // 멤버 세팅

        //when
        memberService.join(member); // 가입시킴
        Member findMember = memberService.findMember(1L); // 멤버 찾기

        //then
        assertThat(member).isEqualTo(findMember); // 가입한 멤버와 조회한 멤버가 같은지 검증
    }
}

 

OrderServiceTest

class OrderServiceTest {

    private MemberService memberService; //수정
    private OrderService orderService; //수정


    // 추가
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

    @DisplayName("주문 생성 테스트")
    @Test
    void createOrder() {
        //given
        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        //when
        Order order = orderService.createOrder(memberId, "itemA", 10000);

        //then
        assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}
  • `@BeforeEach` 는 각 테스트를 실행하기 전에 호출된다.

 


정리

 

728x90
Comments