스프링 공부 (인프런 김영한 선생님)/스프링 핵심원리

[스프링 핵심원리] 3. 객체지향 설계와 스프링, 정리

ProgYun. 2023. 5. 7. 17:22
 

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

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

www.inflearn.com

 

두 편에 걸쳐서 작성한 코드와 개선 과정을 정리해보겠습니다

 

먼저 우리는 비즈니스 요구사항에 따라 Member, Order, Discount 세 가지 항목에 관한 기능을 구현하였습니다

 

그 도중, 새로운 할인 정책을 개발했고 (FixDiscountPolicy -> RateDiscountPolicy)

그 새로운 정책을 적용하는 과정에서 OCP와 DIP 원칙을 위배한 코드 작성으로 인해 클라이언트 코드 수정이 불가피하다는 사실

(OrderServiceImpl 필드 수정 필요로 인함)을 마주했고,

이 문제를 해결하기 위해 관심사의 분리 즉, AppConfig를 이용하여 의존관계 주입(DI)를 해결했습니다.

 

또한 AppConfig의 가독성이 떨어진다는 문제점으로 인해, 역할이 잘 드러나게 끔 그리고 중복이 제거될 수 있도록

 

다음과 같이 최종적으로 AppConfig를 리팩터링하였습니다

package hello;

import hello.springintroduction.Member.MemberRepository;
import hello.springintroduction.Member.MemberService;
import hello.springintroduction.Member.MemberServiceImpl;
import hello.springintroduction.Member.MemoryMemberRepository;
import hello.springintroduction.discount.DiscountPolicy;
import hello.springintroduction.discount.FixDiscountPolicy;
import hello.springintroduction.discount.RateDiscountPolicy;
import hello.springintroduction.order.OrderService;
import hello.springintroduction.order.OrderServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
// 사용영역과 구성 영역으로 구역이 이분할 됨.
// 사용영억(클라이언트 코드), 구성영역 (DIP 코드)

@Configuration
public class AppConfig { // 생성자를 통한 주입
    // 각각의 Impl에 생성자를 구현함으로써 Dependency injection이 가능함.
    // XML로 사용하면 AppConfig.java 조차 필요가 없다!

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

    @Bean
    public MemoryMemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    /*public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }*/

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

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }


}

코드 설계를 다음과 같이 변경함으로 인해

더 이상 클라이언트 코드는 어떤 구현체가 나에게 올 것인지에 대해 신경을 쓸 필요가 없게 되었습니다


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

SRP, DIP, OCP 코드 설계와 리팩터링을 통해 충분히 적용되었습니다

 

클라이언트 객체는 직접 구현 객체를 생성하고 연결하고 실행하는 다양한 책임을 갖고 있었으나

SRP를 적용하면서 역할과 구현을 분리했습니다(관심사 분리)

구현 객체를 생성하는 역할은 AppConfig에 모두 분리 위임하였고

클라이언트 객체는 이제 온전히 실행에만 신경 쓸 수 있게 되었습니다.

 

DIP 원칙

 

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

 

OCP 원칙

 

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

 

지금까지 OrderServiceImpl은 DiscountPolicy의 구현체에 의존하고 있어, DIP를 위반

클라이언트 코드도 변경해야 했지만, 이제 추상화 인터페이스에만 의존하고 AppConfig가 구현체를 생성하여

생성자를 통해 주입해주기 때문에, 더 이상 DIP 위반 소지가 없게 되었습니다.

(클라이언트 코드 변경 필요가 없어졌습니다) -> OCP 원칙을 달성했습니다!

 


IOC, DI, 컨테이너

 

제어의 역전(IoC)

 

기존 프로그램 - 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성, 연결, 실행 (혼자 다한다)

AppConfig의 등장 - 구현 객체는 자신의 로직을 실행하는 역할만 담당하고 제어 흐름은 AppConfig가 가져간다.

OrderServiceImpl의 경우 어떤 인터페이스가 실행될지는 알고 있지만, 어느 구현체가 올지는 몰라도 자기 할일만 한다.

무엇이 올지에 대한 제어권은 AppConfig가 가져간다, OrderServiceImpl도 AppConfig가 생성한다

 

프로그램 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전이라고 정의한다.

 

개발자가 모든 객체를 생성/호출하지 않고 프레임워크에 의해 호출된다

 

프레임워크 vs 라이브러리

프레임워크 - 내가 작성한 코드를 프레임워크가 제어하고, 대신 실행하면 프레임워크(ex, Junit)

내가 작성한 코드가 직접 제어의 흐름을 담당한다면 프레임워크가 아니라 라이브러리임.

 

 

 

의존관계는 정적인 클래스 의존관계와 실행시점(런타임)에 결정되는 동적인 객체(인스턴스) 의존 관계 둘을 분리해서 생각해야 함.

 

정적인 클래스 의존관계 - Import하는 코드 보고 판단, App 실행하지 않고도 분석이 가능하다

동적인 클래스 의존관계

public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy)

어떤 객체가 될지 코드만 보고 분석 불가 -> 동적인 객체 인스턴스 의존관계라 할 수 있음

 

실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결되는 것

- 의존관계 주입

 

객체 인스턴스 생성, 그 참조값을 전달해서 연결 - AppConfig에서는 참조값이 생성자로 넘어감

 

의존관계 주입을 설정하면 클라이언트 코드 변경 없이도 클라이언트가 호출하는 대상의 타입 인스턴스 변경이 가능

- AppConfig에서 우리는 홍해가 갈라지는것을 보았음

(AppConfig 가라사대 FixDiscountPolicy가 RateDiscountPolicy가 되었다)

 

의존관계 주입을 사용하면 정적인 클래스 의존관계를 변경하지 않고 동적인 클래스 의존관계를 쉽게 변경할 수 있음.

즉, 클래스 다이어그램에 변동을 주지 않음으로써 애플리케이션 로직에 영향이 가지 않음

 

IOC(올림픽 그거 아닙니다 ㅡ.ㅡ) 컨테이너 / DI 컨테이너

AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것을 말함.

의존관계 주입에 초점을 맞추어 주로 DI 컨테이너라고 합니다

어셈블러, 오브젝트 팩토리 등으로 불리기도 합니다

 

여러 DI가 존재하는데 어셈블러라고 하기도 하고 오브젝트 팩토리라고도 불립니다

Trigger 해주는 느낌으로 생각하면 될 것같고 IOC는 좀 더 범용적인 느낌이 강합니다

Dependency Injection이 좀 더 스프링 스러운 단어라고 합니다!

 

다음시간에는 코드를 스프링으로 변환하는 과정을 다룹니다