본문 바로가기
개인/[java] 스프링 핵심 원리 학습

관심사의 분리

by hyeley5 2023. 8. 28.

애플리케이션을 하나의 공연이라고 생각해보면,

인터페이스를 배역(배우 역할) 

실제 배역을 정할 때 여 주인공, 남 주인공은 배우들이 정하는게 아니다.

 

하지만 이전에 했던 코드들은

마치 로미오 역할(인터페이스)을 하는 남 주인공(구현체, 배우)이 ------>  줄리엣 역할(인터페이스)을 하는 여자 주인공(구현체, 배우) 직접 초빙하는 것과 똑같다.

 

 

 

public class OrderServiceImpl implements OrderService{

 

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    private DiscountPolicy discountPolicy = new FixDiscountPolicy();

 

OrderServiceImpl 은 OrderService 관련 로직만 해야하는데,

FixDiscountPolicy 정책을 자기가 직접 선택하며 discountPolicy 에 할당한다..

 

다시 공연을 예로...
남자 주인공은 공연도 해야하고 여주도 직접 초빙해야 하기 때문에 다양한 책임을 가지게 된다.

 

 

관심사를 분리해야 한다!

  • 배우는 본인의 역할인 배역을 수행하는 것에만 집중해야 한다.
  • 남주는 어떤 여주가 와도 똑같이 공연을 할 수 있어야 한다!
  • 공연을 구성하고, 배우를 섭외하고, 책임을 담당하는 별도의 "공연 기획자"가 나올 시점이다.

 

애플리케이션도 이렇게 개발을 해야 한다!

 

 

 

AppConfig 등장!

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

 

기존에 만들었던 MemberServiceImpl 코드에서 new MemoryMemberRepository(); 지우고

 

private final MemberRepository memberRepository;

public MemberServiceImpl(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}

생성자를 만들어준다. 생성자를 통해서 

 

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository());
    }
}

AppConfig 통해서 이렇게 해서 확인해보면

 

MemberServiceImpl 에는 MemoryMemberRepository() 코드가 없고

오로지 MemberRepository 인 인터페이스만 있다!! ---> 추상화에만 의존하는 것이다! --> DIP 를 지켰다.

 

이러한 부분에서 용어를 생성자를 통해서 객체가 생성된 것으로 해서 생성자 주입이라고 한다.

 

 

똑같이 OrderService도 만들어주고

 

이렇게 되면 OrderServiceImpl 은 Rate가 들어올 지, Fix가 들어올 지는 전혀 모르는 것이다! (마치 대본 보고 공연하듯이 로직만 실행한다.)

 

파라미터로 들어가는데 주입해준다고 표현한다. (Injection)

 

다시 반복하면

  • 설계 변경으로 MemberServiceImpl 은 MemoryMemberRepository 를 의존하지 않는다!
  • 단지 MemberRepository 인터페이스에만 의존한다. 
  • MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입 될 지)는 알 수 없다. 
  • MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다.

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

클래스 다이어그램

객체의 생성과 연결은 AppConfig가 담당한다.

----> DIP 완성

MemberServiceImpl MemberRepository 인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.

----> 관심사의 분리 : 객체를 생성하고 연결하는 역할 / 실행하는 역할이 명확히 분리되었다.

 

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

  • appConfig 객체는 memoryMemberRepository 객체를 생성하고 그 참조값을 memberServiceImpl 을 생성하면서 생성자로 전달한다.
  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서
    DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다. 

 

OrderServiceImpl 에서도 FixDiscountPolicy 를 의존하지 않는다.

단지 DiscountPolicy 인터페이스만 의존한다!

  • OrderServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
  • OrderServiceImpl 의 생성자를 통해서 어떤 구현 객체을 주입할지는 오직 외부( `AppConfig` )에서 결정한다.
  • OrderServiceImpl 은 이제부터 실행에만 집중하면 된다.
  • OrderServiceImpl 에는 MemoryMemberRepository , FixDiscountPolicy 객체의 의존관계가 주입된다.

 

 

 

MemberApp에서 

public class MemberApp {

    public static void main(String[] args) {
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
//        MemberService memberService = new MemberServiceImpl();
        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());
    }
}

이렇게 변경을 해준다.

 

public class OrderApp {

    public static void main(String[] args) {
        
        // AppConfig 통해서
        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);
        System.out.println("order.calculatePrice = " + order.calculatePrice());

    }
}

OrderApp도 변경!

 

 

이렇게 변경 후 테스트 코드도 수정해주어야 한다.

 

public class MemberServiceTest {
    
    // 변경 전
//    MemberService memberService = new MemberServiceImpl();
    MemberService memberService;

    @BeforeEach
    public void beforEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }

이렇게 해주어야 실행하기 전에 할당하고 테스트가 돌아가게끔! (테스트 두번 있음 두 번 돌아간다.)

 

public class OrderServiceTest {

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

오더서비스도 수정!

 

 

테스트 다 성공!

  • AppConfig는 공연 기획자다.
  • AppConfig는 구체 클레스를 선택한다. 배역에 맞는 담당 배우를 선택! 어떻게 동작해야 할지 전체 구성을 책임진다.
  • 이제 각 배우들은 담당 기능을 실행하는 책임만 진다.

 

다음은 AppConfig 리팩터링!! 

댓글