김영한님의 스프링 핵심 원리 - 기본편 강의
19강 관심사의 분리를 듣고 작성했습니다.

DIP 지키기 - 의존 역전 원칙 (Dependency Inversion Principle)

이전 코드의 문제점

public class OrderServiceImpl implements OrderService {

    //private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); //추상에도 의존, 구체에도 의존 OCP 위반
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); //추상에도 의존, 구체에도 의존 OCP 위반

}
  • 인터페이스(주문 역할) 이 구현체(할인)가 주문이 직접 어떤 할인을 선택할지 결정한다.
  • 더 쉽게 예시를들면, 배우가 여자 주인공을 누구로 할지 선택해야함 (배우 - 인터페이스, 여자주인공 - 구현체)

가야할 방향

  • 배우는 본인의 역할인 배역을 수행에만 집중
  • 배우는 어떤 여자주인공이든 똑같이 공연을 해야함
  • 새로운 인물이 나타나야 한다.
  • 즉, 공연 기획자가 배우와 공연 기획자의 책임을 확실히 분리해야 한다.
  • 공연 기획자: 구성, 배우 섭외, 배우 지정

AppConfig 등장

AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.

  • MemberServiceImpl
  • OrderServiceImpl

AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)해준다.

  • MemberServiceImpl -> MemberMemoryRepository
  • OrderServiceImpl -> MemoryMemberRepository, FixDiscountPolicy
public class AppConfig {

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

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

}

참고로 AppConfig에서 생성자 주입시 반복되는 호출이 있는데, 아래와 같이 리팩토링 할 수 있다. 이는 20강 AppConfig 리팩터링 내용이다.

public class AppConfig {

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

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

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

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

}
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
  • MemberServiceImpl은 MemberMemoryRepository에 의존하지 않는다.
  • 단지, MemberRepository 인터페이스만 의존한다.
  • MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.
  • MemberServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
  • MemberServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고, 실행에만 집중하면 된다. (join, findMember)
public class OrderServiceImpl implements OrderService {

    // 완벽하게 DIP을 지키고 있다. interface만 알고 있음. FixDiscountPolicy인지 RateDiscountPolicy인지 알 필요가 없다.
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
  • OrderServiceImpl 도 FixDiscountPolicy 에 의존하지 않는다.
  • 단지, DiscountPolicy 인터페이스만 의존한다.
  • OrderServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다.
  • OrderServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
  • OrderServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고, 실행에만 집중하면 된다. (createOrder)

DIP의 완성

객체의 생성과 연결은 AppConfig가 담당한다.
MemberServiceImpl은 MemberRepository 인터페이스(추상)에만 의존하면 된다.
구체 클래스를 몰라도 된다.

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


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