반응형
# 객체 지향 원리 적용_1
## 새로운 할인 정책 개발
- 기존의 고정 금액 할인이 아닌, 주문 금액 당 퍼센트인 정률할인으로 변경.
- 기존에 DiscountPolicy (인터페이스) 의 구현체로 FIxDiscountPolicy 사용하였는데, 정률할인을 구현체인 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;
}
}
}
테스트 코드 생성 단축키.
- 인텔리제이에서 윈도우의 경우 Ctrl + Shift + T 입력 시 Test 생성 팝업 호출됨.
- 아래와 같이 검증 코드에 Alt + Enter 입력 시 (윈도우의 경우) Add static import 하여 사용 가능하다.
RateDiscountPolicyTest
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.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
Assertions.assertThat(discount).isEqualTo(1000);
}
@Test
@DisplayName("VIP가 아니면 할인이 적용되지 않아야 함.")
void vip_x() {
// given
Member member = new Member(1L, "memberBasic", Grade.BASIC);
// when
int discount = discountPolicy.discount(member, 10000);
// then
Assertions.assertThat(discount).isEqualTo(1000);
}
}
## 새로운 할인 정책 적용과 문제점
- OCP 위반 : 현재 작업한 소스에서 할인 정책 변경 시 OrderServiceImpl 의 코드를 고쳐야 함.
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository = new MemoryMemberRepository();
// private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); // 고정할인
private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); // 정률할인
}
- 역할과 구현 분리되었고, 다형성 활용 및 인터페이스와 구현 객체 분리 됨.
- 하지만, OCP, DIP 같은 객체지향 설계 원칙을 준수하지 않음. (문제점)
- DIP 위반 : 주문 서비스 클라이언트 (OrderServiceImpl) 의 경우 DiscountPolicy 인터페이스에 의존하면서 DIP를 지킨 것 같지만, 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존 중.
여기서..
추상(인터페이스) 의존 : DiscountPolicy
구체(구현) 클래스 : FixDiscountPolicy, RateDiscountPolicy
## 관심사의 분리 (DIP, OCP 위반 문제 해결)
- 공연을 예로 들면 배우는 배우의 역할에만 집중, 배우가 공연을 구성하고 다른 배역을 섭외 하는 등의 업무를 해서는 않된다. 배우는 상대 배우가 누구라도 동일한 공연을 할 수 있어야 함. 이를 위해 공연을 구성하고 배역을 섭외하는 역할을 하는 별도의 기획자가 필요. (AppConfig)
AppConfig (중요!!!!) - 객체의 생성&&주입(연결) 담당.
- 구현 객체 생성, 연결하는 책임을 갖는 별도의 설정 클래스.
- 애플리케이션의 실제 동작에 필요한 구현 객체 생성, 생성한 객체 인스턴스의 참조(래퍼런스)를 생성자를 통해 주입(연결) 함.
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
public class MemberServiceImpl implements MemberService {
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
}
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;
}
}
- 위와같이 설계 변경을 함으로써 Impl이 인터페이스에 의존하게 됨.
- 오직 외부(AppConfig)에서 어떤 구현 객체가 주입될지 결정 됨. (DI (Dependency Injection, 의존관계 주입))
MemberApp
- AppConfig 로 의존관계 주입하여 적용.
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();
Member memberA = new Member(1L, "memberA", Grade.VIP);
memberService.join(memberA);
Member findMember = memberService.findMember(1L);
System.out.println("member : " + memberA.getName());
System.out.println("findMember : " + findMember.getName());
}
}
OrderApp
- AppConfig 로 의존관계 주입하여 적용.
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();
// OrderService orderService = new OrderServiceImpl();
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.toString());
}
}
MemberServiceTest
- AppConfig 로 의존관계 주입하여 적용.
- @BeforeEach : 실행 전 실행됨.
package hello.core.member;
import hello.core.AppConfig;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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(1L);
// then
Assertions.assertThat(member).isEqualTo(findMember);
}
}
OrderServiceTest
- AppConfig 로 의존관계 주입하여 적용.
- @BeforeEach : 실행 전 실행됨.
public class OrderServiceTest {
MemberService memberService;
OrderService orderService;
@BeforeEach
void beforeEach() {
AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();
}
}
## AppConfig 리팩터링
- 중복 및 역할에 따른 구현이 잘 안보이는 현상 발생하여 이를 개선.
기존 AppConfig
public class AppConfig {
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
}
}
변경 AppConfig
- new MemoryMemberRepository() 중복 제거.
- 역할과 구현 클래스가 한눈에 보이도록 분리.
public class AppConfig {
// memberService 역할
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// memberRepository 역할
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
// orderService 역할
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// discountPolicy 역할
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
## 새로운 구조와 할인 정책 적용
- 기존 정액할인 적용 -> 정률할인 정책 적용으로 변경.
- 정책 변경을 위해 AppConfig 에서만 변경해주면 됨. -> AppConfig로 클라이언트 코드에 대한 변경 없이 정책 변경 가능해짐. (사용영역 전혀 변경할 필요 없음)
public class AppConfig {
// memberService 역할
public MemberService memberService() {
return new MemberServiceImpl(memberRepository());
}
// memberRepository 역할
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
// orderService 역할
public OrderService orderService() {
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
// discountPolicy 역할
public DiscountPolicy discountPolicy() {
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
반응형
'인프런 강의 학습 > 스프링 핵심 원리(기본편)' 카테고리의 다른 글
재학습_스프링 컨테이너와 스프링 빈_1 (0) | 2022.09.07 |
---|---|
재학습_객체 지향 원리 적용_2 (0) | 2022.09.04 |
재학습_예제 프로젝트 진행_2 (0) | 2022.08.27 |
재학습_예제 프로젝트 진행_1 (0) | 2022.08.24 |
재학습_객체 지향 설계와 스프링 (0) | 2022.08.23 |