본문 바로가기

SPRING/Spring

[스프링| 스프링 핵심 원리 | 기본편 | 스프링 핵심 원리 이해2 - 객체 지향 원리 적용]IoC, DI, 그리고 컨테이너

제어의 역전 (Inversion of Control, IoC)

제어의 역전은 프로그래밍에서 주로 사용되는 디자인 패턴 중 하나로, 프로그램의 흐름 제어를 사용자가 아닌 외부 시스템이 관리하는 원칙을 말해. 전통적인 프로그래밍에서는 사용자가 프로그램의 흐름을 제어하고, 언제 어떤 객체를 생성하고 사용할지 직접 관리하는데, IoC에서는 이러한 제어권이 프레임워크 같은 외부 시스템으로 넘어간다고 생각하면 돼.

예를 들어, 스프링 프레임워크에서는 개발자가 직접 객체를 생성하고 소멸시키는 대신, 스프링 컨테이너가 이런 작업들을 대신 관리해주는데, 이렇게 되면 개발자는 비즈니스 로직에 더 집중할 수 있고, 코드는 더욱 깔끔하고 유지 관리가 쉬워진다.

 

라이브러리

라이브러리는 개발자가 작성하는 애플리케이션 코드에서 호출되어 사용되는 일련의 함수나 데이터를 제공하는 모듈이야. 여기서 중요한 점은, 개발자가 애플리케이션의 흐름을 직접 제어하며, 필요할 때 라이브러리의 기능을 선택적으로 호출한다는 거야. 즉, 라이브러리는 개발자가 주도하는 코드 실행 구조에서 일부 기능을 보조하는 역할을 한다.

프레임워크

반면, 프레임워크는 제어의 역전(IoC) 원칙을 적용한 구조를 가지고 있어. 프레임워크는 애플리케이션의 실행 흐름을 스스로 관리하고, 개발자가 프레임워크에 정의된 규칙이나 인터페이스를 따라 코드를 작성하도록 요구해. 프레임워크는 프로그램의 흐름을 제어하며, 특정 시점에 개발자의 코드를 필요에 따라 호출하게 되는데, 이것이 제어의 역전이라는 개념이 적용된 예라고 할 수 있어.

IoC를 통한 설명

제어의 역전을 사용하여 프레임워크와 라이브러리를 구분하는 것은, 누가 '프로그램의 흐름'을 관리하는가에 대한 차이에서 기인해. 라이브러리를 사용할 때는 개발자가 애플리케이션의 흐름을 완전히 제어하고, 필요한 기능이 있을 때 라이브러리를 호출한다. 반면 프레임워크는 애플리케이션 코드의 실행을 스스로 책임지고, 개발자가 작성한 코드를 특정 시점에 호출하는 방식으로 작동한다.

이러한 차이는 개발자가 얼마나 많은 제어를 소프트웨어에 직접적으로 행사할 수 있는지, 그리고 그 제어가 외부에 의해 어떻게 관리되는지를 중요하게 만들어. 프레임워크는 구조 내에서 많은 것을 관리하기 때문에, 프로젝트의 초기 설정과 지속적인 유지 관리가 라이브러리를 사용하는 경우보다 더 복잡할 수 있지만, 보다 일관된 아키텍처와 더 적은 코드 중복을 제공할 수 있어.

 
 
 

의존관계 주입 (Dependency Injection, DI)

의존관계 주입은 객체지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로, 객체의 생성과 그 사용을 분리하여 객체 간의 결합도를 낮추고 유연성 및 재사용성을 높이는 방법이야. DI를 통해 각 객체는 자신이 필요로 하는 의존 객체(종속성)를 외부에서 받게 되며, 이는 주로 생성자, 메소드, 또는 필드를 통해 주입될 수 있어.

의존관계 주입을 사용함으로써 개발자는 더 테스트하기 쉽고 관리하기 편한 코드를 작성할 수 있으며, 코드의 모듈성도 향상된다. 의존성이 주입되면, 객체는 해당 의존성을 제공하는 외부 시스템(예: 프레임워크)에 의존하게 되므로, 구체적인 객체 생성에 대한 처리는 신경 쓸 필요가 없어져.

정적인 클래스 의존관계

정적인 클래스 의존관계는 컴파일 시점에 결정되며, 클래스 간의 관계를 의미해. 이는 한 클래스가 다른 클래스를 사용하는 관계를 포함하는데, 예를 들어 클래스 A가 클래스 B의 메소드를 호출하거나 B의 인스턴스를 생성할 때 발생해. 정적 의존관계는 소스 코드 수준에서 명확하게 확인할 수 있으며, 주로 import 문을 통해 확인할 수 있어.

동적인 객체 인스턴스 의존관계

반면, 동적인 객체 인스턴스 의존관계는 런타임 시점에 결정되며, 실제 객체 인스턴스 간의 관계를 말해. 예를 들어, 객체 A가 인터페이스 I를 통해 서비스를 요청하고, 런타임 시점에 실제로 서비스를 제공하는 객체 B가 그 인터페이스 I를 구현하고 있을 경우, A는 B의 인스턴스에 의존하게 돼. 이러한 의존관계는 객체의 생명 주기와 관련이 있으며, 의존성 주입을 통해 관리될 수 있어.

이 두 관계를 통해, 정적 의존관계는 주로 코드와 설계의 구조적 측면을 다루는 반면, 동적 의존관계는 실행 시 객체들의 실제 상호작용과 유연성을 다루는데 중점을 둔다. 이를 통해, 개발자는 애플리케이션의 구성 요소 간의 결합도를 낮추고, 확장성과 유지 보수성을 향상시킬 수 있어.

 

DI 컨테이너 (Dependency Injection Container)

DI 컨테이너는 의존성 주입을 자동화하는 프레임워크 또는 라이브러리의 일부로, 응용 프로그램의 객체들 간의 의존 관계를 관리하고 조정하는 역할을 한다. DI 컨테이너는 객체를 생성하고, 그 객체가 필요로 하는 의존성을 주입하여, 각 객체가 올바르게 연결될 수 있도록 도와준다.

DI 컨테이너의 주요 기능

  1. 객체의 생성과 생명 주기 관리: DI 컨테이너는 등록된 클래스의 객체를 생성하고, 필요에 따라 관리(예: 싱글톤, 프로토타입)한다. 이는 객체의 생성부터 소멸까지의 전 과정을 컨테이너가 책임진다는 것을 의미한다.
  2. 의존성 자동 주입: 컨테이너는 객체 생성 시 필요한 의존성을 자동으로 주입한다. 이는 생성자 주입, 세터 주입, 혹은 필드 주입을 통해 이루어질 수 있다. 개발자는 의존성의 구체적인 생성 방법을 명시하지 않아도, 컨테이너가 알아서 적절한 의존 객체를 제공한다.
  3. 구성 용이성: 컨테이너는 구성 파일이나 어노테이션을 통해 어떤 객체가 어떤 의존성을 필요로 하는지 정의할 수 있다. 이로 인해 개발자는 코드 수정 없이도 의존성을 변경하거나 업데이트할 수 있다.
  4. 통합과 표준화: 다양한 프레임워크와 라이브러리가 일관된 방식으로 의존성을 처리할 수 있도록 도와줌으로써, 코드의 일관성과 재사용성을 향상시킨다.