- 현재 AppConfig는 역할(MemberRepository 드러나지 않음)에 따른 구현이 보이지 않고 중복이 있다는 단점(new MemoryMemberRepository)이 있다.
- 이와 같이 생성자 주입을 통해 역할(인터페이스)과 구현(구현체)가 명확히 드러나고 향후 구현체 코드만 바꾸면 중복이 해결된다.
새로운 구조와 할인 정책 적용
- 이제 RateDisCountPolicy로 할인 정책을 바꾸기만 하면 정률 할인 정책이 적용된다.
- 이제 할인 정책을 변경해도 AppConfig만 변경하면 되는 것이다.
중간정리
- 주문 서비스 클라이언트가 인터페이스인 DiscountPolicy 뿐만 아니라 구체 클래스인 FixDiscountPolicy에도 의존하여 DIP 위반
- 따라서 AppConfig 통해 관심사를 분리, 구현 객체를 생성하고 연결하는 "책임"을 부여함
- 이제 클라이언트 객체는 자신의 역할을 실행하는 것만 집중하여 권한이 줄어듬
- AppConfig 덕분에 애플리케이션이 사용 영역과 구성 영역으로 분리됨
좋은 객체 지향 설계의 5가지 원칙 적용
- SRP(단일 책임 원칙)을 따르기 위해 관심사를 분리한 것임, 구현 객체 생성 연결은 AppConfig, 클라이언트 객체는 실행 담당
- DIP(의존관계 역전 원칙)은 어떻게 따랐나?, "프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다"->즉 클라이언트 코드가 DisCountPolicy 추상화 인터페이스에만 의존하도록 코드를 변경했다. 하지만 인터페이스만으로는 아무것도 실행을 시키지 못하므로 AppConfig 통해 FixDiscountPolicy 객체 인스턴스를 클라이언트 코드 대신 생성해서 클라이언트 코드에 의존관계 주입.-> 이렇게 DIP 원칙 따르면서 문제를 해결했다.
- OCP(소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다. 애플리케이션을 사용 영역과 구성 영역으로 나누었다. Appconfig가 의존관계를 Fix->Rate로 변경해서 클라이언트 코드에 주입하므로 클라이언트 코드는 그대로 둬도 된다. 즉 소프트웨어 요소를 새롭게 확장해도 사용 영역을 변경할 필요가 없다.
IOC(Inversion of Control) : 제어의 역전
- 맨 처음 코드에서는 클라이언트 구현 객체가 스스로 필요한 객체를 생성 연결 실행했지만, AppConfig를 쓴 이후는 클라이언트 구현 객체는 자신의 로직 실행만 담당하고 AppConfig가 제어 흐름을 담당한다. 심지어 그 클라이언트 구현 객체마저도 AppConfig가 호출한다.
- 이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IOC)이라 한다.
프레임워크vs라이브러리
- 프레임워크가 내가 작성한 코드를 직접 제어하고 실행하면 프레임워크가 같다(Junit)
- 반면 내가 작성한 코드가 직접 제어의 흐름을 담당한다면 그것은 프레임워크가 아니라 라이브러리다.
의존관계
- OrderServiceImpl은 DiscountPolicy 인터페이스에 의존한다. 따라서 실제 어떤 구현 객체가 사용될지는 모른다(Fix or Rate?).
- 의존관계는 "정적인 클래스 의존 관계와, 실행 시점에 결정되는 동적인 객체(인스턴스) 의존 관계" 둘을 분리해서 생각해야 한다.
정적인 클래스 의존관계
- 정적인 클래스 의존관계는 클래스가 사용하는 import 코드만 보고 의존관계를 쉽게 판단할 수 있다. 즉 애플리케이션 실행 없이 분석 가능하다.
- OrderServiceImpl은 MemberRepository, DiscountPolicy 인터페이스에 의존한다. 그런데 이것 만으로는 실제 어떤 객체가 OrderServiceImpl에 주입될지 모른다.(고정이 올지 정률이 올지...)
동적인 객체 인스턴스 의존 관계
- 애플리케이션 실행 시점에 실제 생성된 객체 인스턴스의 참조가 연결된 의존 관계다.
의존관계 주입(Dependency Injection)
- 애플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 클라이언트와 서버의 실제 의존관계가 연결 되는 것을 의존관계 주입이라 한다.
- 객체 인스턴스를 생성하고 그 참조값을 전달해서 연결된다.
- 의존관계 주입을 사용하면 클라이언트 코드를 변경하지 않고 클라이언트가 호출하는 대상의 타입 인스턴스를 변경할 수 있다.
- 즉 정적인 클래스 의존관계를 변경하지 않고 동적인 객체 인스턴스 의존관계를 쉽게 변경할 수 있다.
IOC컨 테이너, DI 컨테이너
- AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해주는 것이 IOC 혹은 DI 컨테이너라 한다.
스프링으로 전환하기
이제 본 코드를 스프링으로 변경하겠다. 먼저 AppConfig를 변경하자
- 맨 위 @Configration 어노테이션과 메소드에 @Bean 어노테이션을 다음처럼 붙여준다.
- 이렇게 하는 순간 @Bean이 붙은 메소드들이 전부 스프링 컨테이너라는 곳에 등록된다.
- ApplicationContext가 방금 등록한 빈들이 있는 스프링 컨테이너를 관리하는 곳이다.
- new AnnotationConfigApplicationContext(AppConfig.class); 를 호출한다. 아까 AppConfig에 @Configration 어노테이션이 붙은 것이 기억날 것이다. 말 그대로 "어노테이션을 기반으로 Configration(구성)을 관리한다는 뜻히다"
- AppConfig.class를 파라미터로 넘겼으므로 AppConfig의 설정 정보를 가지고 스프링이 스프링 컨테이너에 객체 생성 정보를 넣어 관리할 것이다.
- 이제 getBean을 통해 Appconfig에 정의된 메소드 이름(memberService)을 호출하여 가져온다. 두 번째 파라미터에는 타입(인터페이스)인 MemberService를 넣어준다.
- MemberApp을 실행시키면 @Bean으로 등록한 메소드들이 빈으로 스프링 컨테이너에 등록됐음을 볼수 있다. 꺼낼 때는 위처럼 이름과 타입으로 꺼내면 된다.
- OrderService 역시 같은 방법으로 컨테이너에서 꺼낼 수 있다.
정리
-
ApplicationContext를 스프링 컨테이너라 한다.
-
기존에는 Appconfig를 사용해서 DI를 했지만 이제 스프링 컨테이너를 통해 사용한다.
-
스프링 컨테이너는 @Configration이 붙은 AppConfig를 구성 정보로 사용한다 여기서 @Bean이라 적인 메소드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록한다. 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈이라 한다.
-
스프링 빈은 @Bean이 붙은 메소드의 이름을 스프링 빈의 이름으로 사용한다.
-
이제 스프링 컨테이너를 통해 스프링 빈 객체를 찾아야 한다. 그것을 applicationContext.getBean() 을 통해 하는 것이다.
-
그렇다면 스프링 컨테이너를 사용하면 어떤 장점이 있을까? 이제 알아볼 것이다...