스프링 핵심 원리 - 기본편
https://www.inflearn.com/course/스프링-핵심-원리-기본편/dashboard
섹션 3. 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
3.1 새로운 할인 정책 개발
새로운 할인 정책 확장
RateDiscountPolicy 추가
RateDiscountPolicy 코드 추가
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: cmd + shift + t
}
}
테스트 작성
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% 할인이 적용되어야 한다.") // JUnit5부터 지원
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
//Assertions.assertThat(discount).isEqualTo(1000);
assertThat(discount).isEqualTo(0);
}
}
3.2 새로운 할인 정책 적용과 문제점
할인 정책을 애플리케이션에 적용
//수정 전
private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
//수정 후
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
문제점 발견
왜 클라이언트 코드를 변경해야 할까?
기대했던 의존관계
실제 의존관계
정책 변경
어떻게 문제를 해결할 수 있을까?
인터페이스에만 의존하도록 설계 변경
인터페이스에만 의존하도록 코드 변경
public class OrderServiceImpl implements OrderService {
//private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
private DiscountPolicy discountPolicy;
}
해결방안
3.3 관심사의 분리
관심사 분리
AppConfig 등장
AppConfig
MemberService 코드, MemberServiceImpl - 생성자 주입
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository()); // 생성자를 통해 들어감 MemberServiceImpl.java에서 확인
}
}
public class MemberServiceImpl implements MemberService {
//private final MemberRepository memberRepository = new MemoryMemberRepository();
private final MemberRepository memberRepository;
// MemberRepository라는 인터페이스만 존재, MemoryMemberRepository X, 추상화에만 의존, DIP를 지킴, 구체적인 것은 MemberServiceImpl은 모름
// => 생성자 주입
public MemberServiceImpl(MemberRepository memberRepository) {
// 생성자를 통해 memberRepository의 구현체를 설정
this.memberRepository = memberRepository;
}
}
그림 - 클래스 다이어그램
그림 - 회원 객체 인스턴스 다이어그램
OrderService 코드, OrderServiceImpl - 생성자 주입
public class AppConfig {
public OrderService orderService() {
// 생성자 주입
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
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;
}
}
AppConfig 전체 코드
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.Order;
import hello.core.order.OrderService;
import hello.core.order.OrderServiceImpl;
public class AppConfig {
// 애플리케이션 전체를 설정하고 구성
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository()); // 생성자를 통해 들어감 MemberServiceImpl.java에서 확인
}
public OrderService orderService() {
// 생성자 주입
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
AppConfig 실행
사용 클래스 - MemberApp
public class MemberApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService(); //MemberServiceImpl이 들어감
}
}
사용 클래스 - OrderApp
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();
}
}
테스트 실행
정리
3.4 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;
public class AppConfig {
// 애플리케이션 전체를 설정하고 구성
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); // 생성자를 통해 들어감 MemberServiceImpl.java에서 확인
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService() {
// 생성자 주입
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
public DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
3.5 새로운 구조와 할인 정책 적용
그림 - 사용, 구성의 분리
그림 - 할인 정책의 변경
할인 정책 변경 구성 코드
public DiscountPolicy discountPolicy() {
//return new FixDiscountPolicy(); // 변경 전
return new RateDiscountPolicy(); // 변경 후
}
3.6 전체 흐름 정리
새로운 할인 정책 개발
새로운 할인 정책 적용과 문제점
관심사의 분리
AppConfig 리팩터링
새로운 구조와 할인 정책 적용
3.7 좋은 객체 지향 설계의 5가지 원칙의 적용
SRP 단일 책임 원칙: 한 클래스는 하나의 책임만 가져야 한다.
DIP 의존관계 역전 원칙: 프로그래머는 추상화에 의존해야 하며, 구체화에 의존하면 안된다.
OCP: 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
3.8 IoC, DI, 그리고 컨테이너
제어의 역전 IoC (Inversion of Control)
프레임워크 vs. 라이브러리
의존관계 주입 DI (Dependency Injection)
정적인 클래스 의존관계
클래스 다이어그램
동적인 객체 인스턴스 의존 관계
객체 다이어그램
IoC 컨테이너, DI 컨테이너
3.9 스프링으로 전환하기
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 // 설정정보, 애플리케이션의 구성정보
public class AppConfig {
// 애플리케이션 전체를 설정하고 구성
@Bean // 스프링 컨테이너에 등록됨
public MemberService memberService() {
return new MemberServiceImpl(memberRepository()); // 생성자를 통해 들어감 MemberServiceImpl.java에서 확인
}
@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();
}
}
MemberApp에 스프링 컨테이너 적용
package hello.core;
import hello.core.member.Grade;
import hello.core.member.Member;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
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(); //MemberServiceImpl이 들어감
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
//AppConfig에 있는 환경 설정 정보를 갖고, @Bean 붙은 것을 스프링 컨테이너에 넣고 관리해줌
MemberService memberService = applicationContext.getBean("memberService", MemberService.class);// (이름, 반환타입)
//MemberService memberService = new 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에 스프링 컨테이너 적용
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;
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); //VIP 회원 생성
memberService.join(member);
Order order = orderService.createOrder(memberId, "itemA", 20000); //"itemA" 주문
System.out.println("order = " + order); // toString에 있는 것 출력
}
}
스프링 컨테이너
FIN.
[스프링 핵심 원리 - 기본편] week11 (0) | 2022.10.12 |
---|---|
[스프링 핵심 원리 - 기본편] week10 (0) | 2022.10.04 |
[스프링 핵심 원리 - 기본편] week08 (0) | 2022.09.25 |
[스프링 핵심 원리 - 기본편] week07 (0) | 2022.09.18 |
[스프링 핵심 원리 - 입문] week06 (0) | 2022.09.11 |
댓글 영역