ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DI(Dependency Injection)란?
    Design Pattern/생성 디자인 패턴 2024. 4. 11. 12:53
    반응형

     종속성 주입(Dependency Injection, DI)은 소프트웨어 설계 패턴 중 하나로, 코드를 더 유연하고 테스트하기 쉽게 만들어줍니다. 이 패턴은 개발자가 프로젝트에 필요한 서비스나 모듈을 직접 생성하고 관리하지 않아도 되게 해 줍니다. 대신, 이러한 서비스나 모듈은 필요할 때 외부에서 '주입'되어 사용됩니다. 이렇게 함으로써, 코드 간의 결합도가 낮아지고, 유닛 테스트와 코드 재사용성이 향상됩니다.

     

     예를 들어, 어떤 애플리케이션에서 데이터베이스 접근 로직이 필요하다고 해봅시다. 종속성 주입을 사용하지 않는 경우, 해당 로직을 필요로 하는 각 클래스 내에서 데이터베이스 연결을 직접 생성하고 관리해야 합니다. 하지만 DI를 사용하면, 데이터베이스 접근 로직을 구현한 클래스(서비스)를 애플리케이션의 다른 부분에 '주입'할 수 있습니다. 이 경우, 데이터베이스 연결을 관리하는 코드는 한 곳에만 존재하게 되며, 이를 필요로 하는 다른 클래스들은 이를 직접 생성할 필요 없이 사용할 수 있게 됩니다.

    이런 방식으로, DI는 코드의 결합도를 낮추고, 유지보수성과 확장성을 향상시키며, 테스트 용이성을 제공합니다.

    DI

     위 이미지는 DI 개념을 간단하고 명확하게 설명해줍니다. 프로그래머가 자신의 책상에서 다양한 도구(해머, 스크루드라이버, 렌치 등)를 둘러싸고 앉아 있는 모습을 보여줍니다. 각 도구는 프로그래머가 프로젝트에 쉽게 픽업해서 사용할 수 있는 다양한 서비스나 모듈을 나타냅니다. 프로그래머는 이 도구들이 어떻게 만들어지는지 알 필요 없이, 필요할 때마다 간편하게 사용할 수 있습니다. 이는 DI가 프로그래머의 코드에 서비스를 끊김없이 '주입'해 개발 과정을 더 효율적이고 유연하게 만든다는 개념을 상징합니다.

     

    의존성이 없는 코드 예시

     이 예시에서는 MessageService라는 인터페이스와 이 인터페이스를 구현하는 EmailService 클래스를 사용하여 메시지를 보내는 기능을 구현합니다. 이 코드는 의존성 주입을 사용하지 않고, 직접적인 의존성을 갖습니다.

     

    JAVA

    public class EmailService {
        public void sendMessage(String message, String receiver) {
            System.out.println("Email sent to " + receiver + " with message: " + message);
        }
    }
    
    public class Application {
        private EmailService emailService;
    
        public Application() {
            // EmailService에 대한 직접적인 의존성
            this.emailService = new EmailService();
        }
    
        public void processMessages(String message, String receiver) {
            this.emailService.sendMessage(message, receiver);
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Application app = new Application();
            app.processMessages("Hello", "user@example.com");
        }
    }

     

    GO

    package main
    
    import "fmt"
    
    type EmailService struct{}
    
    func (s *EmailService) SendEmail(message string, receiver string) {
    	fmt.Printf("Email sent to %s with message: %s\n", receiver, message)
    }
    
    type Application struct {
    	emailService EmailService
    }
    
    func (app *Application) ProcessMessages(message string, receiver string) {
    	app.emailService.SendEmail(message, receiver)
    }
    
    func main() {
    	app := Application{}
    	app.ProcessMessages("Hello, World!", "user@example.com")
    }

    의존성이 있는 코드 예시

     Application 클래스는 메시지 서비스에 대한 의존성을 갖고 있으며, 이 의존성은 생성자를 통해 주입됩니다.

     

    JAVA

    // MessageService 인터페이스 정의
    public interface MessageService {
        void sendMessage(String message, String receiver);
    }
    
    // EmailService는 MessageService 인터페이스를 구현
    public class EmailService implements MessageService {
        @Override
        public void sendMessage(String message, String receiver) {
            // 메시지 전송 로직 (여기서는 단순 출력으로 대체)
            System.out.println("Email sent to " + receiver + " with message: " + message);
        }
    }
    
    // Application 클래스는 MessageService에 의존성을 갖음
    public class Application {
        private MessageService messageService;
    
        // 생성자를 통한 의존성 주입
        public Application(MessageService service) {
            this.messageService = service;
        }
    
        public void processMessages(String message, String receiver) {
            // 메시지 처리 로직, 실제 메시지 전송은 MessageService가 담당
            this.messageService.sendMessage(message, receiver);
        }
    }
    
    // Main 클래스, 실행을 위한 메인 메서드 포함
    public class Main {
        public static void main(String[] args) {
            // EmailService 객체 생성
            MessageService emailService = new EmailService();
            
            // Application 객체에 EmailService 의존성 주입
            Application app = new Application(emailService);
            
            // 메시지 전송 프로세스 실행
            app.processMessages("Hello DI", "user@example.com");
        }
    }

     

    GO

    package main
    
    import "fmt"
    
    // MessageService 인터페이스 정의
    type MessageService interface {
    	SendMessage(message string, receiver string)
    }
    
    // EmailService 구조체와 인터페이스 구현
    type EmailService struct{}
    
    func (s *EmailService) SendMessage(message string, receiver string) {
    	fmt.Printf("Email sent to %s with message: %s\n", receiver, message)
    }
    
    // Application 구조체
    type Application struct {
    	messageService MessageService
    }
    
    func NewApplication(service MessageService) *Application {
    	return &Application{
    		messageService: service,
    	}
    }
    
    func (app *Application) ProcessMessages(message string, receiver string) {
    	app.messageService.SendMessage(message, receiver)
    }
    
    func main() {
    	emailService := &EmailService{}
    	app := NewApplication(emailService)
    	app.ProcessMessages("Hello, DI!", "user@example.com")
    }

     

    작동 방식

    • EmailService는 MessageService 인터페이스를 구현합니다. 실제 메시지 전송 로직(여기서는 콘솔에 출력하는 형태)을 포함합니다.
    • Application 클래스는 메시지를 처리하는 데 필요한 MessageService의 의존성을 갖습니다. 이 의존성은 생성자를 통해 외부에서 주입받습니다. 이를 통해 Application 클래스는 메시지 서비스의 구현 세부 사항에 대해 알 필요가 없으며, 다양한 메시지 서비스 구현체를 유연하게 사용할 수 있습니다.
    • Main 클래스에서는 EmailService 객체를 생성하고, 이를 Application 객체의 생성자를 통해 주입합니다. 그 후, Application 객체를 사용하여 메시지 전송 프로세스를 실행합니다.

    이 예시는 Java에서 생성자를 통한 종속성 주입의 기본적인 형태를 보여줍니다. 이를 통해 코드의 결합도를 낮추고, 유닛 테스트와 유지 보수성을 향상시킬 수 있습니다.

     

    개선된 점

    • 결합도 감소: 의존성 주입을 사용하면, Application 클래스는 MessageService 인터페이스에만 의존하게 됩니다. 구체적인 구현체(EmailService)에 대한 의존성이 줄어들기 때문에, 다른 메시지 서비스로의 변경이나 확장이 용이해집니다.
    • 테스트 용이성: 의존성 주입을 사용하면, 테스트 중에 다양한 구현체나 모의 객체(mock object)를 주입할 수 있어, 단위 테스트가 훨씬 용이해집니다.
    • 유연성 및 확장성 향상: 다양한 종류의 메시지 서비스 구현체를 쉽게 교체하거나 추가할 수 있으므로, 애플리케이션의 유연성과 확장성이 향상됩니다.

    단점

    • 복잡도 증가: 간단한 애플리케이션에서는 의존성 주입이 코드의 복잡도를 불필요하게 증가시킬 수 있습니다. 의존성 관리를 위한 추가적인 코드나 구성이 필요하기 때문입니다.
    • 학습 곡선: 의존성 주입과 관련된 디자인 패턴이나 프레임워크(Spring 같은)를 사용하려면, 이에 대한 이해와 학습이 필요합니다.

    결론

    의존성 주입은 애플리케이션의 결합도를 낮추고, 테스트와 유지 보수를 용이하게 하는 등 다양한 이점을 제공하지만, 프로젝트의 규모나 복잡도에 따라 그 필요성이 달라질 수 있습니다. 작은 프로젝트에서는 간단한 구조가 더 적합할 수 있으며, 대규모 프로젝트나 테스트 용이성, 유연성이 중요한 경우에 의존성 주입을 고려해볼 수 있습니다.

     

     

    반응형
Designed by Tistory.