[정리]
클라이언트가 주문서비스에 주문 생성하고 그 주문서비스가 회원조회, 할인 적용해서 결과물을 클라이언트에 반환.
[할인 정책 추가&테스트]
<RateDiscountPolicy>
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;
}
}
}
<RateDiscountPolicyTest> - RateDiscountPolicy가 10%할인 되는 지 테스트
class RateDiscountPoicyTest {
RateDiscountPoicy discountPoicy=new RateDiscountPoicy();
@Test
@DisplayName("VIP는10%할인이 적용됨")
void vip_o(){
//given 멤버 만들고
Member member= new Member(1L,"memberVIP", Grade.VIP);
//when
int discount=discountPoicy.discount(member,10000);
//then 결과
assertThat(discount).isEqualTo(1000); //alt+emter로 static method로 만들어주는게 좋아=>Assertions.assertThat(discount).isEqualTo(1000);가
}
@Test
@DisplayName("VIP가 아니면 할인이 적용 안됨")
void vip_x(){
//given 멤버만들고
Member member= new Member(2L,"memberBASIC", Grade.BASIC);
//when
int discount=discountPoicy.discount(member,10000);
//then
assertThat(discount).isEqualTo(0);
}
}
[OCP, DI 위반]
이제 할인정책을 변경하려면 클라이언트인 OrderServiceImpl 코드를 고쳐야 함
public class OrderServiceImpl implements OrderService {
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
}
private final MemberRepository memberRepository = new MemoryMemberRepository();
OrderServiceImpl이 직접 객체를 생성하고 구체까지 선택해서 discountPolicy에 할당함
추상(인터페이스) 의존: DiscountPolicy
구체(구현) 클래스: FixDiscountPolicy , RateDiscountPolicy
역할과 구현을 충실하게 분리 OK
다형성도 활용하고, 인터페이스와 구현 객체를 분리 OK
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수X
DIP 위반 추상에만 의존하도록 변경(인터페이스에만 의존)
인터페이스에만 의존하도록 설계를 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
BUT NPE(null pointer exception)가 발생구현체가 없는데 어떻게 코드를 실행할 수 있을까?
누군가가 클라이언트인 OrderServiceImpl 에 DiscountPolicy 의 구현 객체를 대신 생성하고 주입
[관심사의 분리]
OrderServiceImpl에서 DiscountPolicy를 FixDiscountPolicy로 하겠다고 직접 객체를 생성하고 선택.
환경구성에 관한 것은 AppConfig가 다 함. 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결)
객체에 대한 의존관계를 외부에서(appConfig) 넣어줌
<AppConfig> - 전체를 설정하고 구성, 구현 객체 생성하고 생성한 객체 참조를 생성자를 통해 주입
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
<MemberServiceImpl>
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
public void join(Member member) {
memberRepository.save(member);
}
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
}
MemoryRepository에 뭐가 들어갈지를 생성자 만들어서 생성자를 통해서 구현체가 뭐가 들어갈지..
memberservice를 불러다 쓸 때 memberservice구현 객체가 생성되고
: MemoryRepository의 구현체에 뭐가 들어갈지 생성자를 통해서 객체가 들어감 -> 생성자 주입
이제 인터페이스(추상화)에만 의존 DIP잘 지킴
MemberServiceImpl 은 MemoryMemberRepository 를 의존하지 않는다!
단지 MemberRepository 인터페이스만 의존한다. MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
MemberServiceImpl 의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부( AppConfig )에서 결정된다. MemberServiceImpl 은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다
1. AppConfig가 MemberService객체를 만들 때 MemoryMemberRepository 를 생성
2.MemberServiceImpl 를 생성할 때 MemoryMemberRepository 의 참조값 001을 생성자에 같이 넘겨
[AppConfig 사용]
<MemberApp> - AppConfig를 이용해서
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService(); //appConfig가 memberService달라하면 memberService의 인터페이스를 줘
//memberService에는 memberServiceImpl이 있어
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>
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new 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);
}
}
<MemberServiceTest>
class MemberServiceTest {
MemberService memberService;
//테스트 실행 전에 다 항상 실행
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
}
class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
public void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
}
[리팩토링]
역할이 드러나게 하는 AppCofig 리팩토링
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();
return new RateDiscountPolicy();
}
}
멤버서비스 역할, 리포지터리 역할, 오더서비스 역할 다 드러남
[스프링으로 전환]
<AppConfig>
@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();
}
}
@Configuration : 설정정보,구성정보 담당
@Bean : 스프링 컨테이너에 등록됨
<MemberApp> - AppConfig에서 스프링 사용 버전으로 변경하기
public class MemberApp {
public static void main(String[] args) {
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());
}
}
applicationContext : 스프링 컨테이너. (이전에는 직접 객체 생성하고 DI했지만 스프링컨테이너를 통헤 사용 )
모든 객체(@Bean) 관리, @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록
스프링 빈: 스프링 컨테이너에 등록된 객체
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
AppConfig에 있는 환경 설정 정보를 가지고 스프링이 @Bean들을 스프링 빈에 객체 생성한걸 집어 넣어서 관리
->MemberService memberService = applicationContext.getBean("memberService", MemberService.class);스프링 컨테이너를 통해서 이 객체를 찾아와 찾을 객체이름 "메서드 이름", 타입
찾을 때는 스프링 컨테이너를 통해서 가져와. 스프링이 환경정보를 가지고 필요한 것들 읽어서 관리하고 나중에 스프링 컨테이너에서 거내 써
'Spring 강의 > springMVC' 카테고리의 다른 글
springMVC 기본 - (5) 싱글톤 컨테인너 (0) | 2022.03.11 |
---|---|
springMVC 기본 - (4) 스프링 컨테이너와 빈 (0) | 2022.03.10 |
springMVC - IoC, DI, 컨테이너 (0) | 2022.03.09 |
springMVC 기본 - (2) 회원 도메인 (0) | 2022.03.09 |
springMVC 기본 - (1) 좋은 객체 지향 설계와 스프링 (0) | 2022.03.06 |