왜? -> 섹션 2에서 발견한 DIP, OCP를 못 지키는 문제 해결하기 위해
급작스러운 요구사항이 발생되었을 때
저번 편에서 만든 정책에서 인터페이스 추가!
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;
}
}
}
Test코드
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() {
Member member = new Member(1L, "memberVIP", Grade.VIP);
int discount = discountPolicy.discount(member, 10000);
assertThat(discount).isEqualTo(1000); //alt+enter 온디멘드 ~>. static import
}
@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.java에서 new RateDiscoutPolicy()로 변경 => 여기서 객체를 변경하면서 OCP, DIP 위반 ...
왜? -> OrderServiceImple은 추상 의존(DicountPolicy)뿐 아니라 구체클래스에도 의존...(FixDiscountPolicy, RateDiscountPolicy)
DIP위반 !! -> 인터페이스에만 의존하도록 코드를 변경해줘야함 . .```
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
} //이제 널포인터에러 발생ㅋ -> 구현 객체 대신 생성 및 주입 필요
new ~~()가 결국은 하나하나 .. 직접해야하는? 코드임 말 그대로. 개빡셈. 집중이 불가능하므로 역할 분담 필요 !!
객체 생성 및 연결하는 클래스
MemeberServiceImpl.java를 다음과 같이 수정
private final MemberRepository memberRepository ;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
OrderServiceImple.java
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
AppConfig.java
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
}
public OrderService orderService() {
return new OrderServiceImpl(
new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
}
}
=> 생성자 주입 ~~
굿보이 AppConfig~.
다시 정리
AppConfig 객체는 각 객체를 생성하고 그 참조값을 Impl을 생성하면서 생성자로 전달함. -> Impl의 입장에서 DI라고 함.(의존관계 주입)
MemberApp.java
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl; //사용되지 않음 !!
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig= new AppConfig();
MemberService memberService = appConfig.memberService();
//밑은 이전 코드
//MemberService memberService = new MemberServiceImpl(memberRepository);
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());
}
}
OrderApp.java
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import hello.core.order.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
//MemberService memberService = new MemberServiceImpl(memberRepository);
//OrderService orderService = new OrderServiceImpl(memberRepository, discountPolicy);
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);
}
}
MemberServiceTest.java
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(1L);
//then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
OrderServiceTest.java
class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
@Test
void createOrder() {
long memberId = 1L;
Member member = new Member(memberId, "memberA", Grade.VIP);
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 10000);
Assertions.assertThat(order.getDiscountPrice()).isEqualTo(1000);
}
}
-> new 객체 생성은 AppConfig에서만 실행하고 고로 다른 import도 필요 없음 .. 다른 클래스는 그냥 실행만 하고 객체는 AppConfig가 주입해줌.
원래의 AppConfig.java 코드 수정
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());
//return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
}
public OrderService orderService() {
//return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
new MemoryMemberRepository()의 붕복 부분이 제거되어 AppConfig 부분만 변경하면 된다 !
AppConfig.java
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();//원래 FixDiscountPolicy()였음
}
드디어 새로운 할인 정책이 손쉽게 적용 !
DIP위반 문제를 역할 분담을 통해 AppConfig가 의존관계 주입. 또 Refactoring을 통해 중복을 제거하고 역할 명확하게 분리하며 OCP도 완벽하게 지킴 ㅋ
한 클래스는 하나의 책임만 가져야 한다.
AppConfig의 등장으로 구현 객체의 생성과 연결의 책임 분리
클라이언트 객체는 실행만 !
구체화 말고 추상화에 의존해라.
이것 또한 AppConfig가 의존관계 주입하며 해결
확장에는 오픈, 변경에는 클로즈
AppConfig가 객체 인스턴트를 대신 생성하고 주입하면서 클라이언트 코드는 변경하지 않아도 됨 -> 만족
제어의 역전. 자신이 호출 X, 대신 호출해주는 것.
-> AppConfig, 객체는 실행 역할만 담당하게 되면서 제어권이 사라짐
-> Impl또한 로직만 실행하게 됨
<프레임워크 VS 라이브러리
내가 작성한 코드 제어 및 실행(ex.JUnit) VS 코드가 직접 제어 흐름 담당>
의존관계 -> 정적인 클래스 의존 관계 + 실행 시점의 동적인 객체 의존 관계
정적인 클래스 의존관계
클래스가 사용하는 import 코드로 쉽게 파악 가능.
코드 수정 전 기존 코드
동적인 객체 인스턴스 의존관계
런 타임에 외부에서 객체를 생성하고 연결해주어 클라이언트와 서버의 실제 의존관계가 연결이 되는 것
AppConfig 생성 이후의 코드
-> AppConfig와 같이 객체를 생성 및 관리, 의존관계 연결해줌
<의존관계 주입에 초첨 = DI 컨테이너>
어샘블러, 오브젝트 팩토리 ...
AppConfig를 스프링 기반으로 변경 !
package hello.core;
import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
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 설정 구성
@Configuration
public class AppConfig {
//스프링 빈
@Bean
MemberService memberService() {
return new MemberServiceImpl(memberRepository());
//return new MemberServiceImpl(new MemoryMemberRepository()); //직접 할당하지 않도록 하는 부분 !
}
@Bean
public OrderService orderService() {
//return new OrderServiceImpl(new MemoryMemberRepository(),new FixDiscountPolicy()); // 순서 중요
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
MemberApp에서 원래 AppConfig코드 대신
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
로 수정
OrderApp 또한 마찬가지
ApplicationContext applicationContext = new
AnnotationConfigApplicationContext(AppConfig.class);
MemberService memberService =
applicationContext.getBean("memberService", MemberService.class);
OrderService orderService = applicationContext.getBean("orderService",
OrderService.class);
여기서 ApplicationContext는 아까 설명했던 컨테이너.
스프링 사용 전에는 개발자가 직접 AppConfig 통해서 직접 객체 생성 및 의존관계 주입을 했다면 이제는 컨테이너를 대신 돌린다.
//아직은 원리까지 .. 장점은 차차 알아가봅시다!