inversion of control
제어 반전(Inversion of Control, IoC)은 SOLID 원칙 중 하나인 의존성 역전 원칙(Dependency Inversion Principle, DIP)을 구현하는 핵심 개념이다. 제어 반전에서는 객체 생성과 실행 흐름을 제어하는 주체가 개발자가 아니라 프레임워크나 외부 컨테이너로 넘어가게 된다. 즉, 원래는 개발자가 직접 객체를 만들고 메서드를 호출했지만, 제어 반전을 적용하면 이 제어권이 역전되어 개발자가 아닌 다른 주체(라이브러리, 이벤트 루프, 프레임워크 등)가 객체를 관리하고 필요한 시점에 실행하게 된다. 이를 통해 코드의 결합도를 낮추고 확장성과 테스트 용이성을 높일 수 있다.
제어 반전을 달성하기 위해 네 가지 방법이 주로 사용된다. 첫 번째는 의존성 주입(Dependency Injection, DI)이다. DI는 객체 간의 의존 관계를 외부에서 설정해주어, 클래스가 필요한 객체를 직접 생성하지 않고, 외부에서 주입받도록 하는 방식이다. 두 번째는 이벤트 기반(Event-driven) 프로그래밍이다. 이 방식은 시스템이 발생한 이벤트에 반응하도록 하여 실행 흐름을 제어한다. 세 번째는 콜백 함수(callback function) 를 활용하는 방법이다. 콜백 함수는 특정 이벤트나 조건에 맞춰 실행될 함수를 미리 등록해두고, 조건이 맞을 때 해당 함수를 호출하는 방식이다. 마지막으로 템플릿 메서드 패턴을 들 수 있다. 이 패턴은 알고리즘의 구조는 부모 클래스가 정의하고, 세부 구현은 자식 클래스가 담당하는 방식으로, 제어 흐름을 부모 클래스가 제어하게 만든다. 이러한 방식들은 모두 개발자가 직접 제어하지 않고, 외부 시스템이나 프레임워크가 제어하게 함으로써, 코드의 결합도를 낮추고 확장성을 높이는 데 기여한다.
의존성 주입
의존성 주입(DI)은 제어 반전을 구현하는 대표적인 방법으로, 외부에서 객체를 주입받는 방식이다. 아래 예시에서는 NestJS의 DI를 간단히 구현한 코드이다. 이 예시에서 Controller 클래스는 Service 객체를 직접 생성하지 않고, 생성자에서 Service 객체를 주입받는다. 이처럼 의존성 주입을 통해 객체 간의 결합도를 낮추고, 코드의 유연성과 확장성을 높일 수 있다.
// Controller 클래스 class Controller { constructor(private service: Service) {} // 의존성 주입 handleRequest() { this.service.doSomething(); // 서비스 로직 실행 } }
템플릿 메서드 패턴
템플릿 메서드 패턴은 알고리즘의 구조는 부모 클래스가 정의하고, 구체적인 동작은 자식 클래스가 담당하는 방식이다. 부모 클래스가 제어의 흐름을 관리하고, 자식 클래스는 일부 구현만 담당하게 된다. 아래의 예시에서는 CaffeineBeverage 클래스가 알고리즘의 흐름을 정의하고, brew()와 addCondiments() 메서드는 자식 클래스에서 구현된다. 부모 클래스는 알고리즘의 순서를 관리하지만, 세부적인 동작은 자식 클래스가 결정하게 된다.
abstract class CaffeineBeverage { // 템플릿 메서드: 알고리즘의 틀을 정의 prepareRecipe(): void { this.boilWater(); this.brew(); // 자식 클래스에서 구현 this.pourInCup(); this.addCondiments(); // 자식 클래스에서 구현 } private boilWater(): void { console.log("물을 끓입니다."); } private pourInCup(): void { console.log("컵에 따릅니다."); } // 추상 메서드: 자식 클래스에서 구현해야 함 protected abstract brew(): void; protected abstract addCondiments(): void; } class Coffee extends CaffeineBeverage { protected brew(): void { console.log("커피를 내립니다."); } protected addCondiments(): void { console.log("설탕과 우유를 추가합니다."); } } class Tea extends CaffeineBeverage { protected brew(): void { console.log("차를 우려냅니다."); } protected addCondiments(): void { console.log("레몬을 추가합니다."); } } // 사용 예시 const coffee = new Coffee(); coffee.prepareRecipe(); // 출력: // 물을 끓입니다. // 커피를 내립니다. // 컵에 따릅니다. // 설탕과 우유를 추가합니다. const tea = new Tea(); tea.prepareRecipe(); // 출력: // 물을 끓입니다. // 차를 우려냅니다. // 컵에 따릅니다. // 레몬을 추가합니다.
콜백 함수
콜백 함수는 다른 함수의 인자로 전달되어 특정 작업이 완료된 후 실행되는 함수이다. 아래 예제는 setInterval을 사용하여 일정 시간마다 콜백 함수를 실행하는 코드이다. 이 코드에서 repeatTask 함수는 콜백 함수를 받아 일정한 간격으로 실행하는 역할을 한다. setInterval을 사용하면 지정된 주기(interval)마다 task가 실행되며, 여기서는 sayHello 함수가 1초마다 호출된다. 이처럼 setInterval을 활용하면 콜백 함수를 반복적으로 실행할 수 있으며, 실행의 흐름을 setInterval이 제어하게 된다.
function repeatTask(task: () => void, interval: number): void { setInterval(task, interval); } // 실행할 콜백 함수 function sayHello(): void { console.log("안녕하세요! 1초마다 실행됩니다."); } // 1초마다 `sayHello` 실행 repeatTask(sayHello, 1000);
이벤트 기반 프로그래밍
이벤트 기반 프로그래밍은 특정 이벤트가 발생할 때까지 기다리며, 그 이벤트가 발생하면 등록된 콜백 함수를 실행하는 방식이다. 이를 통해 프로그램의 흐름을 외부에서 제어할 수 있다. 아래 예시에서는 addEventListener를 사용하여 버튼의 클릭 이벤트에 대한 리스너를 등록했다. 이벤트 기반 프로그래밍에서는 프로그램의 흐름을 외부 이벤트(여기서는 버튼 클릭)가 제어하며, 해당 이벤트가 발생할 때마다 등록된 콜백 함수가 실행된다.
function handleClick(event: Event): void { console.log("버튼이 클릭되었습니다!"); } // DOM 요소에 이벤트 리스너 등록 const button = document.querySelector("button"); if (button) { button.addEventListener("click", handleClick); }
블로그의 정보
Ayden's journal
Beard Weard Ayden