
의존성 주입(Dependency Injection, DI)은 객체 지향 프로그래밍에서 널리 사용되는 설계 기법입니다.
처음 보면 이해하기 어렵게 느껴지지만, 핵심은 객체가 필요한 의존성을 직접 생성하지 않고 외부에서 전달받도록 만드는 것입니다.
이 글에서는 DI의 개념, 필요성, 그리고 실제 코드 적용 방법을 예제를 통해 차례로 설명합니다.
이를 통해 DI가 제공하는 유연성, 확장성, 테스트 용이성을 자연스럽게 이해할 수 있습니다.
Dependency Injection이란?
Dependency Injection(DI)은 객체가 필요한 의존성을 직접 생성하지 않고
외부에서 주입받는 설계 방식입니다.
이 방식에서 객체는 무엇이 필요한지만 정의하고,
어떻게 생성되는지는 외부에 맡깁니다.
DI를 적용하면 다음과 같은 효과를 얻을 수 있습니다.
- 코드 결합도가 낮아진다.
- 구현체를 쉽게 교체할 수 있다.
- 테스트가 쉬워진다.
- 확장성과 유지보수성이 향상된다.
DI를 이해하기 위한 기본 개념: 의존성(Dependency)
DI를 이해하시려면 먼저 의존성(Dependency) 개념을 알아두어야 합니다.
의존성이란 한 클래스가 기능을 수행하기 위해 필요한 다른 객체를 의미합니다.
예를 들어, 메시지를 전송하는 프로그램이 있다고 해보겠습니다.
이 프로그램은 메시지 내용과 수신자를 입력받고,
실제로 전송을 처리하는 MessageService를 호출해야 합니다.
따라서 MyApplication은 MessageService에 의존한다고 말합니다.
코드로 살펴보는 DI
메시지 전송을 위한 인터페이스와 구현체
public interface MessageService {
void sendMessage(String message, String receiver);
}
public class EmailService implements MessageService {
@Override
public void sendMessage(String message, String receiver) {
System.out.println("Email sent to " + receiver + " with message: " + message);
}
}
MessageService는 메시지 전송 기능을 정의하는 인터페이스이며,EmailService는 이 기능을 실제로 구현한 클래스입니다.
의존성을 주입받는 클래스
public class MyApplication {
private MessageService service;
public MyApplication(MessageService svc) {
this.service = svc;
}
public void processMessages(String msg, String rec) {
this.service.sendMessage(msg, rec);
}
}
MyApplication은 메시지를 전송해야 하지만,
이메일·SMS 등 어떤 방식으로 전송되는지는 알 필요가 없습니다.
필요한 것은 오직 MessageService 인터페이스뿐입니다.
실제 의존성 주입
public class MyApp {
public static void main(String[] args) {
MessageService emailService = new EmailService();
MyApplication app = new MyApplication(emailService);
app.processMessages("Hello Dependency Injection", "receiver@example.com");
}
}
여기서 핵심은 MyApplication이 EmailService를 직접 생성하지 않는다는 점입니다.
필요한 구현체는 외부(main 메서드 또는 DI 컨테이너)에서 주입해줍니다.
이렇게 하면 구현체 교체가 쉬워지고 테스트도 용이해집니다.
DI가 제공하는 핵심 장점
재사용성과 유지보수성 향상
DI를 사용하면 MyApplication은 구현체가 아닌 인터페이스에만 의존합니다.
예를 들어, 메시지를 이메일이 아니라 SMS로 보내고 싶다면 단순히 다른 구현체를 주입하면 됩니다.
MessageService smsService = new SMSService();
MyApplication appWithSMS = new MyApplication(smsService);
애플리케이션 로직을 수정하지 않고 동작만 교체할 수 있는 구조가 됩니다.
이는 DI와 인터페이스 기반 설계가 제공하는 대표적인 장점입니다.
테스트 용이성 향상
DI는 테스트에서도 큰 효과를 발휘합니다.
실제 이메일을 매번 전송하면서 테스트할 수는 없습니다. 비용이 들고, 속도가 느리며, 실패 요소도 많기 때문입니다.
이럴 때는 실제 구현체 대신 Mock 객체를 주입하여 테스트할 수 있습니다.
이를 통해 안전하고 빠르게 애플리케이션의 로직을 검증할 수 있습니다.
Mock 객체를 이용한 DI 테스트 예시
Mock 구현체
public class MockMessageService implements MessageService {
public String message;
public String receiver;
@Override
public void sendMessage(String message, String receiver) {
this.message = message;
this.receiver = receiver;
}
}
Mock 객체는 실제 기능을 수행하지 않고, 호출 여부와 전달된 값만 기록하는 가짜 객체입니다.
MyApplication 테스트 코드
public class MyApplicationTest {
@Test
public void testProcessMessages() {
MockMessageService mockService = new MockMessageService();
MyApplication app = new MyApplication(mockService);
app.processMessages("test", "receiver@test.com");
assertEquals("test", mockService.message);
assertEquals("receiver@test.com", mockService.receiver);
}
}
이 테스트는 실제 이메일 전송 없이,MyApplication이 MessageService를 올바르게 호출했는지만 검증합니다.
즉, DI를 사용하면 외부 의존성을 분리할 수 있어
테스트를 더 독립적, 빠르고, 안정적으로 만들 수 있습니다.
DI는 단순히 객체를 주입하는 기술이 아니라,
유지보수성과 테스트 가능성을 높이는 핵심 설계 철학입니다.
이 원리를 이해해두시면 스프링 같은 프레임워크를 다룰 때도 훨씬 수월하게 접근하실 수 있습니다.
DI를 자연스럽게 활용할 수 있도록 꾸준히 연습해보시기 바랍니다. 💪
'Computer Science > 프로그래밍 이론 💬' 카테고리의 다른 글
| 일급 컬렉션이란? (0) | 2024.02.07 |
|---|---|
| 비동기 프로그래밍 이해하기 - 동기 방식과 뭐가 다를까? (0) | 2024.02.06 |
| 제어의 역전(IOC) 톺아 보기 (0) | 2024.02.06 |
| 객체지향 프로그래밍이란? (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆