[Spring] 주문과 할인 도메인 설계
1. OrderService 인터페이스를 생성하여 Impl에서 받게 할 것이다
2. Impl 에서 Member 정보를 저장한 interface 와 할인 정책을 가지고 있는 Discount interface에 따를 것이다.
주문과 할인 도메인 개발
discount 개발
package hello.springbasic.discount;
import hello.springbasic.member.Member;
public interface DiscountPolicy {
//return은 할인 대상 금액
int discount(Member member, int price);
}
할인을 하기 위해서는 price와 member가 필요하다.
package hello.springbasic.discount;
import hello.springbasic.member.Grade;
import hello.springbasic.member.Member;
public class FixdiscountPolicy implements DiscountPolicy {
private int discountFixAmount = 1000;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return discountFixAmount;
}
else {
return 0;
}
}
}
고정할인은 1000원으로 고정하고,
만약 맴버의 등급이 vip라면 할인율은 1000원으로 내보낸다
그렇지 않을 경우에는 할인율은 없다.
package hello.springbasic.order;
import hello.springbasic.member.Member;
public class Order {
private Long memberId;
private String ItemName;
private int ItemPrice;
private int discountPrice;
public Order(Long memberId, String itemName, int itemPrice, int discountPrice) {
this.memberId = memberId;
this.ItemName = itemName;
this.ItemPrice = itemPrice;
this.discountPrice = discountPrice;
}
//계산된 결과
public int calculatePrice(int discountPrice) {
this.discountPrice = discountPrice;
return discountPrice;
}
//toString을 사용하여 보기 편하게 반환한다
@Override
public String toString() {
return "Order{" +
"memberId=" + memberId +
", ItemName='" + ItemName + '\'' +
", ItemPrice=" + ItemPrice +
", discountPrice=" + discountPrice +
'}';
}
public Long getMemberId() {
return memberId;
}
public void setMemberId(Long memberId) {
this.memberId = memberId;
}
public String getItemName() {
return ItemName;
}
public void setItemName(String itemName) {
ItemName = itemName;
}
public int getItemPrice() {
return ItemPrice;
}
public void setItemPrice(int itemPrice) {
ItemPrice = itemPrice;
}
public int getDiscountPrice() {
return discountPrice;
}
public void setDiscountPrice(int discountPrice) {
this.discountPrice = discountPrice;
}
}
주문 페이지에서는 고객의 멤버아이디, 고객이름, 주문한 아이템, 아이템의 가격을 가지고 온다.
OrderService interface
package hello.springbasic.order;
public interface OrderService {
//클라이언트는 주문을 생성할 때, 회원id,상품명,상품가격을 넘겨야한다.
//주문서비스에서는 2. 회원 조회, 3. 할인적용 의 역할을 위임할 것이다.
Order createOrder(Long memberId, String itemName, int itemPrice);
}
OrderImpl
package hello.springbasic.order;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.member.Member;
import hello.springbasic.member.MemberRepository;
import hello.springbasic.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
//회원을 찾기 위해 repository에서 정보를 가져와야 한다.
private final MemberRepository memberRepository = new MemoryMemberRepository();
private final DiscountPolicy discountPolicy = new FixdiscountPolicy();
@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);
}
}
id을 찾고, discountPolicy을 이용하여 할인가격을 받아온다.
그렇다면 요구사항이 변경되어서 할인율을 정률 할인정책으로 분리해볼 것이다.
RatediscountPolicy 을 생성하여 vip일 경우 10% 할인이 적용되도록 구현할 것이다.
package hello.springbasic.discount;
import hello.springbasic.member.Grade;
import hello.springbasic.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;
}
}
}
새로운 할인정책 적용해보기
package hello.springbasic.order;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.discount.RatediscountPolicy;
import hello.springbasic.member.Member;
import hello.springbasic.member.MemberRepository;
import hello.springbasic.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
//회원을 찾기 위해 repository에서 정보를 가져와야 한다.
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);
}
}
OrderServiceimpl 에서 객체 fixdiscountPolicy 을 RatediscountPolicy로 고치면된다.
하지만 이는 Orderserviceimpl는 fix와 rate에 의존된 것으로 보인다.
원했던 의존관계
실제 의존관계
이는 DIP 을 위반한 것이다.
DIP(의존성 역전 원칙, Dependency Inversion Principle)은 SOLID 원칙 중 하나로,
상위 모듈(추상적인 것)이 하위 모듈(구체적인 것)에 의존하면 안 된다는 원칙
즉, "구체적인 구현(Concrete Class)에 의존하지 말고, 추상적인 인터페이스(Interface, 추상 클래스)에 의존해라!"
💡 쉽게 말해, **"유연한 코드"**를 만들기 위한 원칙이다.
결국은 인터페이스에만 의존하게 해야하는 것이다!
해결하는 방법:
package hello.springbasic.order;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.discount.RatediscountPolicy;
import hello.springbasic.member.Member;
import hello.springbasic.member.MemberRepository;
import hello.springbasic.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
//회원을 찾기 위해 repository에서 정보를 가져와야 한다.
private final MemberRepository memberRepository = new MemoryMemberRepository();
private 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);
}
}
new 을 없애버리고 인터페이스만 의존하게 한다.
그러면 nullpointexception발생한다.
하지만 인터페이스에는 값이 할당이 안되어있다. 인터페이스부분을 어떻게 변경해야할까?
인터페이스의 구현객체를 대신 생성하고 주입해줘야한다.
AppConfing 등장
공연 기획자같이 FixDiscount, RateDiscount 중에 무엇을 선택할지를 OrderService 가 선택하는 것은 안된다.
AppConfig을 사용하여 이 클래스가 여기서 FixDiscount, RateDiscount 중 어느걸 사용할지 선택 후에
Orderservice는 그저 실행해주는 역할을 하도록 해야한다.
package hello.springbasic;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.member.MemberService;
import hello.springbasic.member.MemberServiceImpl;
import hello.springbasic.member.MemoryMemberRepository;
import hello.springbasic.order.OrderService;
import hello.springbasic.order.OrderServiceImpl;
public class AppConfig {
//생성자를 주입한다
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
//orderserviceImpl은 두개를 받고 있는데,
return new OrderServiceImpl(new MemoryMemberRepository(), new FixdiscountPolicy());
}
}
package hello.springbasic.order;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.discount.RatediscountPolicy;
import hello.springbasic.member.Member;
import hello.springbasic.member.MemberRepository;
import hello.springbasic.member.MemoryMemberRepository;
public class OrderServiceImpl implements OrderService {
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//회원을 찾기 위해 repository에서 정보를 가져와야 한다.
private final MemberRepository memberRepository ;
private final DiscountPolicy discountPolicy;
//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 hello.springbasic;
import hello.springbasic.member.Grade;
import hello.springbasic.member.Member;
import hello.springbasic.member.MemberService;
import hello.springbasic.order.Order;
import hello.springbasic.order.OrderService;
import hello.springbasic.order.OrderServiceImpl;
import hello.springbasic.AppConfig;
public class OrderApp {
public static void main(String[] args) {
AppConfig appConfig = new AppConfig();
MemberService memberService = appConfig.memberService();
OrderService orderService = appConfig.orderService();
Member member = new Member("맴버a" , Grade.VIP, 1L);
memberService.join(member);
Order order = orderService.createOrder(1L,"음료" , 5000);
System.out.println(order);
}
}
package hello.springbasic;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.member.MemberService;
import hello.springbasic.member.MemberServiceImpl;
import hello.springbasic.member.MemoryMemberRepository;
import hello.springbasic.order.OrderService;
import hello.springbasic.order.OrderServiceImpl;
public class AppConfig {
//생성자를 주입한다
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
//orderserviceImpl은 두개를 받고 있는데,
return new OrderServiceImpl(new MemoryMemberRepository(), new FixdiscountPolicy());
}
public DiscountPolicy discountPolicy() {
return new FixdiscountPolicy();
}
}
AppConfig를 보면 역할과 구현 클래스가 한눈에 들어온. 그래서 애플리케이션 전체 구성이 어떻게 되어있는지 빠르게 파악할 수 있다.
그러면 이제 FixCountPolicy 에서 RatecountPolicy 로 바꿀 것이다.
이제 어느부분만 바꾸면 될까?
이제 AppConfig만 수정하면 된다.
package hello.springbasic;
import hello.springbasic.discount.DiscountPolicy;
import hello.springbasic.discount.FixdiscountPolicy;
import hello.springbasic.discount.RatediscountPolicy;
import hello.springbasic.member.MemberService;
import hello.springbasic.member.MemberServiceImpl;
import hello.springbasic.member.MemoryMemberRepository;
import hello.springbasic.order.OrderService;
import hello.springbasic.order.OrderServiceImpl;
public class AppConfig {
//생성자를 주입한다
public MemberService memberService() {
return new MemberServiceImpl(new MemoryMemberRepository());
}
public OrderService orderService() {
//orderserviceImpl은 두개를 받고 있는데,
return new OrderServiceImpl(new MemoryMemberRepository(), new FixdiscountPolicy());
}
public DiscountPolicy discountPolicy() {
// return new FixdiscountPolicy(); 이전 할인 정책
return new RatediscountPolicy();
}
}
return부분만 수정하여 클라이언트 코드를 안바꿔도 된다. 사용영역은 변경할 필요가 없다.