[스프링 핵심 원리 - 기본편] 스프링 핵심 원리 이해2 - 객체 지향 원리 적용 (관심사의 분리와 AppConfig 리팩터링)

Hyeonjun·2022년 8월 21일
0
post-thumbnail

관심사의 분리

미쳤다 미쳤어 김영한 그는 신인가?

애플리케이션을 하나의 공연이라 생각해보자.

각각의 인터페이스를 배역이라 생각하자

  • 실제 배역을 맡은 배우를 선택하는 것은 누구지?
    • 각 배역을 선택하는 것은 배우가 아니다.
  • 이전 코드는 배역(인터페이스)을 맡은 배우(구현체)가 다른 배역(인터페이스)의 배우(구현체)를 직접 초빙하는 것과 같음.
  • 배우가 공연을 하는 동시에 다른 배역도 정해야하는 다양한 책임을 갖게 되는 것.

관심사를 분리하자

  • 배우는 본인의 역할인 배역을 수행하는 것에만 집중해야 한다.
  • 배우는 어떤 여자 주인공이 선택되더라도 똑같이 공연을 할 수 있어야 한다.
  • 공연을 구성하고, 담당 배우를 섭외하고, 역할에 맞는 배우를 지정하는 책임을 담당하는 별도의 공연 기획자가 필요한 시점.
  • 공연 기획자를 만들고 배우와 공연 기획자의 책임을 분리하자.

AppConfig의 등장

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

AppConfig

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(new MemoryMemberRepository()); // 생성자 주입
    }

    public OrderService orderService() {
        return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
    }
}
  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    • MemberServiceImpl
    • MemoryMemberRepository
    • OrderServiceImpl
    • FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입(연결, Injection)해준다.
    • MemberServiceImplMemoryMemberRepository
    • OrderServiceImpMemoryMemberRepository, FixDiscountPolicy

MemberServiceImpl - 생성자 주입

public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

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

    ...
}
  • 설계 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다!
  • 단지 MemberRepository 인터페이스만 의존한다.
  • MemberServiceImpl의 입장에서 생성자를 통해 어떤 구현 객체가 주입될지 알 수 없다.
  • MemberServiceImp의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
  • MemberServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고, 실행에만 집중하면 된다.

클래스 다이어그램

  • 객체의 생성과 연결은 AppConfig가 담당한다.
  • DIP의 완성
    • MemberServiceImplMemberRepository인 추상에만 의존하면 된다. 이제 구체 클래스를 몰라도 된다.
  • 관심사의 분리
    • 객체를 생성하고 연결하는 역할이 명확히 분리됨.

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

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

OrderServiceImpl - 생성자 주입

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

    ...
}
  • 설계 변경으로 OrderServiceImplFixDiscountPolicy를 의존하지 않는다!
  • 단지 DiscountPolicy인터페이스만을 의존한다.
  • OrderServiceImpl입장에서 생성자를 통해 어떤 구현 객체가 주입될 지 알 수 없다.
  • OrderServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정한다.
  • OrderServiceImpl은 이제부터 실행에만 집중하면 된다.

AppConfig 실행

MemberApp

public class MemberApp {

    public static void main(String[] args) {
        AppConfig appConfig = new 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);
        System.out.println("findMember = " + findMember);
    }
}
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);
    }
}
  • AppConfig에서 설정한 구현체를 appConfig.~Service()에서 주입하게 됨.

정리

  • AppConfig를 통해서 관심사를 확실하게 분리했다.
  • AppConfig는 구체 클래스를 선택한다. 애플리케이션이 어떻게 동작해야 할 것인지 전체 구성을 책임진다.
  • 이제 책임을 보다 확실하게 분리하게 된 것.

    현재 AppConfig를 보면 중복이 있고, 역할에 따른 구현이 잘 안보인다.

  • 역할에 따른 구현을 한 눈에 확인하는 것이 굉장히 중요
  • AppConfig에서는 해당 부분을 확인하기 어려움.

AppConfig

public class AppConfig {

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

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    private DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
  • new MemoryMemberRepository()이 부분의 중복이 제거되었다.
    • 이제 MemoryMemberRepository를 다른 구현체로 변경할 때 한 부분만 변경하면 된다.
  • AppConfig를 보면 역할과 구현 클래스가 한 눈에 들어온다.
    • 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.
profile
더 나은 성취

0개의 댓글