인프런의 <스프링 핵심 원리 - 기본편>을 듣고 공부 용도로 정리한 글 입니다.
기능 변경하기
예제로 어떤 제품의 할인 정책을 바꿔보면서 어떤 문제점들이 있는 지 알아보겠다.
다형성을 이용하여 역할과 구현을 나눠 개발하였다. 따라서 정책 역할에 원하는 구현체만 갖다 낀다면 쉽게 할인 정책을 바꿀 수 있을 거 같다.
아래 그림처럼 할인 정책의 구현체로는 FixDiscountPolicy와 RateDiscountPolicy가 있다.
현재는 FixDiscountPolicy를 사용하고 있으며 RateDicountPolicy로 변경할 계획이다.

public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
다음과 같이 코드를 변경하면 객체지향 설계 원칙을 준수하며 잘 해결한 거 같지만, 사실은 아니다.
OCP와 DIP를 준수하지 못한다.
OCP : 변경하지 않고 확장할 수 있어야 하지만 지금 코드는 기능을 변경하면서 클라이언트 코드에 영향을 주고 있다.
DIP : 클라이언트가 인터페이스에도 의존하면서 구현체도 같이 의존하고 있다.
마치 배우가 자신의 상대 배우를 직접 선택하는 것과 같다. 배우는 단지 연기에만 집중해야 한다. 배우를 선택하는 것은 감독이 해야할 일이다. -> 관심사를 분리해야 한다.
AppConfig
연극에서 감독이 배우를 섭외하고, 배우를 지정하는 것처럼 AppConfig가 대신 구현 객체를 생성하고, 연결하도록 하자!
public class AppConfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
public MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
AppConfig가 필요한 구현 객체를 생성하고, 생성한 객체 인스턴스의 참조를 생성자를 통해서 주입해준다.
MemberServiceImpl -> MemoryMemberRepository
OrderserviceImpl -> MemoryMemberRepository, RateDiscountPolicy
할인 정책을 바꾸어도 클라이언트 코드에 변경이 없다. -> OCP 만족
생성자 주입 부분
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;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
}
클라이언트는 인터페이스에만 의존하고 있고 생성자를 통해서 구현 객체를 주입받는다.
어떤 구현 객체가 들어올 지는 외부(AppConfig)에서 결정한다. -> DIP 만족
IoC, DI, 그리고 컨테이너
제어의 역전 IoC
- 기존에는 클라이언트가 직접 필요한 구현 객체를 생성하고 연결하고 실행했다.
- 하지만 AppConfig가 등장 후 구현 객체는 자신의 로직만을 실행하고 프로그램의 제어 흐름을 AppConfig가 가져갔다.
- 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아닌 외부에서 관리하는 것을 제어의 역전 (IoC)라 한다.
* 프레임워크 vs 라이브러리
프레임워크가 내가 작성한 코드를 제어하고, 대신 실행한다면 그것은 프레임워크이다 ex) JUnit
반면 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것인 라이브러리다.
의존관계 주입 DI (Dependency Injection)
- OrderServiceImpl은 DiscountPolicy 인터페이스에만 의존하지 실제 어떤 구현 객체가 사용될지 모른다.
- 의존관계는 정적인 클래스 의존관계와 동적인 클래스 의존관계로 나뉘는다.
- 정적인 클래스 의존관계 : import만 보고 쉽게 판단 가능하다. OrderServiceImpl가 DiscountPolicy 인터페이스를 의존한다는 것은 실행하지 않아도 바로 알 수 있다.
- 동적인 클래스 의존관계 : 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.
- 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
IoC 컨테이너, DI 컨테이너
AppConfig 처럼 객체를 대신 생성하고 의존관계를 연결해주는 것을 말한다.
최근에는 의존관계 주입에 초점을 두어 주로 DI컨테이너라 한다.
스프링으로 전환
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
@Configuration은 Appconfig를 설정 정보로 사용하겠다는 어노테이션이다.
@Bean은 스프링 컨테이너에 Bean으로 등록하겠다는 것인데, @Bean 이 붙은 메서드를 모두 호출해서 객체를 스프링 컨테이너에 등록한다.
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);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "ItamA", 10000);
System.out.println("order = " + order);
}
}
기존에는 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제는 스프링 컨테이너를 통해서 사용한다.
스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾는다. 근데.. 뭐가 좋지?
'Backend > Spring' 카테고리의 다른 글
| [Spring] 의존관계 자동 주입 (0) | 2023.02.19 |
|---|---|
| [Spring] 컴포넌트 스캔 (0) | 2023.02.17 |
| [Spring] 싱글톤 컨테이너 (0) | 2023.02.13 |
| [Spring] 스프링 컨테이너와 스프링 빈 (0) | 2023.02.13 |
| [Spring] 객체 지향 설계와 스프링 (0) | 2023.02.12 |