🤖 Computer Science

Dependency Injection이란?

date
Nov 13, 2023
slug
what-is-dependency-injection
author
status
Public
tags
Java
객체지향
Spring
DI
summary
Dependency Injection (의존성 주입) 에 대한 개념을 익혀봅시다
type
Post
thumbnail
제목을 입력해주세요_-001 (4).png
category
🤖 Computer Science
updatedAt
Nov 30, 2023 07:04 AM
지난번 작성된 글 제어의 역전(IoC)이란? 에서 간단히 알아본 의존성 주입Dependency Injection 이란 무엇인지 이 시간 함께 알아보는 시간을 가지도록 하겠습니다!

Dependency Injection이란?

의존성 주입(Dependency Injection)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴입니다.
이 패턴은 코드의 결합도를 낮추고, 유연성과 재 사용성을 높이는 것입니다.
지난 게시글에서 알아봤던 것처럼, DI 패턴을 통해 제어의 역전이 일어나고, 이를 통해 코드의 재사용성 유연성이 향상되는 것이죠.
의존성 주입에 대해 알아보기 위해 먼저 의존성 이 무엇인지 살펴보겠습니다.

Dependency란?

의존성은 어떤 클래스가 제대로 작동하기 위해 필요한 다른 클래스나 컴포넌트를 말합니다.
이 의존성은 보통 코드에서 다른 객체의 형태로 존재하게 되는데요.
한 클래스가 다른 클래스 없이는 제대로 기능을 수행할 수 없을 때 이를 의존한다 라고 표현합니다.
 
코드를 통해 함께 살펴보겠습니다.
먼저 의존성이 될 수 있는 인터페이스를 정의해 보겠습니다.
현재의 요구사항은 여러 가지 방법으로 메세지를 보내야 하는 기능을 추가해야 하는 상황입니다.
이를 위한 인터페이스인 MessageService 가 있고 많은 메세지를 보내는 방법 중 하나인
이메일로 메세지를 보내기 위한 MessageService 의 구현체 EmailService 가 있다고 가정해 봅시다.
public interface MessageService { void sendMessage(String message, String receiver); } public vlass EmailService implements MessageService { @Override public void sendMessage(String message, String receiver) { //이메일을 보내는 로직 System.out.println("Email sent to " + receiver + " with message: " + message); } }
 
다음으로, 의존성을 필요로 하는 클래스를 만들어보겠습니다.
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 클래스는 MessageService 를 사용하여 메세지를 전송하는 기능을 가지고 있습니다.
이때, MyApplication 클래스는 생성자를 통해 의존성을 주입받습니다.
 
마지막으로, 메인 클래스에서 (혹은 프레임워크에서) 실제로 의존성을 주입합니다.
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 클래스는 MessageService 인터페이스에 의존 하고 있습니다. 즉, MessageService 인터페이스의 구현체 없이는 MyApplicationproceessMessages 메서드가 제대로 기능을 수행할 수 없습니다.
의존성 주입을 통해 EmailService 객체를 MyApplication 에 주입함으로써, 클래스 간 결합도를 낮추고 유연성과 용이성을 향상시켜줍니다.
 
여기까지 이 글을 읽으셨다면, 의존성 주입이 어떤 의미를 가져다주는지를 아시는 분이 50% 그리고 그래서 의존성 주입이 무슨 장점을 가져다 주는데?라고 생각하시는 분이 50% 일 것이라고 예상이 됩니다.
왜냐면 제가 그랬거든요 ㅠㅋㅋㅋㅋㅋ
실제 의존성 주입이 가져다주는 이점을 코드를 통해 함께 살펴보겠습니다.

1. 코드 재사용성 향상 및 유지보수성 향상

public class MyApp { public static void main(String[] args) { MessageService emailService = new EmailService(); MyApplication appWithEmail = new MyApplication(emailService); appWithEmail.processMessages("Hello Email", "email@example.com"); MessageService smsService = new SMSService(); MyApplication appWithSMS = new MyApplication(smsService); appWithSMS.processMessages("Hello SMS", "123456789"); } }
위에서 봤던 코드처럼 MyApplicationMessageService 인터페이스에 대한 의존성을 가지고 있습니다.
그리고 위에서 메세지를 보내는 방식이 이메일뿐만 아니라 문자 메시지 (SMS) 를 통한 메세지 전송 서비스 구현체가 있다면, 그대로 같은 코드에 구현체만 바꿔 끼워 준다면, 코드는 정상 동작을 할 것입니다.
즉, MyApplicationMessageService 에 대한 의존성 가지고 있고 이메일 또는 SMS 전송 기능을 가진 그 구체적인 구현에 의존하지 않고 있다는 점입니다.
따라서 필요에 따라 그 구현체만 갈아끼워 주면 되는 형태인 것입니다. 이는 코드를 더 유연하게 재사용할 수 있게 되고, 또 소프트웨어의 유지 보수성과 확장성을 높이는데 큰 도움이 됩니다.

2. 테스트 용이성 향상

먼저 테스트를 위해 MessageServiceMock 객체를 만듭니다. 이 객체는 실제 메세지를 전송하는 대신 테스트가 진행될 수 있도록 돕는 가짜 객체입니다.
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; } }
이제 MyApplication 의 기능을 테스트하기 위한 테스트 코드를 짜보겠습니다. 테스트에서는 MockMessageServiceMyApplication 에 주입하여, 실제 메세지 전송 로직이 실행되지 않도록 합니다.
import static org.junit.Assert.assertEquals; import org.junit.Test; 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("receiber@test.com", mockService.receiver); } }
이 테스트 코드를 보면 MockMessageService 는 실제 이메일 서비스를 대신해서 메시지 전송 로직을 가상으로 처리합니다.
이를 통해서 MyApplication 클래스가 의존하는 MessageService 의 구체적인 구현에 영향을 받지 않습니다.
이게 무슨 말이냐, 만약 MessageService 의 구현에 의존하는 형태에서 테스트를 진행한다면 테스트를 통과하지 않았을 때, ProcessMessages 메서드가 문제인지, 아니면 MessageService 가 문제인지를 한 번에 확인하기가 어렵습니다. 하지만 이렇게 의존성 주입 패턴을 활용하면 가짜 Mock 객체를 주입받아 사용하므로 다른 메서드의 영향을 받지 않고 메서드가 올바르게 동작하는지를 독립적으로 테스트할 수 있습니다.
 
DI는 스프링뿐만 아니라 개발 생태계 전체적으로 많이 맞닥뜨리게 되는 용어입니다.
DI가 주는 의미와 또 여러 장점들을 통해 깨끗하고 또 효율적으로 코드를 작성할 수 있도록 열심히 노력해야겠습니다! 💪💪💪