스프링 핵심 원리 - 기본편 (3) 스프링 핵심 원리 이해2 - 객체 지향 원리 적용

강아람·2022년 8월 21일
0
post-thumbnail

📖 새로운 할인 정책 개발

만약 기획자가 정액 할인이 아닌 정률 할인으로 변경을 요구한다면?

귀여우심...ㅋㅋ

RateDiscountPolicy 를 만들면 된다!

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;
        }
    }
}

💻 Test

ctrl + shift + T : Create Test

📒 회원 등급이 VIP인 경우

@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);
}

실행 결과 : SUCCESSFUL


📘 회원 등급이 BASIC인 경우

@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);
}

실행 결과 : FAILED

💡 실패 test도 해야 한다!!!




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

public class OrderServiceImpl implements OrderService {

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

새로운 할인 정책을 변경하려는 경우, 할인 정책의 클라이언트인 OrderServiceImpl의 코드가 변경되어야 한다.


❓ DIP, OCP를 준수하고 있다고 생각했지만 실상 그게 아니다.

  • DIP : 구현체가 아닌 인터페이스에 의존해야 한다.
    • 인터페이스인 DiscountPolicy와 구현체인 FixDiscountPolicy, RateDiscountPolicy에 의존하고 있다.

  • OCP : 소프트웨어의 확장에는 열려있어야 하지만 변경에는 닫혀있어야 한다.
    • 구현체를 변경하려는 경우 코드를 변경해야 한다.

💡 해결 방법

DIP

OrderServiceImpl에 DiscountPolicy 구현 객체를 대신 생성하고 주입해주어야 한다.




📖 관심사의 분리 🌟🌟🌟

💻 AppConfig

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

이전에는 구현체가 의존하는 인터페이스의 구현 객체를 직접 선택했다.

public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();

😎 이러한 선택을 앞으로는 전적으로 AppConfig가 한다!!!

  • AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다.
    • MemberServiceImpl
    • MemoryMemberRepository
    • OrderServiceImpl
    • FixDiscountPolicy
  • AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해 주입해준다.
    • MemberServiceImpl이 직접 구현 객체 MemoryMemberRepository를 선택
      ▶ MemberServiceServiceImpl이 MemberRepository 인터페이스에만 의존
    • OrderServiceImpl이 직접 구현 객체인 MemoryMemberRepository와 FixDiscountPolicy를 선택
      ▶ OrderSErviceImpl이 MemberRepository와 DiscountPolicy 인터페이스에만 의존

💡 생성자 주입과 의존관계 주입

  • AppConfig 입장
    생성자를 통해 구현 객체를 선택하여 생성, 주입해주어 의존관계를 연결, 실행해준다.
  • 클라이언트 입장
    의존관계를 외부에서 주입해주기 때문에 실행에만 집중할 수 있다.

💻 관심사의 분리

  • 객체의 생성과 연결은 AppConfig가 담당
  • DIP 완성 : 구현 클래스는 인터페이스에만 의존하면 된다!
  • 객체를 생성하고 연결, 실행하는 역할이 명확히 분리됨

◾ MemberService의 구현 객체 주입 (repository 구현 객체 주입)

AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();

◾ MemberService와 OrderService의 구현 객체 주입

AppConfig appConfig = new AppConfig();
memberService = appConfig.memberService();
orderService = appConfig.orderService();

Appconfig가 구체 클래스를 선택하여 애플리케이션이 어떻게 동작할지 전체 구성을 책임진다.




📖 AppConfig 리팩터링

public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    private MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
}
  • 중복 제거 : 구현체를 변경할 경우 해당 부분의 코드만 변경
  • 역할 구분 : 애플리케이션의 전체 구성을 빠르게 파악 가능



📖 새로운 구조와 할인 정책 적용

AppConfig가 객체의 생성과 연결을 담당하면서 사용 영역과 구성 영역이 분리됨
▶ 사용 영역의 변경 없이 구성 영역의 변경만으로 작동 방법을 변경할 수 있음




📖 좋은 객체 지향 설계의 5가지 원칙 적용

우리는 5가지 원칙 중 SRP, DIP, OCP 적용!

◾ SRP : 단일 책임 원칙

한 클래스는 하나의 책이만 가져야 한다.

◾ DIP : 의존관계 역전 원칙

프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다.

◾ OCP : 개방-폐쇄 원칙

소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀있어야 한다.




📖 IoC, DI, 그리고 컨테이너

💻 제어의 역전, IoC (Inversion of Control)

프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것

💥 프레임워크 vs 라이브러리

프레임워크

프레임워크가 코드를 제어하고 대신 실행 (예. JUnit)

라이브러리

코드가 직접 제어 흐름을 담당


💻 의존관계 주입, DI(Dependency Injection)

애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것을 의존관계 주입이라 한다.

🌟

  • 클라이언트 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.
  • 정적인 클래스 의존관계를 변경하지 않고, 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.

정적클래스 의존 관계와 실행 시점에 결정되는 동적객체(인스턴스) 의존 관계를 분리해서 생각해야 한다.

📒 정적인 의존관계 확인 : 클래스 다이어그램

📘 동적인 의존관계 확인 :


💻 IoC 컨테이너, DI 컨테이너

AppConfig처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 IoC 컨테이너 또는 DI 컨테이너라고 한다.




📖 스프링으로 전환하기

💻 스프링 컨테이너

  • 스프링 컨테이너 : ApplicationContext
  • 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정(구성)정보로 사용한다.
  • AppConfig에 @Bean이라 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록한다. (스프링 빈)

👀 스프링 컨테이너에 등록된 객체 (스프링 빈)

0개의 댓글