지난 번 작성된 글 제어의 역전(IOC) 이란? 에서 간단히 알아본 의존성 주입(Dependency Injection)
이란 무엇인지 함께 알아보는 시간을 가지도록 하겠습니다!
Dependency Injection이란?
의존성 주입(Dependency Injection)은 객체 지향 프로그래밍에서 사용되는 디자인 패턴입니다.
이 패턴은 코드의 결합도를 낮추고, 유연성과 재 사용성을 높이는 것입니다.
지난 게시글에서 알아봤던 것처럼, DI 패턴을 통해 제어의 역전이 일어나고, 이를 통해 코드의 재사용성 유연성이 향상되는 것이죠.
의존성 주입에 대해 알아보기 위해 먼저 의존성
이 무엇인지 살펴보겠습니다.
Dependency란?
의존성은 어떤 클래스가 제대로 작동하기 위해 필요한 다른 클래스나 컴포넌트를 말합니다.
이 의존성은 보통 코드에서 다른 객체의 형태로 존재하게 되는데요.
한 클래스가 다른 클래스 없이는 제대로 기능을 수행할 수 없을 때 이를 의존한다
라고 표현합니다.
코드를 통해 함께 살펴보겠습니다.
먼저 의존성이 될 수 있는 인터페이스를 정의해 보겠습니다.
현재의 요구사항은 여러 가지 방법으로 메세지를 보내야 하는 기능을 추가해야 하는 상황입니다.
이를 위한 인터페이스인 MessageService
가 있고 많은 메세지를 보내는 방법 중 하나인
이메일로 메세지를 보내기 위한 MessageService의 구현체 EmailService
가 있다고 가정해 봅시다.
public interface MessageService {
void sendMessage(String message, String receiver);
}
public class EmailService implements MessageService {
@Override
public void sendMessage(String message, Strigng 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 인터페이스의 구현체 없이는 MyApplication 의 proceessMessages 메서드가 제대로 기능을 수행할 수 없습니다.
의존성 주입을 통해 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", "12345678");
}
}
위에서 봤던 코드처럼 MyApplication 은 MessageService 인터페이스에 대한 의존성을 가지고 있습니다.
그리고 위에서 메세지를 보내는 방식이 이메일뿐만 아니라 문자 메시지 (SMS) 를 통한 메세지 전송 서비스 구현체가 있다면, 그대로 같은 코드에 구현체만 바꿔 끼워 준다면, 코드는 정상 동작을 할 것입니다
.
즉, MyApplication 은 MessageService 에 대한 의존성 만 가지고 있고 이메일 또는 SMS 전송 기능을 가진 그 구체적인 구현에 의존하지 않고 있다는 점입니다.
따라서 필요에 따라 그 구현체만 갈아끼워 주면 되는 형태인 것입니다. 이는 코드를 더 유연하게 재사용할 수 있게 되고, 또 소프트웨어의 유지 보수성과 확장성을 높이는데 큰 도움이 됩니다.
2. 테스트 용이성 향상
먼저 테스트를 위해 MessageService 의 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;
}
}
이제 MyApplication 의 기능을 테스트하기 위한 테스트 코드를 짜보겠습니다. 테스트에서는 MockMessageService 를 MyApplication 에 주입하여, 실제 메세지 전송 로직이 실행되지 않도록 합니다.
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("receiver@test.com", mockService.receiver);
}
}
이 테스트 코드를 보면 MockMessageService 는 실제 이메일 서비스를 대신해서 메시지 전송 로직을 가상으로 처리합니다.
이를 통해서 MyApplication 클래스가 의존하는 MessageService 의 구체적인 구현에 영향을 받지 않습니다.
이게 무슨 말이냐, 만약 MessageService 의 구현에 의존하는 형태에서 테스트를 진행한다면 테스트를 통과하지 않았을 때, ProcessMessages 메서드가 문제인지, 아니면 MessageService 가 문제인지를 한 번에 확인하기가 어렵습니다. 하지만 이렇게 의존성 주입 패턴을 활용하면 가짜 Mock 객체를 주입받아 사용하므로 다른 메서드의 영향을 받지 않고 메서드가 올바르게 동작하는지를 독립적으로 테스트할 수 있습니다.
DI는 스프링뿐만 아니라 개발 생태계 전체적으로 많이 맞닥뜨리게 되는 용어입니다.
DI가 주는 의미와 또 여러 장점들을 통해 깨끗하고 또 효율적으로 코드를 작성할 수 있도록 열심히 노력해야겠습니다! 💪💪💪
'Computer Science > 프로그래밍 이론 💬' 카테고리의 다른 글
일급 컬렉션이란? (0) | 2024.02.07 |
---|---|
비동기 프로그래밍 (0) | 2024.02.06 |
제어의 역전(IOC) 이란? (0) | 2024.02.06 |
객체지향 프로그래밍이란? (0) | 2024.02.02 |
안녕하세요, 저는 주니어 개발자 박석희 입니다. 언제든 하단 연락처로 연락주세요 😆