RateDiscountPolicy
package sangyunpark.core.member.discount;
import sangyunpark.core.member.Grade;
import sangyunpark.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;
}
}
}
Test
package sangyunpark.core.member.discount;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
class RateDiscountPolicyTest {
DiscountPolicy discountPolicy = new RateDiscountPolicy();
@Test
@DisplayName("VIP는 10% 할인이 적용되어야 한다")
void vip_o(){ // 오류 발생 x
// given
Member member = new Member(1L,"박상윤", Grade.VIP);
// when
int discount = discountPolicy.discount(member,10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 한다.")
void vip_x(){ // 오류 발생 o
// given
Member member = new Member(1L,"박상윤", Grade.BASIC);
// when
int discount = discountPolicy.discount(member,10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
}
package sangyunpark.core.order;
import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.discount.FixDiscountPolicy;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.core.member.MemoryMemberRepository;
import sangyunpark.core.member.discount.RateDiscountPolicy;
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
@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);
}
}
실제 의존 관계
정책 변경
package sangyunpark.core.order;
import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.core.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
private DiscountPolicy discountPolicy; // null point exception 발생
@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);
}
}
package sangyunpark.core;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.member.MemberServiceImpl;
import sangyunpark.core.member.MemoryMemberRepository;
import sangyunpark.core.member.discount.FixDiscountPolicy;
import sangyunpark.core.order.OrderService;
import sangyunpark.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 sangyunpark.core.member;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository; // 추상화에만 의존
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) { // 회원 가입
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) { // 회원 찾기
return memberRepository.findById(memberId);
}
}
package sangyunpark.core.order;
import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.core.member.MemoryMemberRepository;
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);
}
}
package sangyunpark.core;
import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberService;
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
Member member = new Member(1L, "memberA", Grade.VIP);
memberService.join(member);
Member findMember = memberService.findMember(1L);
System.out.println("newMember = " + member.getName());
System.out.println("find Member = " + findMember.getName());
}
}
package sangyunpark.core;
import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.order.Order;
import sangyunpark.core.order.OrderService;
public class OrderApp {
public static void main(String[] args) { // Member, Order test
AppConfig appConfig = new AppConfig(); // appConfig 선언
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
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);
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}
Test해보기
MemberServiceTest
package sangyunpark.core.member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sangyunpark.core.AppConfig;
public class MemberServiceTest {
MemberService memberService;
@BeforeEach
public void beforeEach(){
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
@Test
void join(){
//given
Member member = new Member(1L, "memberA", Grade.VIP);
//when
memberService.join(member);
Member findMember = memberService.findMember(member.getId());
//then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
OrderServiceTest
package sangyunpark.core.order;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import sangyunpark.core.AppConfig;
import sangyunpark.core.member.*;
public class OrderServiceTest {
OrderService orderService;
MemberService memberService;
@BeforeEach
public void beforeEach(){
AppConfig appConfig = new AppConfig();
orderService = appConfig.orderService();
memberService = appConfig.memberService();
}
@Test
void createOrder(){
Long memberId = 1L;
Member member = new Member(memberId,"박상윤", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "상품 1", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
// VIP 인경우 1000원
}
}
package sangyunpark.core;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.member.MemberServiceImpl;
import sangyunpark.core.member.MemoryMemberRepository;
import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.discount.FixDiscountPolicy;
import sangyunpark.core.order.OrderService;
import sangyunpark.core.order.OrderServiceImpl;
public class AppConfig { // 역할과 구현을 확실하게 해준다.
public MemberService memberService(){
return new MemberServiceImpl(memberRepository()); // 생성자 주입
}
private MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
public OrderService orderService(){ // 구체적인 방식은 여기서 선택
return new OrderServiceImpl(new MemoryMemberRepository(), discountPolicy()); // 생성자 주입
}
}
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
한 클래스는 하나의 책임만 가져야 한다.
프로그래머는 추상화에 의존해야하지, 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다.
소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
순수한 Java 코드만으로 DI를 적용했던 부분을 스프링을 사용해서 구현
package sangyunpark.core;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sangyunpark.core.member.MemberRepository;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.member.MemberServiceImpl;
import sangyunpark.core.member.MemoryMemberRepository;
import sangyunpark.core.member.discount.DiscountPolicy;
import sangyunpark.core.member.discount.RateDiscountPolicy;
import sangyunpark.core.order.OrderService;
import sangyunpark.core.order.OrderServiceImpl;
@Configuration
public class AppConfig { // 역할과 구현을 확실하게 해준다.
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository()); // 생성자 주입
}
@Bean
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public OrderService orderService(){ // 구체적인 방식은 여기서 선택
return new OrderServiceImpl(new MemoryMemberRepository(), discountPolicy()); // 생성자 주입
}
}
package sangyunpark.core;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberService;
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("newMember = " + member.getName());
System.out.println("find Member = " + findMember.getName());
}
}
package sangyunpark.core;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import sangyunpark.core.member.Grade;
import sangyunpark.core.member.Member;
import sangyunpark.core.member.MemberService;
import sangyunpark.core.order.Order;
import sangyunpark.core.order.OrderService;
public class OrderApp {
public static void main(String[] args) { // Member, Order test
// 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",20000);
System.out.println("order = " + order);
System.out.println("order.calculatePrice = " + order.calculatePrice());
}
}