Framework/Spring

[Spring] DI와 IoC

Ella_K 2023. 3. 13. 18:33

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

강의 예제 활용

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

//private final DiscountPolicy discountPolicy = new FixDiscountPolicy();
private final DiscountPolicy discountPolicy = new RateDiscountPolicy();
  • 정액 할인 정책에서 정률 할인 정책으로 변경 시
  • 추상화(인터페이스), 구체클래스(할인정책 구형 클래스) 모두 의존한다. 즉 DIP에 위반한다.
  • 할인의 역할 뿐 아니라 누구를 선택할지 책임도 가지게 된다. 다양한 책임을 가지며 SRP를 위반한다.
  • 할인정책 변경시 클라이언트 코드를 변경해야 하므로 OCP를 위반한다.
  • DIP에 위반하지 않을려면
private DiscountPolicy discountPolicy;
DIP를 위반하지 않을려면 누군가가 대신 주입해주어야 한다.

관심사 분리

  • 객체를 생성하고 연결하는 역할과 실행하는 역할을 분리해야한다.

AppConfig

public class AppConfig {

    private MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository()); // 생성자 주입
    }

    public DiscountPolicy discountPolicy() {
        return new FixDiscountPolicy();
    }
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
}
  • 애플리케이션의 전체 동작을 구성, 구현 객체를 생성하고, 연결하는 책임을 가지는 별도의 설정 클래스
  • AppConfig를 이용해서 구현체를 선택한다.
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

    @Override
    public Order createOrder(Long memberId, String itemName, int itemPrice) {
        Member member = memberRepository.findByid(memberId);
        int discountPrice = discountPolicy.discount(member, itemPrice); // 단일책임원칙을 잘 지킨 것 (할인에 대한 변경이 필요하면 할인 쪽만 고치면 된다.)
        return new Order(memberId, itemName, itemPrice, discountPrice);
    }
}
  • 할인정책을 변경할려면 아래 부분만 변경하면 된다. 이는 DIP, OCP, SRP를 만족한다.
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }

 

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

1. SRP 단일 책임 원칙

  • AppConfig: 구현 객체를 생성하고 연결하는 책임
  • 클라이언트 객체: 실행하는 책임

2. DIP 의존관계 역전 원칙

  • AppConfig가 구체화 구현 클래스 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계를 주입함으로써 클라이언트 코드는 추상화 인터페이스만 의존할 수 있게 됨

3. OCP 개방-폐쇄 원칙

  • AppConfig가 할인정책을 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 변경하지 않아도 된다.

 

IoC (Inversion of Control) 제어의 역전

  • 기존 프로그램은 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다.
  • 하지만 AppConfig가 등장한 이후에는 구현 객체는 자신의 로직을 수행하는 역할만 담당한다. 제어 흐름은 AppConfig가 담당한다.
프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것

 

프레임 워크와 라이브러리 차이

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

 

DI (Dependency Injection) 의존관계 주입

정적인 클래스 의존관계

  • 애플리케이션을 실행하지 않고 분석할 수 있음

동적인 클래스 의존관계

  • 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존관계 (할인 정책 구현 객체 인스턴스, Repository 구현 객체 인스턴스가 생성자 주입을 통해서 인터페이스 타입의 참조와 연결된다.)

의존관계 주입 DI

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

 

IoC컨테이너, DI 컨테이너

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

 

스프링으로 전환

@Configuration // @Configuration붙은 AppConfig를 설정정보로 사용
public class AppConfig {
    @Bean // 스프링 컨테이너에 등록
    public MemberRepository memberRepository(){ // 메서드 명을 스프링 빈의 이름으로 사용한다.
        return new MemoryMemberRepository();
    }
    
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository()); // 생성자 주입
    }
    
    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }
   
    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }
}
public class OrderApp {
    public static void main(String[] args) {
//        AppConfig appConfig = new AppConfig(); // 필요한 객체를 AppConfig에서 직접 조회
//        MemberService memberService = appConfig.memberService();
//        OrderService orderService = appConfig.orderService();

        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        // 스프링 컨테이너를 통해서 필요한 스프링 빈(객체)를 찾는다. applicationContext.getBean()을 통해 찾는다.
        
        Long memberId = 1L;
        Member member = new Member(memberId, "memberA", Grade.VIP);
        memberService.join(member);

        Order order = orderService.createOrder(memberId, "itemA", 20000);
        System.out.println("order = " + order);
        System.out.println("order = " + order.calculatePrice());
    }
  • ApplicationContext: 스프링 컨테이너
  • AppConfig 설정을 구성한다는 뜻에서 @Configuration을 붙여준다.
  • 각 메서드에 @Bean을 붙여준다. 이렇게 하면 스프링 컨테이너에 스프링 빈으로 등록한다.
  • 스프링 컨테이너를 통해 DI를 한다.
  • 스프링 컨테이너는 @Configuration이 붙은 AppConfig를 설정 정보로 사용한다.
  • @Bean 이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
  • 스프링 컨테이너에 객체를 스프링 빈으로 등록하고, 스프링 컨테이너에서 스프링 빈을 찾아서 사용한다.