오봉이와 함께하는 개발 블로그

Spring - 스프링 핵심 원리 이해2 - 2(객체 지향 원리 적용) 본문

BE/Spring

Spring - 스프링 핵심 원리 이해2 - 2(객체 지향 원리 적용)

오봉봉이 2022. 6. 2. 19:54
728x90

관심사의 분리

  • 본인의 역할을 수행하는 것에만 집중해야 한다
  • 어떤 구현체가 선택되더라도 코드 변경 없이 실행할 수 있어야 한다.
    • 이를 위해 중간에 연결할 수 있는 무언가가 필요하다!

AppConfig 등장

  • 앱의 전체 동작 방식을 구성(config)하기 위해 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스
package hello.core;

import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberSerivce;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;

public class AppConfig {
    public MemberSerivce memberSerivce() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}
  • AppConfig는 앱의 실제 동작에 필요한 구현 객체 생성
    • MemberSerivce (인터페이스)
      • MemberServiceImpl (구현체)
        • MemoryMemberRepository
    • OrderService (인터페이스)
      • OrderServiceImpl (구현체)
        • MemoryMemberRepository
        • FixDiscountPolicy

생성자 주입

package hello.core.member;

public class MemberServiceImpl implements MemberSerivce{

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    /*
    ...
    */
}
  • AppConfig로 설계를 변경했기 때문에 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않음
    • MemberRepository(인터페이스)만 의존
    • MemberServiceImpl는 어떤 구현체가 들어올지 알 수 없음
    • MemberServiceImpl의 생성자를 통해 어떤 구현체가 주입될지는 외부(AppConfig)에서 결정된다
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로 설계를 변경했기 때문에 OrderServiceImpl은 MemoryMemberRepository, FixDiscountPolicy를 의존하지 않음
    • MemberRepository, DiscountPolicy(인터페이스)만 의존
    • OrderServiceImpl은 어떤 구현체가 들어올지 알 수 없음
    • OrderServiceImpl의 생성자를 통해 어떤 구현체가 주입될지는 외부(AppConfig)에서 결정된다

클래스 다이어그램

  • 객체 생성과 연결은 AppConfig가 담당
  • DIP 완성
    • MemberServiceImpl은 MemberRepository인 추상에만 의존하면 됨(구체 클래스를 몰라도 된다)
    • 이로써 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리됨

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

  • AppConfig는 MemoryMemberRepository객체를 생성하고 참조값을 MemberServiceImpl을 생성하며 참조값 전달
  • 클라이언트(MemberServiceImpl)의 입장에서 보면 의존관계를 외부(AppConfig)에서 주입하는 거 같다 하여 DI(Dependency Injection), 의존성 주입이라 함

AppConfig 실행

public class MemberApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberSerivce memberSerivce = appConfig.memberSerivce();
//        MemberSerivce memberSerivce = new MemberServiceImpl();
        Member member = new Member(1L, "memberA", Grade.VIP);
        memberSerivce.join(member);

        Member findMember = memberSerivce.findMember(1L);
        System.out.println("new member = " + member.getName());
        System.out.println("find member = " + findMember.getName());
    }
}
public class OrderApp {
    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberSerivce memberSerivce = appConfig.memberSerivce();
        OrderService orderService = appConfig.orderService();
//        MemberSerivce memberSerivce = new MemberServiceImpl();
//        OrderService orderService = new OrderServiceImpl();

        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberSerivce.join(member);

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

        System.out.println("order = " + order);
        System.out.println("order.calculatePrice = " + order.calculatePrice());
    }
}

테스트 코드 수정

  • @BeforeEach는 각 테스트를 실행하기 전에 호출된다.
public class MemberServiceTest {

    MemberSerivce memberSerivce;
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberSerivce = appConfig.memberSerivce();
    }
//    MemberSerivce memberSerivce = new MemberServiceImpl();

    @Test
    void join() {
        // given
        Member member = new Member(1L, "memberA", Grade.VIP);
        // when
        memberSerivce.join(member);
        Member findMember = memberSerivce.findMember(1L);
        // then
        Assertions.assertThat(member).isEqualTo(findMember);
    }
}
public class OrderServiceTest {

    MemberSerivce memberSerivce;
    OrderService orderService;
    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberSerivce = appConfig.memberSerivce();
        orderService = appConfig.orderService();
    }

//    MemberSerivce memberSerivce = new MemberServiceImpl();
//    OrderService orderService = new OrderServiceImpl();

    @Test
    void createOrder() {
        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberSerivce.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 10000);
        Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
    }
}

정리

  • AppConfig를 통해 관심사를 확실하게 분리
    • 배역, 배우를 생각하자
  • AppConfig는 공연 기획자
  • AppConfig는 구체 클래스를 선택한다.
    • 앱이 어떻게 동작해야 할지 전체 구성을 책임진다
  • OrderServiceImpl(MemberService)은 기능을 실행하는 책임만 지면 된다.
출처 : 인프런 김영한 지식공유자님의 스프링 완전 정복 로드맵 강의
728x90
Comments