본문 바로가기
내가 공부하려고 올리는/스프링

관심사의 분리(IoC, DI 컨테이너)

by 결딴력 2022. 2. 2.
반응형

관심사의 분리

  • 공연을 할 때 배우는 연기에만 집중하고 직접 상대 배역을 캐스팅하거나 시나리오를 집필하지 않는다.
  • 배우는 연기를, 공연은 기획자가 준비한다.
  • 이처럼 각각의 책임을 확실히 분리하는 것을 관심사의 분리라 한다.

 

 

AppConfig

  • 구현 객체를 생성하고 연결하는 책임을 갖는 별도의 설정 클래스

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


  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 클래스를 주입
  • 위의 코드를 컴파일 오류 없이 동작하게 하기 위해서는 각 Service 클래스에서 생성자를 만든다.

        private final MemberRepository memberRepository;
     	
        public MemberServiceImpl(MemberRepository memberRepository) {
     		this.memberRepository = memberRepository;
     	}​
  • 이렇게 코드를 수정하면 클라이언트는 오로지 MemberRepository 인터페이스에만 의존한다. => DIP 완성
  • MemberServiceImpl의 경우 생성자를 통해 어떤 구현 객체가 주입되는지 알지 못한다.
  • 따라서 위의 코드를 통해 관심사가 분리되었다는 것을 알 수 있다.
    (MemberServiceImpl실행만을 담당, 구현 클래스의 주입AppConfig가 담당)

 

 

DI

 

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

 

 

  • appConfig 객체는 memoryMemberRepository 객체를 생성
  • 생성된 객체의 참조값을 memberServiceImpl을 생성하면서 생성자로 전달
  • 클라이언트 객체인 memberServiceImpl 입장에서는 의존관계를 외부에서 주입하는 것과 같음
  • 이러한 방식을 DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 함

 

 

AppConfig 리팩터링

  • 기존 AppConfig는 중복이 있고, 역할에 따른 구현이 잘 보이지 않는다.
    기대하는 역할과 구현 그림
  • 위의 그림처럼 역할과 구현을 명확하게 나누기 위해 리팩터링을 다음과 같이 진행한다.

    public class AppConfig {
    	//역할
     	public MemberService memberService() {
     		return new MemberServiceImpl(memberRepository());
     	}
     	
            //역할
     	public OrderService orderService() {
     		return new OrderServiceImpl(
     			memberRepository(),
     			discountPolicy());
     	}
     
     	//구현
     	public MemberRepository memberRepository() {
     		return new MemoryMemberRepository();
     	}
     
     	//구현
     	public DiscountPolicy discountPolicy() {
     		return new FixDiscountPolicy();
     	}
    }​
  • 기존에 new MemoryMemberRepository() 중복으로 사용하던 것을 제거하였다.
  • 역할과 구현을 명확히 보이게 리팩터링 하였다.

 

 

할인 정책 변경

  • 이 상황에서 할인 정책을 정률 할인 정책으로 변경하면 어떻게 될까?
  • 다음과 같이 코드를 수정하면 된다.

     public DiscountPolicy discountPolicy() {
    	// return new FixDiscountPolicy();
     	return new RateDiscountPolicy();
     }​
  • 할인 정책을 RateDiscountPolicy()로 변경했으나 사용 영역의 코드는 변경하지 않아도 된다.
  • 구성 영역의 코드만 위와 같이 변경하면 된다.

 

 

좋은 객체 지향 설계 원칙 적용

  • AppConfig를 도입함으로써 SRP, DIP, OCP 원칙이 적용
  • SRP : 단일 책임 원칙
    • 클라이언트 객체는 실행하는 책임만 담당
    • AppConfig는 구현 객체를 생성하고 연결하는 책임을 담당
    • 관심사가 명확히 분리됨
  • DIP : 의존관계 역전 원칙
    • 클라이언트 코드는 오로지 인터페이스에만 의존
    • AppConfig에서 클라이언트 코드에 의존관계를 주입
  • OCP : 개방-폐쇄 원칙
    • 할인 정책이 변경되어도 클라이언트의 코드를 변경하지 않는다.
    • 할인 정책 변경 시 오로지 구성 영역(AppConfig)의 코드만 변경

 

 

IoC, DI, 컨테이너

IoC

  • MemberServiceImpl을 예로 들어, 이 클래스는 필요한 인터페이스를 호출하지만
    어떤 구현 객체들이 실행될지는 모른다.
  • 어떤 구현 객체가 실행될지는 AppConfig가 권한을 갖는다.
  • 정리하자면 프로그램에 대한 제어 흐름 권한을 모두 AppConfig가 갖는다는 것이다.
  • 이렇게 클라이언트 구현 객체가 아닌 외부에서 제어 흐름을 관리하는 것을 제어의 역전, IoC라 부른다.

 

DI

  • MemberServiceImplMemberRepository 인터페이스에 의존
  • MemberServiceImpl은 실제 어떤 구현 객체가 사용되는지는 모른다.
  • 정적 클래스 의존관계
    클래스 다이어 그램
    • 위 클래스 다이어그램만 보고도 OrderServiceImplMemberRepository, DiscountPolicy
      의존하는 것을 확인할 수 있다.
    • BUT 이러한 클래스 의존관계 만으로는 어떤 실제 구현 객체가 OrderServiceImpl주입될지 알 수 없다.
  • 동적 클래스 의존 관계
    • 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
    • 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달
      클라이언트와 서버의 실제 의존 관계가 연결되는 것을 의존관계 주입이라 한다.
      객체 다이어그램

IoC 컨테이너, DI 컨테이너

  • AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을
    IoC 컨테이너, DI 컨테이너라고 한다.
  • 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.

 

 

 

 

출처 : 인프런 김영한 님 스프링 강의

 

반응형

댓글