Dependency Injection 기초
Coddy C# 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 70개 중 46번째.
의존성 주입 (Dependency Injection, DI)은 클래스가 스스로 의존성을 생성하는 대신 외부로부터 의존성을 전달받는 기법입니다. 이를 통해 코드는 더 유연해지고, 테스트 가능해지며, 결합도가 낮아집니다.
DI 없이 클래스가 스스로 의존성을 생성하면, 변경이나 테스트가 어려워집니다:
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는 테스트를 위해 이메일, SMS 또는 모의(mock) 발송자를 사용하는지 여부를 알 필요도 없고 신경 쓰지도 않습니다. 객체가 생성될 때 의존성이 "주입"됩니다:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // 이메일: 주문이 완료되었습니다이 패턴은 구체적인 구현보다는 추상화(인터페이스)에 의존한다는 원칙을 따릅니다. 이는 해당 구현을 사용하는 클래스를 수정하지 않고도 구현을 교체할 수 있게 해주며, 이는 단위 테스트와 유연한 아키텍처를 유지하는 데 필수적입니다.
챌린지
쉬움의존성 주입(Dependency Injection)의 강력함을 보여주는 로깅 시스템을 구축해 봅시다. 로그가 작성되는 방식을 하드코딩하는 대신, 구체적인 내용을 알지 못해도 콘솔, 파일 시뮬레이션 등 모든 로깅 대상과 함께 작동할 수 있는 유연한 Logger 클래스를 만들 것입니다.
코드는 세 개의 파일로 구성됩니다:
ILogWriter.cs:Logging네임스페이스에ILogWriter라는 인터페이스를 정의합니다. 이 인터페이스는 모든 로깅 대상에 대한 계약을 나타내며, 메시지가 작성된 위치를 설명하는 문자열을 반환하는 단일 메서드Write(string message)를 가져야 합니다.LogWriters.cs:Logging네임스페이스에ILogWriter의 두 가지 구체적인 구현체를 만듭니다:ConsoleLogWriter-Write메서드는"Console: " + message를 반환합니다.FileLogWriter- 생성자를 통해 파일 이름을 전달받으며,Write메서드는"File[" + filename + "]: " + message를 반환합니다.
Logger.cs: 생성자 주입을 통해 의존성을 전달받는Logger클래스를Logging네임스페이스에 만듭니다.Logger는 다음을 수행해야 합니다:- 생성자를 통해
ILogWriter를 받아 프라이빗 필드에 저장합니다. - 주입된 writer에게 작업을 위임하고 그 결과를 반환하는
Log(string message)메서드를 가집니다.
- 생성자를 통해
Program.cs:ConsoleLogWriter를 사용하는 로거와FileLogWriter를 사용하는 로거, 두 개의 서로 다른 로거를 생성하여 의존성 주입을 시연합니다. 주입된 의존성에 따라 동일한Logger클래스가 어떻게 다르게 동작하는지 보여줍니다.
두 개의 입력을 받게 됩니다:
- 파일 로거에 사용할 파일 이름
- 로그로 남길 메시지
ConsoleLogWriter를 사용하여 Logger를 생성하고 메시지를 로그로 남깁니다. 그런 다음 (입력받은 파일 이름을 사용하여) FileLogWriter를 사용하는 또 다른 Logger를 생성하고 동일한 메시지를 로그로 남깁니다. 각 결과를 별도의 줄에 출력하세요.
예를 들어, 입력이 app.log와 System started라면 출력은 다음과 같아야 합니다:
Console: System started
File[app.log]: System startedLogger 클래스가 전혀 변경되지 않는다는 점에 주목하세요. 이 클래스는 어떤 ILogWriter 구현체와도 작동합니다. 이것이 의존성 주입의 본질입니다. Logger는 추상화(인터페이스)에 의존하고, 구체적인 구현은 외부에서 "주입"됩니다. 나중에 Logger 클래스를 전혀 수정하지 않고도 DatabaseLogWriter나 CloudLogWriter를 쉽게 추가할 수 있습니다!
치트 시트
의존성 주입(Dependency Injection, DI)은 클래스가 스스로 의존성을 생성하는 대신 외부로부터 의존성을 전달받는 기술로, 코드를 더 유연하고 테스트 가능하며 결합도를 낮게 만듭니다.
DI가 없는 경우 (강한 결합):
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");
}의존성 주입하기:
IMessageSender emailSender = new EmailSender();
OrderService service = new OrderService(emailSender);
service.PlaceOrder(); // Email: Order placedDI는 구체적인 구현이 아닌 추상화(인터페이스)에 의존한다는 원칙을 따르며, 이를 통해 사용하는 클래스를 수정하지 않고도 구현체를 교체할 수 있게 합니다.
직접 해보기
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를 사용하는 (filename을 이용한) 또 다른 Logger를 생성하고 동일한 메시지를 기록합니다
// 결과 출력
}
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8심화 OOP 개념
상속보다 합성(Composition)제네릭 (클래스 및 메서드)델리게이트와 이벤트어트리뷰트와 리플렉션IDisposable과 using 문Dependency Injection 기초