Spring(기본) - 핵심원리 이해하기 객체 지향 원리 적용(1)

Kwon Yongho·2023년 4월 3일
0

Spring

목록 보기
10/37
post-thumbnail

스프링 핵심 원리 이해2 - 객체 지향 원리 적용

  1. 새로운 할인 정책 개발
  2. 새로운 할인 정책 적용과 문제점
  3. 관심사의 분리
  4. AppConfig 리팩터링
  5. 새로운 구조와 할인 정책 적용
  6. 전체 흐름 정리
  7. 좋은 객체 지향 설계의 5가지 원칙의 적용
  8. IoC, DI, 그리고 컨테이너
  9. 스프링으로 전환하기

1. 새로운 할인 정책 개발

주문한 금액의 %를 할인해주는 새로운 정률 할인 정책을 추가!

RateDiscountPolicy 코드 추가

package com.example.springex1.discount;

import com.example.springex1.member.Grade;
import com.example.springex1.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;
        }
    }
}

결과

2. 새로운 할인 정책 적용과 문제점

java/com/example/springex1/order/OrderServiceImpl.java

//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); --> 아래 코드로 변경
    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
  • 역할과 구현을 충실하게 분리 --> OK
  • 다형성 활용, 인터페이스와 구현 객체 분리 --> OK

문제점
OCP, DIP 같은 객체지향 설계 원칙을 충실히 준수?

  • DIP: 주문서비스 클라이언트(OrderServiceImpl)는 추상(인터페이스) 뿐만 아니라 구체(구현) 클래스에도 의존하고
    있다.
    • 추상(인터페이스) 의존 : DisCountPolicy
    • 구체(구현) 클래스 : FixDiscountPolicy, RateDiscountPolicy
  • 지금 코드는 기능을 확장해서 변경하면, 클라이언트 코드에 영향을 준다! 따라서 OCP를 위반한다.

실제 의존 관계

정책변경

중요!: 즉 FixDiscountPolicy를 RateDiscountPolicy로 변경하는 순간 OrderServiceImpl의 소스 코드도 함계 변경해야 한다! OCP 위반

어떻게 문제를 해결?

  • DIP를 위반하지 않도록 인터페이스에만 의존하도록 외존관계를 변경하면 된다.
    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); --> 아래 코드로 변경
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
    private DiscountPolicy discountPolicy;
  • 클라이언트인 OrderServiceImpl에 DiscountPolicy의 구현 객체를 대신 생성하고 주입해주어야 한다.

3. 관심사의 분리

AppConfig 등장

  • 애플리케이션의 전체 동작 방식을 구성하기 위해, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스를 만들자.

AppConfig

package com.example.springex1;

import com.example.springex1.discount.FixDiscountPolicy;
import com.example.springex1.member.MemberService;
import com.example.springex1.member.MemberServiceImpl;
import com.example.springex1.member.MemoryMemberRepository;
import com.example.springex1.order.OrderService;
import com.example.springex1.order.OrderServiceImpl;

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

}

참고 : 지금은 각 클래스에 생성자가 없어서 컴파일 오류가 발생한다. 바로 다음에 코드에서 생성자를
만든다.

MemberServiceImpl - 생성자 주입

package com.example.springex1.member;

public class MemberServiceImpl implements MemberService {

   //  private final MemberRepository memberRepository = new MemoryMemberRepository(); 변경 전 코드

    private final MemberRepository memberRepository;

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


    public void join(Member member) {
        memberRepository.save(member);
    }

    public Member findMember(Long memberId) {
        return memberRepository.findById(memberId);
    }

}

OrderServiceImpl - 생성자 주입

package com.example.springex1.order;

import com.example.springex1.discount.DiscountPolicy;
import com.example.springex1.discount.FixDiscountPolicy;
import com.example.springex1.discount.RateDiscountPolicy;
import com.example.springex1.member.Member;
import com.example.springex1.member.MemberRepository;
import com.example.springex1.member.MemoryMemberRepository;

public class OrderServiceImpl implements OrderService{

//    private final MemberRepository memberRepository = new MemoryMemberRepository();
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy(); --> 아래 코드로 변경
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
//    private DiscountPolicy discountPolicy;

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.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);
    }
}
  • 설정 변경으로 MemberServiceImpl은 MemoryMemberRepository를 의존하지 않는다
  • 단지 MemberRepository 인터페이스만 의존한다.
  • MemberServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 들어올지(주입될지)는 알 수 없다.
  • MemberServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부(AppConfig)에서
    결정된다.
    - MemberServiceImpl은 이제부터 의존관계에 대한 고민은 외부에 맡기고 실행에만 집중하면 된다.

클래스 다이어그램

  • DIP 완성: MemberServiceImpl은 MemberRepository인 추상에만 의존하면 된다. 이제 구체 클래스를
    몰라도 된다.
  • 관심사의 분리: 객체를 생성하고 연결하는 역할과 실행하는 역할이 명확히 분리되었다.
  • 클라이언트인 memberServiceImpl 입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서
    DI(Dependency Injection) 우리말로 의존관계 주입 또는 의존성 주입이라 한다.

테스트 코드 오류 수정

class MemberServiceTest {
//    MemberService memberService = new MemberServiceImpl(memberRepository);

    MemberService memberService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
    }
class OrderServiceTest {

//    MemberService memberService = new MemberServiceImpl(memberRepository);
//    OrderService orderService = new OrderServiceImpl();

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach() {
        AppConfig appConfig = new AppConfig();
        memberService = appConfig.memberService();
        orderService = appConfig.orderService();
    }

결과

참고
김영한: 스프링 핵심 원리 - 기본편(인프런)
Github - https://github.com/b2b2004/Spring_ex

0개의 댓글