Menu
Coddy logo textTech

Dependency Injection 기초

Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 70개 중 46번째.

Dependency Injection(DI)은 클래스가 의존성을 직접 생성하는 대신 외부에서 받는 기법입니다. 이를 통해 코드를 더 유연하고 테스트하기 쉬우며 결합도가 낮게 만들 수 있습니다.

DI 없이 클래스는 자체 dependency를 생성하므로 변경하거나 테스트하기 어렵습니다:

public class OrderService
{
    private EmailSender sender = new EmailSender();  // 강하게 결합됨
    
    public void PlaceOrder() => sender.Send("Order placed");
}

DI를 사용하면 의존성이 생성자를 통해 전달됩니다:

public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message) => Console.WriteLine("Email: " + message);
}

public class OrderService
{
    private IMessageSender sender;
    
    public OrderService(IMessageSender sender)
    {
        this.sender = sender;
    }
    
    public void PlaceOrder() => sender.Send("Order placed");
}

이제 OrderService는 Email, SMS 또는 테스트를 위한 모의 발신자 중 무엇을 사용하는지 알지도 못하고 신경 쓰지도 않습니다. 객체가 생성될 때 dependency가 "injected"됩니다.

IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder();  // 이메일: 주문 완료

이 패턴은 구체적인 구현이 아니라 추상화(인터페이스)에 의존한다는 원칙을 따릅니다. 이 패턴을 사용하면 이를 사용하는 클래스를 수정하지 않고도 구현을 교체할 수 있으므로, 단위 테스트를 수행하고 유연한 아키텍처를 유지하는 데 필수적입니다.

challenge icon

챌린지

쉬움

의존성 주입의 강력함을 보여 주는 Logging 시스템을 만들어 봅시다. 로그가 기록되는 방식을 하드코딩하는 대신, 세부 사항을 알 필요 없이 어떤 Logging 대상(콘솔, file 시뮬레이션 또는 그 밖의 무엇이든)과도 작동할 수 있는 유연한 Logger class를 만들게 됩니다.

코드를 세 개의 file에 나누어 구성합니다.

  • ILogWriter.cs: Logging namespace에 ILogWriter라는 interface를 Define합니다. 이 interface는 모든 Logging 대상에 대한 계약을 나타내며, 메시지가 어디에 기록되었는지를 설명하는 string을 반환하는 단일 method Write(string message)를 가져야 합니다.
  • LogWriters.cs: Logging namespace에 ILogWriter의 두 가지 구체적인 구현을 Create합니다.
    • ConsoleLogWriter - 해당 Write method는 Console: {message}를 반환합니다.
    • FileLogWriter - constructor를 통해 filename을 받고, 해당 Write method는 File[{filename}]: {message}를 반환합니다.
  • Logger.cs: Logging namespace에 constructor injection을 통해 dependency를 받는 Logger class를 Create합니다. Logger는 다음을 수행해야 합니다.
    • constructor를 통해 ILogWriter를 받고 이를 private field에 저장합니다.
    • injected writer에 delegates하고 그 결과를 반환하는 Log(string message) method를 have합니다.
  • Program.cs: 서로 다른 두 개의 logger를 Create하여 dependency injection을 보여 줍니다. 하나는 ConsoleLogWriter를 사용하고 다른 하나는 FileLogWriter를 사용합니다. 어떤 dependency가 injected되었는지에 따라 동일한 Logger class가 다르게 작동하는 모습을 보여 줍니다.

두 개의 inputs를 받습니다.

  • file logger의 filename
  • 기록할 message

ConsoleLogWriter를 사용하여 Logger를 Create하고 message를 기록합니다. 그런 다음 (input의 filename을 사용하여) FileLogWriter를 사용하는 또 다른 Logger를 Create하고 동일한 message를 기록합니다. 각 결과를 한 줄씩 Print합니다.

예를 들어 inputs가 app.logSystem started라면 output은 다음과 같아야 합니다.

Console: System started
File[app.log]: System started

Logger class 자체는 전혀 변경되지 않는다는 점에 주목하세요. 어떤 ILogWriter 구현과도 작동합니다. 이것이 dependency injection의 본질입니다. Logger는 abstraction(interface)에 의존하고, 구체적인 구현은 외부에서 "injected"됩니다. 나중에 Logger class를 전혀 수정하지 않고도 DatabaseLogWriter 또는 CloudLogWriter를 쉽게 Add할 수 있습니다!

직접 해보기

using System;
using Logging;

class Program
{
    public static void Main(string[] args)
    {
        // 입력 읽기
        string filename = Console.ReadLine();
        string message = Console.ReadLine();

        // TODO: ConsoleLogWriter로 Logger를 생성하고 메시지를 로그하세요
        // 결과 출력

        // TODO: FileLogWriter로 또 다른 Logger를 생성하고 (filename 사용) 같은 메시지를 로그하세요
        // 결과 출력
    }
}
quiz icon실력 점검

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

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

직접 연습해 보세요: 온라인 C# 컴파일러