내가 공부하려고 올리는/스프링
관심사의 분리(IoC, DI 컨테이너)
결딴력
2022. 2. 2. 20:06
반응형
관심사의 분리
- 공연을 할 때 배우는 연기에만 집중하고 직접 상대 배역을 캐스팅하거나 시나리오를 집필하지 않는다.
- 배우는 연기를, 공연은 기획자가 준비한다.
- 이처럼 각각의 책임을 확실히 분리하는 것을 관심사의 분리라 한다.
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
- MemberServiceImpl은 MemberRepository 인터페이스에 의존
- MemberServiceImpl은 실제 어떤 구현 객체가 사용되는지는 모른다.
- 정적 클래스 의존관계
- 위 클래스 다이어그램만 보고도 OrderServiceImpl은 MemberRepository, DiscountPolicy에
의존하는 것을 확인할 수 있다. - BUT 이러한 클래스 의존관계 만으로는 어떤 실제 구현 객체가 OrderServiceImpl에 주입될지 알 수 없다.
- 위 클래스 다이어그램만 보고도 OrderServiceImpl은 MemberRepository, DiscountPolicy에
- 동적 클래스 의존 관계
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
- 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해
클라이언트와 서버의 실제 의존 관계가 연결되는 것을 의존관계 주입이라 한다.
IoC 컨테이너, DI 컨테이너
- AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을
IoC 컨테이너, DI 컨테이너라고 한다. - 의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
출처 : 인프런 김영한 님 스프링 강의
반응형