Menu
Coddy logo textTech

의존성 주입

Coddy PHP 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 91개 중 57번째.

이전 레슨에서, 여러분은 컴포지션(composition)을 통해 CarEngine 객체를 포함하는 방법을 살펴보았습니다. 하지만 클래스 내부에서 의존성을 생성하는 것에는 문제가 있습니다. 이는 코드를 경직되게 만들고 테스트하기 어렵게 만듭니다. 의존성 주입(Dependency Injection)은 의존성을 내부에서 생성하는 대신 외부에서 전달함으로써 이 문제를 해결합니다.

이 두 가지 접근 방식을 비교해 보세요:

<?php
// DI 없음 - 내부에서 의존성 생성
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // 강하게 결합됨
    }
}

// DI 사용 - 의존성이 외부에서 전달됨
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // 외부에서 주입됨

두 번째 접근 방식은 의존성 주입(dependency injection)입니다. Car는 자체적으로 엔진을 생성하지 않고, 엔진을 전달받습니다. 이 간단한 변화는 강력한 이점을 제공합니다. 다양한 엔진 유형을 전달할 수 있고, 테스트를 위해 구현을 교체할 수 있으며, 클래스가 더 유연해집니다.

더 큰 유연성을 위해 구체적인 클래스 대신 인터페이스를 주입하세요:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

이제 Car는 가솔린 엔진, 전기 모터 또는 테스트를 위한 목(mock) 객체 등 EngineInterface를 구현하는 모든 클래스와 함께 작동합니다. 이러한 결합 해제(decoupling)는 의존성 주입이 유지 관리 가능하고 테스트 가능한 PHP 애플리케이션을 작성하는 데 필수적인 이유입니다.

challenge icon

챌린지

쉬움

의존성 주입(Dependency Injection)의 강력함을 보여주는 알림 시스템을 구축해 보겠습니다. 메시지 전송 방식을 하드코딩하는 대신, 전송 방법을 쉽게 교체할 수 있는 유연한 시스템을 만들 것입니다.

코드는 다음 네 개의 파일로 구성됩니다:

  • MessageSenderInterface.phpsend(string $recipient, string $message): string이라는 단일 메서드를 가진 MessageSenderInterface 인터페이스를 정의합니다. 이 인터페이스는 모든 메시지 전송 도구가 따라야 할 계약을 설정합니다.
  • EmailSender.phpMessageSenderInterface를 구현하는 EmailSender 클래스를 생성합니다. 인터페이스 파일을 포함(include)해야 합니다. send() 메서드는 "Email to $recipient: $message"를 반환해야 합니다.
  • NotificationService.php — 생성자를 통해 의존성을 주입받는 NotificationService 클래스를 생성합니다. 인터페이스 파일을 포함해야 합니다. 이 클래스는 다음을 수행해야 합니다:
    • 생성자 홍보(constructor promotion)를 사용하여 생성자에서 MessageSenderInterface를 받습니다.
    • 주입된 전송 도구에 작업을 위임하고 그 결과를 반환하는 notify(string $recipient, string $message) 메서드를 가집니다.
    NotificationService가 구체적인 클래스가 아닌 인터페이스에 의존하고 있음에 주목하세요. 이는 서비스를 변경하지 않고도 인터페이스를 구현하는 어떤 전송 도구든 주입할 수 있음을 의미합니다.
  • main.php — EmailSender와 NotificationService 파일을 포함합니다. 수신자(recipient)와 메시지(message)라는 두 개의 입력을 받게 됩니다. EmailSender를 생성하고, 이를 NotificationService에 주입한 다음, notify()를 호출하여 결과를 출력하세요.

이 패턴은 NotificationService를 유연하게 유지해 줍니다. 이 서비스는 이메일, SMS 또는 아직 만들지 않은 그 어떤 전송 도구를 주입하더라도 잘 작동합니다. 서비스는 스스로 의존성을 생성하지 않고 외부로부터 전달받습니다.

치트 시트

의존성 주입(Dependency Injection, DI)은 의존성을 내부에서 생성하는 대신 외부에서 클래스로 전달하는 기술입니다.

DI가 없는 경우 (강한 결합):

<?php
class Car {
    private Engine $engine;
    
    public function __construct() {
        $this->engine = new Engine(); // 내부에서 생성됨
    }
}

DI를 사용하는 경우 (느슨한 결합):

<?php
class Car {
    public function __construct(private Engine $engine) {}
}

$engine = new Engine();
$car = new Car($engine); // 외부에서 주입됨

최대한의 유연성을 위해 구체적인 클래스 대신 인터페이스를 주입하세요:

<?php
interface EngineInterface {
    public function start(): string;
}

class Car {
    public function __construct(private EngineInterface $engine) {}
    
    public function start(): string {
        return $this->engine->start();
    }
}

이를 통해 CarEngineInterface를 구현하는 모든 클래스와 함께 작동할 수 있으며, 코드를 더 유지보수하기 쉽고 테스트 가능하게 만듭니다.

직접 해보기

<?php
require_once 'EmailSender.php';
require_once 'NotificationService.php';

// 입력 읽기
$recipient = trim(fgets(STDIN));
$message = trim(fgets(STDIN));

// TODO: EmailSender 인스턴스 생성
// TODO: NotificationService에 주입
// TODO: notify()를 호출하고 결과 출력

?>
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨