내가 공부하려고 올리는/스프링

객체 지향 설계와 스프링

결딴력 2022. 2. 1. 20:44
반응형

다형성의 한계

  • 다형성만으로는 OCPDIP를 지킬 수 없다.
  • 다형성을 만족하면서 OCP와 DIP를 동시에 만족하기 위해서는 스프링이 필요하다.
  • 이때 스프링이 제공하는 기술은 다음과 같다.
    • DI(Dependency Injection) : 의존관계, 의존성 주입
    • DI 컨테이너 제공
  • 위의 기술로 클라이언트 코드의 변경 없이 기능을 확장할 수 있다.

 

 

자바 프로젝트 생성

  • 자바만을 사용해 간단한 프로젝트를 만들어 본다.
  • 이후 변경에 유연하게 대처가 가능한지 확인해보고, 스프링을 도입해본다.

 

 

비즈니스 요구사항과 설계

  • 회원
    • 회원을 가입하고 조회할 수 있다
    • 회원은 '일반'과 'VIP' 두 가지 등급이 있다.
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.
  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책이 다르게 적용될 수 있다.
    • 나중에 변경 가능한 사항-> 추후에 할인 정책은 변경될 가능성이 높다.
    • "모든 VIP는 1000원을 할인해주는 고정 금액 할인이 적용된다."

 

 

회원 도메인 설계

- 도메인은 소프트웨어를 설계하는 대상 영역을 의미

  • 회원 도메인 요구사항
    • 회원을 가입하고 조회할 수 있다.
    • 회원은 일반과 VIP 두 가지 등급이 있다.
    • 회원 데이터는 자체 DB를 구축할 수 있고, 외부 시스템과 연동할 수 있다.(미확정)
  • 회원 도메인 협력 관계

    회원 도메인 협력 관계

    • 클라이언트 / 회원 서비스 / 회원 저장소 = 역할
    • 메모리 회원 저장소 / DB 회원 저장소 / 외부 시스템 = 구현

  • 회원 클래스 다이어그램
    회원 클래스 다이어그램

    • 회원 클래스에는 회원 서비스와 회원 저장소에 관한 두 개의 인터페이스가 존재
    • 회원 서비스의 구현 클래스는 'MemberServiceImpl'
    • 회원 저장소의 구현 클래스는 'MemoryMeberRepository', 'DbMemberRepository' 등
    • 회원 저장소의 구현 클래스는 언제든지 변경될 수 있다.

 

 

회원 도메인 설계의 문제점

  • 다음 코드를 보자
public class MemberServiceImpl implements MemberService { 

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    ... 중략
  • MemberServiceImpl은 MemberService 인터페이스를 구현하는 클래스이다.
  • MemberServiceImpl에서는 '회원가입', '회원조회' 기능을 구현해야 한다.
  • 두 기능은 DB와 연결되어야 하는데, 현재 MemoryMeberRepository를 데이터 저장소로 사용하고 있으므로
    데이터 저장소와 연결하기 위해 위와 같이 new 연산자로 구현 클래스를 생성해준다.
  • 이 코드의 문제점은?
    • 외부 시스템이나 다른 데이터 저장소로 구현 객체를 변경하고 싶을 때 OCP 원칙을 준수할 수 있을까?
    • 구현 객체가 아닌 인터페이스에 의존하고 있을까? (DIP를 잘 지키고 있을까?)
  • 위 코드는 데이터 저장소가 바뀌게 되면 구현 객체를 변경해줘야 한다. => OCP 위반
  • 위 코드는 인터페이스(MemberRepository)뿐만아니라 구현 객체도 의존한다.(MemoryMemberRepository)
    => DIP 위반

 

 

주문과 할인 도메인 설계

  • 주문과 할인 정책
    • 회원은 상품을 주문할 수 있다.
    • 회원 등급에 따라 할인 정책을 적용할 수 있다.
    • 할인 정책은 모든 VIP에게 1000원을 고정 할인해주는 정책을 일단 사용한다.
      -> 추후 변경 가능성이 높다.

 

 

주문 도메인 협력, 역할, 책임

주문 도메인 협력 관계

 

  1. 주문 생성 : 클라이언트는 주문 서비스에 주문 생성을 요청
  2. 회원 조회 : 할인을 위해서 회원 등급이 필요하기 때문에 주문 서비스는 회원 저장소에서 회원을 조회
  3. 할인 적용 : 확인된 회원 등급에 따라 할인 정책을 결정
  4. 주문 결과 반환 : 주문 서비스는 할인 결과를 포함한 주문 결과를 반환

 

 

주문 도메인 클래스 다이어그램

주문 도메인 클래스 다이어그램

 

  • OrderService / MemberRepository / DiscountPolicy : 역할 (인터페이스)
  • OrderServiceImpl / MemoryMemberRepository / FixDiscountPolicy 등 : 구현 (구현 클래스)
  • 회원 저장소의 데이터 저장소는 언제든지 변경이 가능하다
  • 할인 정책은 FixDiscountPolicy, RateDiscountPolicy 등으로 언제든지 변경이 가능하다.

 

 

주문 도메인 설계의 문제점

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

... 중략
  • 앞서 회원 도메인 설계의 문제와 비슷하다.
  • 주문 서비스의 구현 객체는 직접 구현 객체를 선택하고 있다. -> DIP 위반
  • 이때 데이터 저장소가 변경되거나 할인 정책이 변경될 경우 OCP를 위반하게 된다.

 

 

새로운 할인 정책 개발

  • 주문한 금액의 '%'를 할인해주는 새로운 정률 할인 정책이 추가되었다고 가정해보자
  • 새로운 할인 정책을 적용하는 과정을 통해 실제 문제점을 눈으로 확인해보자
  • 새로운 구현 객체로 RateDiscountPolicy 클래스를 만들었다고 가정해보자.
  • OrderServiceImpl의 코드는 다음과 같이 변경된다.
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository(); 

    // private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); 
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy(); 

... 중략
  • 문제점
    • 역할과 구현을 충분히 분리했다.
    • 다형성도 활용했다.
    • BUT, OCP, DIP와 같은 객체지향 설계 원칙을 충실히 준수하지 못했다.

 

 

기대했던 의존관계

기대했던 의존관계

  • DIP를 위반하지 않기 위해 인터페이스에만 의존하는 관계

 

 

실제 의존관계

실제 의존관계

  • 실제로는 할인 정책이 변경될 때마다 new 연산자로 직접 할인 정책 구현 클래스를 생성하고 있다.
  • 따라서 DIP가 위반된다.

 

 

할인 정책 변경

할인 정책 변경

  • 할인 정책이 변경되는 순간 클라이언트 쪽 코드인 OrderServiceImpl의 코드를 변경해줘야 한다.
  • 따라서 OCP를 위반한다.

 

 

문제 해결 방법

  • 클라이언트 코드인 OrderServiceImpl은 DiscountPolicy의 인터페이스뿐만 아니라
    구현 클래스도 함께 의존하고 있다.
  • 그래서 구현 클래스를 변경하면 클라이언트 코드도 함께 변경해야만 한다.
  • 따라서 문제를 해결하기 위해서는 인터페이스에만 의존하도록 설계를 변경해야 한다.

 

 

인터페이스에만 의존하도록 코드 변경

public class OrderServiceImpl implements OrderService {
     //private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
     private DiscountPolicy discountPolicy;
  • 인터페이스에만 의존할 수 있도록 구현 객체를 직접 생성하지 않는다.
  • 하지만 구현체가 없는데 어떻게 코드가 실행될 수 있을까?
  • 실제로 이 코드를 실행하면 Null Point Exception이 발생한다.

 

 

해결방안

  • 이러한 문제를 해결하기 위해서는 누군가 대신 구현 객체를 생성하고 주입해줘야 한다.

 

 

다음글에서

  • 다음 글에서 어떻게 대신 구현 객체를 생성하고 주입할 수 있는지 알아보자

 

 

 

 

출처 : 인프런 김영한 님 스프링 강의

반응형