ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 핵심 원리 - 기본편 (3) 스프링 핵심 원리 이해2 - 객체 지향 원리 적용
    Back-End/Spring 2022. 8. 21. 23:31

    📖 새로운 할인 정책 개발

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

    귀여우심...ㅋㅋ

    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이라 적힌 메서드를 모두 호출해 반환된 객체를 스프링 컨테이너에 등록한다. (스프링 빈)

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

    댓글

Designed by Tistory.