Little bIT awesome

[스프링 핵심 원리] 섹션 3 객체 지향 원리 적용 본문

백엔드/Spring

[스프링 핵심 원리] 섹션 3 객체 지향 원리 적용

까루카라 2023. 2. 28. 11:13

새로운 할인 정책 개발 : 10% 할인 정책

RateDiscountPolicy 추가

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

할인 정책을 적용할 때, 클라이언트인 OrderServiceImpl 코드를 고쳐야 한다.

문제점

  • OCP, DIP 원칙을 준수하지 않음
    • DIP : 구현 클래스에 의존하고 있다.
    • OCP : 변경해야 확장 가능하다.

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

관심사의 분리

객체를 생성하고, 연결하는 책임을 가지는 별도의 클래스가 필요하다! (AppConfig)

생성자를 통해 MemberRepository를 받고 외부에서 AppConfig를 통해 MemberRepository의 구현체를 넣어줌.

  • MemberServiceImpl의 입장에서 생성자를 통해 어떤 구현 객체가 들어올지 알 수 없다. (구현 클래스에 의존하지 않음)
  • MemberServiceImpl의 생성자를 통해서 어떤 구현 객체를 주입할지는 오직 외부에서 결정된다. (변경 불필요)

 

appConfig는 외부에서 memoryMemberRepository를 생성해서 memberService 객체를 생성할 때 주입해준다.

memberService입장에서 보면 의존관계를 마치 외부에서 주입해주는 것 같다고 해서 의존관계 주입, 의존성 주입(DI: Dependency Injection)이라고 부른다.

appConfig 사용하기

  • MemberApp

  • OrderApp

 

테스트 코드 수정하기

@BeforeEach : 각 테스트를 실행하기 전에 수행된다.

AppConfig 리팩터링 : 중복 제거 및 역할에 따른 구현 가시화

 

기대하는 그림

리팩터링 후

AppConfig만 변경하면 된다.

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

SRP, DIP, OCP

  • SRP : 한 클래스는 하나의 책임만 가져야 한다.
    • 관심사를 분리
    • 기존에 클라이언트 객체가 직접 객체를 구현, 열결, 실행했음.
    • AppConfig가 객체를 구현하고 연결하는 역할을 수행함.
    • 클라이언트는 실행하는 역할만 담당하게 됨.
  • DIP : 추상화에 의존해야지, 구체화에 의존하면 안된다.
    • 기존의 클라이언트 코드가 추상화 인터페이스 뿐만 아니라 구체화 클래스도 함께 의존하고 있었음.
    • AppConfig가 객체를 대신 생성해서 의존 관계를 주입
    • 클라이언트 코드가 추상화 인터페이스에만 의존할 수 있도록 코드를 변경했다.
  • OCP : 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.
    • AppConfig에서만 변경하면 됨.
    • 소프트웨어 요소를 새롭게 확장해도 사용 영역의 변경은 닫혀있다.

제어의 역전 IoC(Inversion of Control)

  • 기존의 프로그램은 클라이언트가 객체를 구현, 연결, 실행을 모두 수행. 구현 객체가 제어 흐름을 스스로 조종했다.
  • AppConfig가 등장한 이후, 구현 객체는 자신의 로직을 실행하기만 하고, 제어 흐름은 AppConfig가 가져간다.
  • 심지어 OrderServiceImpl도 AppConfig가 생성한다. 다른 구현 객체를 생성할 수도 있음.
  • 제어의 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 한다.

프레임워크 vs 라이브러리

  • 내가 작성한 코드를 제어하고, 대신 실행하면 프레임워크
  • 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 라이브러리

의존관계 주입 DI(Dependency Injection)

OrderServiceImpl은 DiscountPolicy에 의존한다. 단, 실제 어떤 구현 객체가 사용될지는 모른다. → 정적인 클래스 의존관계와 동적인 클래스 의존관계를 분리해서 생각해야 한다.

  • 정적인 클래스 의존관계
    • import만 보고도 분석할 수 있다. But, 어떤 객체가 주입될지는 모름
    • 어떤 인터페이스에 의존하는지?
  • 동적인 객체 인스턴스 의존 관계
    • 실제 실행 타임에 외부에서 구현 객체를 생성하고 의존관계를 연결해 줌 : DI
    • 객체 인스턴스를 생성하고 참조값을 전달해서 연결된다.
    • 코드를 변경하지 않고, 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있음.
    • DI를 사용하면, 정적인 의존 관계를 변경하지 않고, 동적인 의존 관계를 변경가능

※ AppConfig처럼 DI해주는 것을 DI 컨테이너라고 한다. (어샘블러, 오브젝트 팩토리 등으로 불리기도 한다. )

 

스프링 기반 으로 변경