제어의 역전

제어의 역전이란 객체의 생성, 실행 그리고 객체간 의존 관계를 맺어주는 제어 권한이 역전되는 것을 뜻한다.

📌 제어의 역전

제어의 역전이라는 건, 간단히 프로그램의 제어 흐름구조가 뒤바뀌는것 이라고 설명할 수 있다. 일반적으로 프로그램의 흐름은 main() 메소드와 같이 프로그램이 시작되는 지점에서 다음에 사용할 오브젝트를 결정하고, 결정한 오브젝트를 생성하고, 만들어진 오브젝트에 있는 메소드를 호출하고, 그 오브젝트 메소드 안에서 다음에 사용할 것을 결정하고 호출 하는 식의 작업이 반복된다. 이런 프로그램 구조에서 각 오브젝트는 프로그램 흐름을 결정하거나 사용할 오브젝트를 구성하는 작업에 능동적으로 참여한다.

(중략)

제어의 역전이란 이런 제어 흐름의 개념을 거꾸로 뒤집는 것이다. 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 또 자신도 어떻게 만들어지고 어디서 사용되는지를 알 수 없다. 모든 제어 권한을 자신이 아닌 다른 대상에게 위임하기 때문이다. 프로그램의 시작을 담당하는 main()과 같은 엔트리 포인트를 제외하면 모든 오브젝트는 이렇게 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.

제어의 역전 예시 1 - 서블릿

  • 개발자는 서블릿을 직접 구현하고 서버에 배포할 수 있다. 하지만 서블릿의 실행은 개발자가 직접 제어하지 않는다.
  • 실제로 서블릿 객체를 적절한 시점에 생성해서 그 안의 메서드를 호출하는 것은 WAS의 서블릿 컨테이너가 한다.

WAS의 Servlet 컨테이너

  • WAS는 Servlet 컨테이너를 가지고 있다.
  • WAS는 웹 브라우저로부터 서블릿 URL에 해당하는 요청을 받으면 해당 서블릿을 메모리에 올리고 실행한다.
  • Servlet 컨테이너는 동일한 서블릿에 대한 요청을 받으면 메모리 상에 올라가있는 서블릿을 실행하여 그 결과를 웹 브라우저에 전달한다.

제어의 역전 예시 2 - 템플릿 메서드 패턴

  • 템플릿 메서드 패턴은 부모 클래스가 큰 흐름 로직인 템플릿을 가지고 있고, 자식 클래스는 큰 흐름에서 변화되는 부분만을 구현하는 디자인 패턴이다.
  • 자식 클래스가 구현하는 부모 클래스의 추상 메서드에 대한 실행 권한은 부모 클래스한테 있다. 이를 제어의 역전이라고 볼 수 있다.

즉 제어권을 상위 템플릿 메소드에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다는, 제어의 역전 개념을 발견 할 수 있다. 템플릿 메소드는 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴이라고 볼 수 있다.

제어의 역전 예시 3 - 프레임워크

프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. 프레임워크는 라이브러리의 다른 이름이 아니다. 프레임워크는 단지 미리 만들어둔 반제품이나, 확장해서 사용할 수 있도록 준비된 추상 라이브러리의 집합이 아니다. 프레임워크가 어떤 것인지 이해 하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다. 라이브러리를 사용하는 애플리케이션 코드는 애플리케이션 흐름을 직접 제어한다. 단지 동작하는 중에 필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐이다. 반면에 프레임워크는 거꾸로 애플리케이션 코드가 프레임워크에 의해 사용된다. 보통 프레임워크 위에 개발한 클래스를 등록해두고, 프레임워크가 흐름을 주도하는 중에 개발자가 만든 애플리케이션 코드를 사용하도록 만드는 방식이다. 최근에는 툴킷, 엔진, 라이브러리 등도 유행을 따라서 무작정 프레임워크라고 부르기도 하는데 이는 잘못된 것이다. 프레임워크에는 분명한 제어의 역전 개념이 적용되어 있어야 한다. 애플리케이션 코드는 프레임워크가 짜놓은 틀에서 수동적으로 동작해야 한다.

📌 스프링 IoC

스프링 프레임워크는 애플리케이션 컨텍스트라고 불리는 IoC 컨테이너를 통해 객체를 관리한다. 몇 가지 용어를 정리해보자.

빈(bean)

  • 스프링이 직접 생성과 제어를 담당하는 객체를 빈이라고 부른다.

빈 팩토리(bean factory)

  • 스프링의 IoC를 담당하는 핵심 컨테이너를 가리킨다. 보통은 빈 팩토리를 바로 사용하지 않고 이를 확장한 애플리케이션 컨텍스트를 이용한다.

애플리케이션 컨텍스트(application context)

  • 빈 팩토리를 확장한 IoC 컨테이너로 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다. 빈 팩토리라고 부를 때는 주로 빈의 생성과 제거의 관점에서 이야기하는 것이고, 애플리케이션 컨텍스트라고 할 때는 스프링이 제공하는 애플리케이션 지원 기능을 모두 포함해서 하는 이야기다.

설정정보/설정 메타정보

  • 애플리케이션 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타 정보를 말한다.
  • IoC 컨테이너에 의해 관리되는 객체들을 생성하고 구성할 때 사용된다.

컨테이너 또는 IoC 컨테이너

  • IoC 방식으로 빈을 관리한다는 의미에서 애플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너라고도 한다.

스프링 프레임워크

  • 스프링 프레임워크는 IoC 컨테이너, 애플리케이션 컨텍스트를 포함해서 스프링이 제공하는 모든 기능을 통틀어 말할 때 주로 사용한다.

📌 스프링 IoC/DI

제어의 역전(IoC)과 의존관계 주입(DI)

  • IoC라는 용어는 매우 느슨하게 정의돼서 폭넓게 사용되는 용어다.
  • 스프링을 IoC 컨테이너라고만 해서는 스프링이 제공하는 기능의 특징을 명확하게 설명하지 못한다.
  • 스프링이 제공하는 IoC 방식을 명확하게 드러내기 위해 의존 관계 주입이라는 용어를 사용하기 시작했다.
  • 스프링 IoC 기능의 대표적인 동작원리는 주로 의존관계 주입이라고 불린다.
  • 스프링이 여타 프레임워크와 차별화돼서 제공해주는 기능은 의존관계 주입이라는 새로운 용어를 사용할 때 분명하게 드러난다.

의존관계 주입, 의존성 주입, 의존 오브젝트 주입?

  • 엄밀히 말해서 객체는 다른 객체에 주입할 수 있는게 아니다. 객체의 레퍼런스가 전달될 뿐이다.
  • DI는 객체 레퍼런스를 외부로부터 주입받고 이를 통해 다른 객체와 다이나믹하게 의존관계가 만들어지는 것이 핵심이다.
  • DI의 의도를 강하게 드러내는 ‘의존관계 설정’이라는 용어도 나쁘지 않다.

런타임 의존관계 설정

image

  • 의존관계란 무엇일까? 위 그림은 A가 B에 의존하고 있음을 나타낸다.

  • B의 기능이 추가되거나 변경나거나, 형식이 바뀌거나 하면 그 영향은 A로 전달된다.

image-20210614202017400

  • 위와 같이 인터페이스에 대해서만 의존관계를 만들어주면 인터페이스 구현 클래스와의 관계가 느슨해진다.
  • 즉, 인터페이스를 통해 의존관계를 제한해주면 그만큼 변경에서 자유로워진다.
  • UserDaosms DConnectionMaker라는 클래스의 존재도 알지 못한다. 단지 인터페이스만 알 뿐이다.
  • 인터페이스를 통해 설계 시점에 느슨한 의존관계를 갖는 경우 실제 UserDao 객체가 사용할 객체는 어떤 클래스인지 알 수 없다.
  • 프로그램이 시작되고 UserDao 객체가 만들어지고 나서 런타임 시에 의존관계를 맺는 대상, 즉 실제 사용대상인 객체를 의존 객체라고 말한다.
  • 의존관계 주입은 이렇게 구체적인 의존 오브젝트와 그것을 사용할 주체, 보통 클 라이언트라고 부르는 오브젝트를 런타임 시에 연결해주는 작업을 말한다.

의존 관계 주입의 3가지 조건

  1. 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 그러기 위해서는 인터페이스에만 의존하고 있어야한다.
  2. 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
  3. 의존관계는 사용할 객체에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

📌 IoC/DI 코드로 살펴보기

애플리케이션 코드

package kr.co.example;

public class Person{
	private Phone phone;
	
	public Person(){
		phone = new FeaturePhone();
	}
}
  • Ioc/DI가 적용되지 않는 경우, Phone 인터페이스를 구현하는 FeaturePhone 클래스를 new 키워드를 통해 직접 생성해서 초기화 하고 있다.
  • 이런 경우 동적으로 새로운 구현 클래스를 적용하기가 어렵다.
  • 만약 Ioc/DI를 사용한다면, SmartPhone 클래스로 바꾸고 싶을때 빈 설정 정보만 수정해주면 된다.

Ioc/DI가 적용된 경우

컨테이너 빈 설정 정보

<beans>
  <bean id = "phone" class="package kr.co.example.Phone">
  <bean id = "person" class="package kr.co.example.Person">
  	<property name="phone" ref="phone"/>
  </bean>
</beans>

애플리케이션 코드

package kr.co.example;

public class Person{
	private Phone phone;
	
	public Person(Phone phone){
		this.phone = phone;
	}
}
  • 빈 설정 정보를 바탕으로 사용할 객체들을 컨테이너에 등록한다.
  • 프로그램을 실행하면 컨테이너는 애플리케이션 코드에 등록한 객체를 넣어준다.
  • 즉, Person 객체의 생성자 매개변수로 Phone 객체를 생성해서 넣어준다.
  • 이렇게 하면 Phone 인터페이스를 구현하는 클래스가 애플리케이션에 등록하지 않는다. 따라서 동적으로 구현 클래스를 마음대로 바꿀 수 있다.

  • Ioc/DI를 사용하면 객체를 생성할 때 해당 객체가 참조하고 있는 다른 객체에 대한 종속성을 애플리케이션 코드 외부에서(컨테이너가) 설정하게 함으로써 결합도(Coupling)를 낮추며 유연성과 확장성을 향상 시킬 수 있다는 장점이 있다.

소프트웨어 공학에서, 결합도(coupling) 또는 의존도는 어떤 모듈이 다른 모듈에 의존하는 정도를 나타내는 것이다.

참고

  • https://www.nextree.co.kr/p11247/

  • https://book.naver.com/bookdb/book_detail.nhn?bid=7006514