이 글은 스프링 핵심 원리 - 기본편 을 듣고 정리한 내용입니다.
package hello.core.discount;
import hello.core.member.Grade;
import hello.core.member.Member;
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent=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.assertj.core.api.Assertions;
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(1000);
}
}
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
애플리케이션 전체 동장 방식 구성(Config)하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스 이다.
MemberServiceImpl - 생성자 주입
-> MemberServiceImpl 내에서 MemberRepository 구현체를 new로 생성하는것이 아니라 생성자를 통해 MemberRepository를 주입받도록 변경하였다.
이렇게 설계 변경으로 인해 MemberServiceImpl은 MemoryMemberRepository 를 의존하지 않는다.
MemberServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정된다.
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
//memberRepository interface필요, but interface만 가지고 있으면 nullPointerException 터질것임.
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
// private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
public OrderService orderService(){
return new OrderServiceImpl( new MemoryMemberRepository(), new FixDiscountPolicy());
}
OrderServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서 결정한다.
OrderServiceImpl은 이제부터 '의존관계에 대한 고민은 외부'에게 맡기고, '실행에만 집중'하면 됨.
객체의 생성과 연결은 AppConfig가 담당
DIP 완성 : MemberServiceImpl은 MemberRepository인 추상에만 의존하면 된다. (구체 클래스는 이제 몰라도 됨)
관심사의 분리 성공
-> 객체를 생성하고 연결하는 역할(AppConfig)과 실행하는 역할(Service)이 명확히 분리되었다.
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
}
}
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService= appConfig.orderService();
}
}
public class MemberServiceTest {
MemberService memberService;
@BeforeEach // 각 테스트 실행 전에 무조건 실행되는 부분
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
}
public class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach // 각 테스트 실행 전에 무조건 실행되는 부분
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
}
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
지금까지 개발내용에서는 이중에 3가지인 SRP, DIP, OCP이 적용된 것이다
1. SRP - 단일 책임 원칙
2. DIP - 의존관계 역전 원칙
- 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다"는 원칙을 다르는 방법이다.
3.OCP - 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
- 애플리케이션을 사용 영역과 구성 영역으로 나누었다.
지금까지 자바로만 작성하였는데 스프링을 사용해보도록 하자.
AppConfig.class를 스프링 기반으로 변경
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
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("findMember = "+findMember.getName());
}
}
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);
// MemberService memberService = new MemberServiceImpl();
// OrderService orderService= new OrderServiceImpl();
Long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 20000);
System.out.println("order = "+ order);
System.out.println("order.calculatePrice= "+order.calculatePrice());
}
}