Spring Framework/[인프런] Spring 핵심 원리

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

계란💕 2022. 8. 12. 16:20

3.1 새로운 할인 정책 개발

 

  새로운 할인 정책

  • 기획자가 서비스 오픈 직전에 할인 정책을 정률제로 바꾸고 싶다고 하면 어떻게 해야할까?

 

  Ex) 

  - RateDiscountPolicy 클래스를 만든다.

java
열기

 

  - 테스트 클래스 만들기

java
열기

 Note) 실행 결과 - 성공 케이스

 

 

  Ex) 실패 케이스

java
열기

 

 Note) 실행 결과 - 실패 케이스

 

 

 

3.2 새로운 할인 정책과 문재점

 

  - 이제 OrderServiceImpl 클래스의 내용 중, FixDiscountPolicy => RateDiscountPolicy로 바꾸면 된다.

  - OCP, DIP 같은 객체지향 설계 원칙을 준수하지 못했다

    -> DIP: 추상 클래스 뿐만아니라 구체(구현) 클래스에도 의존하고 있다. 

    -> OCP: 현재 코드는 기능을 확장해서 변경하면 클라이언트 코드에 영향을 준다. => OCP 위반

기대했던 의존 관계

 

실제 의존관계

 

정책 변경

  - 정액제를 정률제로 변경하는 순간 OrderServiceImpl의 소스 코드도 함께 변경해야한다. => "OCP"위반

 

 

 

  - 그러면 인터페이스에만 의존하도록 다음과 같이 코드를 변경하면?

    -> NullPointerException이 발생한다. => 선언만 하고 생성을 하지 않았기 때문이다.

java
열기

  Note) 해결 방안

    - 누군가가 클라이언트인 "OrderServiceImpl"에 "DiscountPolicy"의 구현 객체를 대신 생성하고 주입해줘야한다.

 

 

 

3.3 관심사의 분리

 

  - AppConfig클래스 만든다. 애플리케이션 전체에 대해 환경 설정하고 구성한다는 뜻이다.

    -> AppConfig는 애플리케이션의 실제 동작에 필요한 구현 객체를 생성한다. 

    -> AppConfig는 생성한 객체 인스턴스의 참조(레퍼런스)를 생성자를 통해서 주입(연결) 해준다. => 'injection'

    -> MemberServiceImpl은 MemoryMemberRepository에서 쓴다.

 

  - MemberServiceImpl 클래스를 다음과 같이 수정한다.

    -> MemberRepository변수를 선언만하고 생성은 하지 않는다.

    -> 클래스의 생성자를 만든다. 생성자를 통해 MemberRepository에 뭐를 넣을지 정할 예정이다.

    -> 추상화에만 의존하도록 만든다.

 

java
열기

 

  - AppConfig

    -> MemberService를 호출하면 new MemberServiceImpl(new MemoryMemberRepository)가 생성된다.

java
열기

 

  - OrderServiceImpl 클래스 내용을 수정한다. (impl은 기능을 실행하는 부분만 책임진다)

    -> final은 무조건 생성 또는 생성자를 통해서 할당되어야한다.

    -> OrderServiceImpl의 멤버변수 memberRepository와 discountPolicy 를 선언하기만 하면 오류 난다.

    -> 따라서, 선언한 다음에 OrderServiceImpl 의 생성자를 만들어주면 오류가 해결된다.

 

 

  - OrderApp 클래스 수정

java
열기

  Note) 실행 결과

 

 

  Ex) 위의 코드의 null 부분을 AppConfig를 쓰도록 바꾼다. 

java
열기

  Note) 실행 결과

    - OrderApp은 더이상 구체 클래스에 의존하지 않는다. 인터페이스에만 의존할 뿐이다.

 

  - MemberServiceTest 클래스도 수정

    -> AppConfig를 사용하도록 한다.

    -> @BeforeEach: 각 테스트 전에 무조건 실행되는 부분이다. 테스트 개수 만큼 돌아간다.

java
열기

 

  - OrderServiceTest 클래스도 수정한다.

java
열기

 

  Note) 실행결과

    - 이제 AppConfig를 통해 관심을 확실하게 분리했다.

    - AppConfig는 공연기획자와 같은 역할이다.

 

 

 

3.4 AppConfig 리팩토링 

 

  - 그런데 현재 AppConfig를 보면 중복이 있고 역할에 따른 구현이 잘 안 보인다는 문제가 있다.

기대하는 그림

 

  - AppConfig 클래스 - 구성 정보

    -> 메서드명에 따라 역할과 구현 클래스가 명확하게 보인다.

  =============코드 내용이 나중에 바뀔 예정 ===============

java
열기

 

 

 

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

 

  Ex) 정액제 => 정률제

    - 할인 정책을 바꾸면 AppConfig의 discountPolicy 의 반환값만 RateDiscountPolicy로 바꾸면 된다.

    - OrderServiceImpl은 바꿀 필요가 없다.

 

  - OrderApp 클래스 수정

java
열기

  Note) 실행결과

 

 

 

3.6 전체 흐름 정리 

  • 새로운 할인 정책 개발
    • 새로운 할인 정책 적용과 문제점 =>클라이언트가 DiscountPolicy 뿐만 아니라 'FixDisountPolicy'에도 의존 => 'DIP'위반
  • 관심사의 분리 => 'AppConfig'
  • AppConfig 리팩토링 - new를 하나만 나오도록한다(중복 제거). 역할과 구현을 분리
  • 새로운 구조와 할인 정책 적용

 

 

 

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

 

  SRP - 단일 책임 원칙(1 클래스 - 1 책임)

  • 구현 객체를 생성하고 연결하는 책임은 AppConfig가 담당한다.
  • 클라이언트 객체를 실행하는 역할만 담당

 

 

  DIP - 의존관계 역전 원칙(개발자는 구체화가 아닌 추상화에 의존하도록)

  • AppConfig가 'FixDiscountPolicy' 객체 인스턴스를 클라이언트 코드대신 생성해서 클라이언트 코드에 의존 관계를 주입해서 DIP를 지킬 수 있다.

 

 

  OCP - 개방 폐쇄 원칙

  • 애플리케이션을 사용 / 구성 영역으로 나눈다.
  • 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀 있어야 한다.

 

 

 

3.8 IoC, DI,  컨테이너

 

  IoC(Inversion Of Control) - 제어의 역전

  • 기존의 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행했다. 즉, 구현 객체가 프로그램의 제어 흐름을 스스로 컨트롤했다. 
  • 반면에, AppConfig를 사용하면 구현 객체는 자신의 로직을 실행하는 역할만 담당한다.
  • ex) 프로그램을 제어하는 권한은 모두 AppConfig가 가지고 있다.  OrderServiceImpl 또한 AppConfig가 생성한다. 그리고 AppConfig는  OrderServiceImpl 가 아닌 OrderService 인터페이스의 다른 구현 객체를 생성하고 실행할 수 있다. 그런 사실과 무관하게 OrderServiceImpl는 자신의 로직을 실행한다.
  • 이렇듯 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.

 

 

  DI(Dependency Injection) - 의존 관계 주입

  • OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 실제 어떤 구현 객체가 사용될지 모른다.
  • 의존 관계는 정적인 클래스 의존 관계와 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야한다.
    • 정적인 클래스 의존 관계: import문만 봐도 분석 가능
    • 동적인 객체(인스턴스) 의존 관계: 애플리케이션을 실행하고나서 분석 가능하다.

 

 

프레임워크(Framework) vs  라이브러리(Library)

  • 프레임워크가 내 코드를 제어하고 대신 실행하면 프레임워크가 맞다. ex) JUnit
  • 내 코드가 제어의 흐름을 직접 담당하는 경우는 라이브러리

클래스 다이어그램

 

객체 다이어그램

 

객체 다이어그램

  • 애플리케이션 실행 시점(런타임)에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존 관계가 되는 것을 의존관계 주입이라고 한다.
  • 객체 인스턴스를 생성하고 그 참조값을 전달해서 연결된다.
  • 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고 클라이언트가 호출하는 대상의 타입 인스턴스를 변경 가능하다. 

 

 

IoC 컨테이너,  DI 컨테이너

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

 

 

3.9 스프링으로 전환하기 

 

  Ex) 지금까지 자바 프로젝트를 만든 것과는 다르게 스프링을 사용해보자

 

    - AppConfig 클래스에  @Configuration어노테이션을 붙인다. => 애플리케이션의 설정 정보를 담당한다.

      -> AppConfig 하위의 각 메서드에 @Bean을 붙인다.

      -> @Bean을 붙이면 그 메서드들이 모두 스프링 컨테이너에 모두 빈으로 등록된다. 기본적으로 메서드 이름(key)으로 등록된다. 메서드 반환값은 value이다.

      -> 또한, @Bean(name="메서드명 대신 붙이고 싶은 이름")을 이용해서 객체의 이름을 정할 수도 있다.

java
열기

 

   - MmberApp

     -> ApplicationContext는 스프링 컨테이너라고 볼 수 있다. bean과 같은 객체들을 모두 관리한다.

     ->  new AnnotationConfigApplicationContext(AppConfig.class)

       => 클래스 AppConfig 안에 있는 환경 설정 정보를 가지고 스프링이 모든 빈을 스프링 컨테이너에 넣어서 관리해준다. 

      -> 이제, AppConfig가 아닌 ApplicationContext에서 빈을 찾아와야 한다.       

        => appliacationContext.getBean("memberService", MemberService.class);  ..("이름", "반환 타입")

        => 스프링 컨테이너 appliacationContext에서 이름이 "memberService"인 객체를 찾아서 MemberService 형태로 반환하겠다는 뜻이다.

java
열기

    

  Ex)  OrderApp으로 실습

java
열기

    Note) 실행 결과

 

 

 

스프링 컨테이너(Stpring Container)

  • ApplicationContext를 스프링 컨테이너라고 한다.
  • 기존에는 AppConfig를 사용해서 직접 객체를 생성하고 의존 관계 주입을 했지만 이제는 스프링 컨테이너를 사용한다.
  • @Configuration이 붙은 'AppConfig'를 설정(구성) 정보로 사용한다. Bean(@Bean이라고 적힌 객체)을 모두 호출해서 반환된 객체를 컨테이너에 등록한다.

 

 

출처 https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/

 

스프링 핵심 원리 - 기본편 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com