💡 우리는 기존의 DiscountPolicy 인터페이스를 활용해 새로운 할인 정책(정률 할인) 구현체를 만들면 된다!
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10; //10% 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
class RateDiscountPolicyTest {
RateDiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 한다.")
void vip_o() {
//given
Member member = new Member(1L, "memberVIP", Grade.VIP);
//when
int discount = discountPolicy.discount(member, 10000);
//then
assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void vip_x() { //given
Member member = new Member(2L, "memberBASIC", Grade.BASIC);
//when
int discount = discountPolicy.discount(member, 10000);
//then
assertThat(discount).isEqualTo(0);
}
}
💡 할인 정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.
💡 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다 (OCP위반)
💡
OrderServiceImpl
이DiscountPolicy
인터페이스와FixDiscountPolicy
모두 의존 → DIP 위반
💡
FixDiscountPolicy
를 RateDiscountPolicy로 변경하면OrderServiceImpl
의 소스 코드도 변경해야함 → OCP 위반
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
❓ 그런데
구현체
가 없는데 어떻게 코드를 실행할 수 있을까?
❗ 누군가가 클라이언트인OrderServiceImpl
에DiscountPolicy
의 구현 객체를 대신 생성하고 주입해주어야 한다.
AppConfig
는 애플리케이션의 실제 동작에 필요한 구현 객체
를 생성한다.AppConfig
는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)
해준다.
MemberServiceImpl
은MemoryMemberRepository
의존 X
MemberRepository
인터페이스만 의존
생성자를 통해서 어떤 구현 객체를 주입할지는 오직
외부(AppConfig)
에서 결정
의존관계에 대한 고민은 외부에 맡기고 실행에만 집중
💡 추상(Interface)에만 의존 →
DIP 완성
💡 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리 →
관심사의 분리
package hello.core;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy());
}
}
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
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();
}
}
💡 AppConfig
의 등장으로 애플리케이션이 사용 영역과, 객체를 생성하고 구성(Configuration)하는 영역
으로 분리
Framework
Library
클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다.
정적인 의존관계는 애플리케이션을 실행하지 않아도 분석할 수 있다
객체 인스턴스를 생성하고, 그 참조값을 전달해서 연결된다.
실행 시점(런타임)에 외부에서 실제 구현 객체를 생성 및 클라이언트에 전달
클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.
AppConfig
처럼 객체를 생성 및 관리 & 의존관계 연결 → IoC 컨테이너 or DI 컨테이너
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.member.MemoryMemberRepository;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@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();
}
}
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MemberApp {
public static void main(String[] args) {
// AppConfig appConfig = new AppConfig();
// MemberService memberService = appConfig.memberService();
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("new member = " + member.getName());
System.out.println("find Member = " + findMember.getName());
}
}
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.order.Order;
import hello.core.order.OrderService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
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, "itemA", 10000);
System.out.println("order = " + order);
}
}
ApplicationContext
를 스프링 컨테이너
라 한다.@Configuration
이 붙은 AppConfig
를 설정(구성) 정보로 사용한다.@Bean
이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다.스프링 빈
이라 한다.applicationContext.getBean()
메서드를 사용해서 찾을 수 있다.