DIP, OCP 원칙을 기반으로 기능 개발 해보기
이전에 정리한 글에서 순수한 Java 코드만을 사용하여 "객체 설계의 5가지 원칙 SOLID"
중 OCP(개방-폐쇄 원칙), DIP(의존관계 역전 원칙)을 지키면서 개발하는 방법에 대하여 공부했다.
이전 내용을 정리해보면 client 측에서 DiscountPolicy, MemberRepository 인터페이스와 구현체에 모두 의존해서 DIP에 위배됐고 해당 문제를 해결하기 위하여 Appconfig를 도입했다.
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}//생성자 주입
}
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;
}// 생성자 주입
}
다음과 같이 client 측에서는 인터페이스에만 의존하고 어떤 구현체가 올지는 생성자를 통해서 주입 받는다.
주입을 해주는곳이 바로 AppConfig 파일이 되겠다.
public class Appconfig {
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
private MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
이렇게 되면 할인 정책이나 DB가 변경됐을때 client 코드를 건들지 않고 AppConfig 수정함으로서 기능을 갈아 끼울 수 있다.
드디어 IOC, DI의 의미와 기능을 실제 코드내에서 접할 수 있게 되었다!
제어의 역전 IoC(Inversion of Control)
프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.
기존의 코드는 클라이언트 구현객체에서 필요한 부분의 구현 객체를 생성했고 실행했다.
즉 클라이언트 구현객체가 프로그램의 흐름을 결정한것이다.
하지만 AppConfig를 적용시킨 후 부터 클라이언트 구현객체는 자신의 로직을 실행만 하고 AppConfig 에서 프로그램의 흐름을 결정한다. 즉, 제어 흐름을 AppConfig가 가져간다.
의존관계 주입 DI(Dependency Injection)
" 동적인 런타임 상황에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해 의존관계가 연결되는것을 의존관계 주입(DI) 라고 한다.
즉, 위의 코드로 예를 들면 MemberServiceImpl과 OrderServiceImpl은 MemberRepository , DiscountPolicy 에 의존한다는 것을 알 수 있지만 해당 의존관계 만으로는 어떤 구현객체가 주입될지는 알 수 없다. 하지만 실행시 Appconfig를 통해 생성자로서 구현객체가 주입된다.
자 이제 순수한 java 코드로만 구현했던 코드에 Spring이 제공하는 기능을 적용해보자.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public DiscountPolicy discountPolicy(){
// return new FixDiscountPolicy();
return new RateDiscountPolicy();
}
}
@Bean 어노테이션을 사용하여 스프링 컨테이너에 각각의 메서드들을 ApplicationContext 라는 컨테이너에 등록한다. @Configuration은 싱글톤 패턴에 대한 내용인데 다음 게시글에 관련내용을 작성하겠다. (% 정확히 말하면 상위에 BeanFactory라는 인터페이스가 있는데 ApplicationContext를 대부분 사용해서 그것만 알아도 무방하다)
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
위와 같이 AppConfig 를 구성정보로 지정하여 스프링 컨테이너를 생성한다.
그리고 스프링 컨테이너는 파라미터로 넘어온 클래스 정보(AppConfig)의 정보를 사용하여 스프링 빈을 등록한다.
IoC 컨테이너, DI 컨테이너
AppConfig 처럼 객체를 생성하고 관리하면서 의존관계를 연결해 주는 것을
IoC 컨테이너 또는 DI 컨테이너라 한다.
의존관계 주입에 초점을 맞추어 최근에는 주로 DI 컨테이너라 한다.
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findAplicationBean(){
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
if(beanDefinition.getRole() == beanDefinition.ROLE_APPLICATION){
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + "object = " + bean);
}
}
}
(단, 빈 이름은 항상 다르게 작성해야함을 주의하자!)
다음글에는 위와같이 Spring 컨테이너에 객체를 Spring Bean으로 등록해서 얻는 이점에 대해 싱글톤 패턴에대한 소개와 함께 정리해보겠다.