public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
OrderServiceImpl
는 추상(인터페이스)DiscountPolicy
뿐만 아니라 구체(구현)클래스 FixDiscountPolicy
RateDiscountPolicy
에도 의존하고 있음기대했던 의존관계 실제 의존관계
OrderServiceImpl
이 인터페이스 뿐만 아니라 인 구체 클래스도 함께 의존하고 있음 -> DIP 위반FixDiscountPolicy
를 RateDiscountPolicy
로 변경하는 순간 OrderServiceImpl
의 소스 코드도 함께 변경해야 함 -> OCP 위반public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
애플리케이션의 전체 동작 방식을 구성(config)하기 위해, 클라이언트인 OrderServiceImpl
대신 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 생성
AppConfig.java
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl( new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
MemberServiceImpl
MemoryMemberRepository
OrderServiceImpl
FixDiscountPolicy
MemberServiceImpl
→ MemoryMemberRepository
OrderServiceImpl
→ MemoryMemberRepository
FixDiscountPolicy
MemberServiceImpl - 생성자 주입
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
...
MemberServiceImpl
은 MemoryMemberRepository
를 의존하지 않고 단지 MemberRepository 인터페이스
만 의존MemberServiceImpl
클라이언트 입장에서 생성자를 통해 어떤 구현 객체가 들어올지는 알 수 없음MemberServiceImpl
의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부AppConfig
에서 결정MemberServiceImpl
은 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중AppConfig
가 담당MemberServiceImpl
은 MemberRepository
인 추상 인터페이스에만 의존, 구체클래스 몰라도 됨MemberServiceImpl
입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 의존관계 주입 또는 의존성 주입 DI(Dependency Injection)
이라고 함AppConfig
코드에서 역할과 구현을 분리 해야함
AppConfig.java
리팩터링 후
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();
}
}
MemoryMemberRepository
를 다른 구현체로 변경할 때 한 부분만 변경하면 됨AppConfig.java
public class AppConfig {
...
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig
에서 할인 정책 역할을 담당하는 구현을 FixDiscountPolicy
→ RateDiscountPolicy
객체로 변경AppConfig
만 변경OrderServiceImpl
를 포함해서 사용 영역의 어떤 코드도 변경할 필요가 없음내가 뭔가 호출하는것이 아니라 프레임워크 같은 것이 내 코드를 대신 호출해주는 것
프레임워크 vs 라이브러리
- 내가 작성한 코드를 제어하고, 대신 실행하면 프레임워크 (Ex. JUnit)
- 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 라이브러리
정적인 클래스 의존 관계
와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계
둘을 분리해서 생각해야 함정적인 클래스 의존 관계
- 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있음
- 애플리케이션을 실행하지 않아도 분석할 수 있음
- 하지만 이러한 클래스 의존관계 만으로는 실제 어떤 객체가
OrderServiceImpl
에 주입 될지 알 수 없음
동적인 클래스 의존 관계
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계
IoC 컨테이너
또는 DI 컨테이너
라고 함 DI 컨테이너
라 함AppConfig.java
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService() {
return new OrderServiceImpl( memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
AppConfig
에 설정을 구성한다는 뜻의 @Configuration
을 작성@Bean
작성 > 스프링 컨테이너에 스프링 빈으로 등록OrderApp.java
에 스프링 컨테이너 적용
public class OrderApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
// OrderService orderService = appConfig.orderService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
//getBean("메서드 이름",반환 타입)
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
...
ApplicationContext
: 스프링 컨테이너 > 모든것을 다 관리@Configuration
이 붙은 AppConfig
를 구성 정보로 사용@Bean
이 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록 > 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 함@Bean
이 붙은 메서드의 명을 스프링 빈의 이름으로 사용applicationContext.getBean()
메서드를 사용해서 스프링 컨테이너를 통해 필요한 스프링 빈을 찾을 수 있음