[Spring] 순수 자바 코드 주문 할인 프로그램 설계

    반응형

    * 다음 포스팅은 인프런 김영한 강사님의 "스프링 핵심 원리 - 기본편" Section2" 강의를 요약 정리한 글 이기에, 대부분의 구현 코드가 생략 되어 있습니다. 자세한 내용은 다음 유료 강의를 참고해 주세요.

     

    스프링 핵심 원리 - 기본편 | 김영한 - 인프런

    김영한 | 스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보

    www.inflearn.com


    #목차

    #0 비즈니스 요구사항 및 설계

    #1 회원 정책 설계

    #2 주문 도메인 설계 

    #3 해당 코드의 문제점 

    #4 해결방안

    - DI : Dependency Injection


    #0 비즈니스 요구사항 및 설계

    -회원 정책

    1. 회원 가입 및 조회 기능이 있다. 

    2. 회원 등급은 vip와 일반 등급으로 나뉜다.

    3. 회원 데이터를 저장할 데이터베이스는 외부 시스템과 연동 가능하며 아직 정해지지 않은 상태이다. (추 후 변경 가능)

    -주문 및 할인 정책

    1. 회원 등급에 따라 할인 정책이 달라진다.

    2. 모든 VIP 고객은 1000원을 할인해 주는 "고정 할인 정책"을 적용한다. (추 후 변경 가능)

     

    요구사항을 분석해 보면 회원 데이터를 저장할 DB와 할인 정책 부분이 아직 정해지지 않은 상태이다. 

    따라서 객체지향 설계 방법론을 이용해 데이터베이스 인터페이스와 할인 정책 관련 인터페이스를 생성한 뒤,

    언제든지 변경 가능한 구현체를 구현하는 방식으로 진행한다.

     


    #1 회원 정책 설계

    -회원 정책 다이어그램 & 클래스 다이어그램

     

    Grade - 등급 정보를 정의한 Enum

    Member - 고객 정보를 가지는 객체

    MemberRepository - 회원 DB 인터페이스

    public interface MemberRepository {
        void save(Member member);
        Member findById(Long memberId);
    }

     

    MemberService -"회원 가입" & "회원 조회" 기능을 포함하는 인터페이스

    public interface MemberService {
        void join(Member member);
        Member findMember(Long memberId);
    }

     

     

    MemoryMemberRepository - MemberRepository 인터페이스를 구현한 메모리 회원 저장소 

     

    MemberServiceImpl - MemberService 인터페이스를 구현한 서비스 클래스

    public class MemberServiceImpl implements MemberService{
        public MemberServiceImpl(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
        ...
    }

     


    #2 주문 도메인 설계 

    -회원 정책 다이어그램 & 클래스 다이어그램

     

    * 할인 정책

    DiscountPolicy : 할인 정책과 관련된 인터페이스 

    FixDiscountPolicy : 고정 할인 정책 관련 구현부 

     

    * 주문

    Order : 주문 관련 정보 (회원 id, 상품명, 상품 가격, 할인 가격) 를 포함하고 있는 엔티티 클래스

     

    OrderService : 주문 서비스 역할 인터페이스

    public interface OrderService {
        Order createOrder(Long memberId, String itemName, int itemPrice);
    }

     

    OrderServiceImpl : 주문 서비스 구현체

    public class OrderServiceImpl implements OrderService{
    
        private final MemberRepository memberRepository = new MemoryMemberRepository();
        private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
        
        ...
    
        @Override
        public Order createOrder(Long memberId, String itemName, int itemPrice) {
            ...
        }
    }

    #3 해당 코드 문제점

    할인 정책을 "고정 할인 정책" (FixDiscountPolicy) 에서 "비율 할인 정책" (RateDiscountPolicy) 으로 변경하는 상황을 가정

     

    DiscountPolicy Interface 의 참조를 생성하는 부분은 OrderServiceImpl 구현체 안에 있다.

    따라서 할인 정책을 변경하기 위해서는 OrderServiceImpl 코드에 직접 접근하여, 수정해 주어야 한다.

    이 코드는 DIP 의존관계 역전 원칙을 위반한 코드이다.

    public class OrderServiceImpl implements OrderService{
    
        private final MemberRepository memberRepository = new MemoryMemberRepository();
        // private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
        private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
        ...
    
        @Override
        public Order createOrder(Long memberId, String itemName, int itemPrice) {
            ...
        }
    }

     

    DIP란?

     프로그래머는 "구체화" (구현 클래스) 에 의존하면 안되고 "추상화" (인터페이스) 에 의존해야 한다는 SOLID 원칙

     

    즉, 주문 도메인 클래스 다이어그램의 설계 의도와 달리

    OrderServiceImpl 구현체가 DiscountPolicy 인터페이스 에만 의존하는 것이 아닌,

    FixDiscountPolicy, RateDiscountPolicy 와 같은 구현체에도 의존하고 있는 상황 이라는 것이다.

     

     


    #4 해결방안

    따라서 클라이언트인 OrderServiceImpl에 DiscountPolicy, MemberRepository 의 구현 객체 (MemoryMemberRepository & FixDiscountPolicy) 를 대신 생성한 뒤 주입해 줄 수 있는 별도의 클래스를 구현 해야 한다.

    이 역할을 AppConfig 클래스가 수행한다.

     

    -AppConfig

    아래 코드와 같이 AppConfig 클래스에서 OrderServiceImpl 구현체를 생성과 동시에

    DiscountPolicy, MemberRepository 의 구현체인 MemoryMemberRepository & FixDiscountPolicy 의 참조를 이어준다.

    public class AppConfig {
      	...
        public OrderService orderService() {
            return new OrderServiceImpl(new MemoryMemberRepository(), new FixDiscountPolicy());
        }
    }

     

     

    -OrderServiceImpl

    public class OrderServiceImpl implements OrderService{
        private final MemberRepository memberRepository ;
        private final DiscountPolicy discountPolicy;
    
        // DI : Dependency Injection 의존성 주입 
        public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
            this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
    
        @Override
        public Order createOrder(Long memberId, String itemName, int itemPrice) {
            ...
        }
    }

     

    -OrderApp

    public class OrderApp {
        public static void main(String[] args) {
            AppConfig appConfig = new AppConfig();
            OrderService orderService = appConfig.orderService();
            ...
        }
    }

     

     

    #Dependency Injection

    이처럼 appConfig 클래스에서 구현 객체 (OrderServiceImpl & MemoryMemberRepository FixDiscountPolicy)를 생성한 뒤 연결해 주는 역할을 수행하게 하면, OrderServiceImpl은 구현체가 아닌 인터페이스 만을 참조하게 되어 DIP 원칙을 준수하는 코드를 작성할 수 있다. 

    이렇게 구현체를 생성한 객체의 참조를 생성자를 통해 생성과 동시에 연결 (주입) 해 주는 기법을 DI Dependency Injection 의존관계 주입 이라고 한다. 

    반응형

    댓글

    Designed by JB FACTORY