DI(Dependency Injection)

Moon·2023년 4월 21일
0

스프링

목록 보기
1/3
post-thumbnail

의존이라는 단어에서 알 수 있듯이 DI는 의존을 처리하는 방법에 대한 내용입니다.

일단 DI는 의존(Dependency)에 대한 설계 패턴이므로, 의존의 의미를 알아야 합니다.

어떤 프로그램을 개발할 때, 단 한 개의 클래스에 모든 코드를 몰아넣는 경우는 없습니다.

예를 들어, 어떤 A 클래스의 print() 메서드가 존재하며, 이 메서드 안에는 BufferedReader 클래스를 사용하여 내용을 출력하고 있습니다. 이것은 print() 메서드를 실행하기 위해 BufferedReader 클래스가 필요하다는 의미입니다.

이렇게 print 기능을 사용하기 위해 다른 클래스(또는 타입)를 필요로 할 때 이를 의존(Dependency)한다고 말합니다.

아까 예에서 A 클래스가 BufferedReader 클래스에 의존한다고 말할 수 있습니다.

의존하는 객체를 직접 생성해서 지역 변수로 보관하거나 의존하는 타입을 필드로 정의하기도 합니다.

아니면 의존하는 타입의 객체를 직접 생성하지 않고 생성자를 통해서 의존 객체를 전달받을 수 있습니다.

아래의 코드는 의존하는 타입의 객체를 직접 생성하는 방식의 코드입니다.

public class FileEncryptor{
	// 의존 객체를 직접 생성
    private Encryptor encryptor = new Encryptor();
    
    public void encrypt(File src, File target) throws IOException{
    	try(
        	FileInputStream is = new FileInputStream(src);
            FileOutputStream out = new FileOutputStream(target)) {
            ...
            encryptor.encryptor(data, 0, len);
          }
    }
}

위 코드는 객체를 직접 생성하고 있습니다.

그런데 갑자기 요구사항의 변화로 Encryptor 클래스의 하위 클래스인 FastEncryptor 클래스를 사용해야 하는 상황이 왔다고 가정하면 이 경우 아래의 코드처럼 FileEncryptor 클래스의 코드를 변경해줘야 합니다.

public class FileEncryptor{
	// Encryptor 대신에 FastEncryptor를 사용하려면 변경
    private Encryptor encryptor = new FastEncryptor();

만약 Encryptor 클래스 대신에 FastEncryptor 클래스의 객체를 사용해야 하는 코드가 많다면, 그에 비례해서 변경해주어야 하는 코드의 양도 증가하는 끔찍한 상황이 발생할 것입니다.

그래서 의존하는 타입의 객체를 직접 생성하는 방식의 코드는 개발 생산성이 전체적으로 낮아지는 상황이 발생합니다.

의존 객체를 직접 생성하는 방식과 달리 DI(Dependency Injection)의존 객체를 외부로부터 전달받는 구현 방식입니다. 아까 말했듯이 이 방식은 생성자를 이용해서 의존 객체를 전달받는 방식이 DI에 따라 구현한 것입니다.

public class FileEncryptor{
	private Encryptor encryptor;
    
    public FileEncryptor(Encryptor encryptor){
    	// 생성자로 전달받은 객체를 필드에 할당
        this.encryptor = encryptor;
    }
    
    public vodi encryptor(File src, File target) throws IOException{
    	...
        // DI 방식으로 전달받은 객체를 사용
        encryptor.encrypt(data, 0, len);
        ...
    }
}

위 코드는 생성자를 통해서 Encryptor 타입의 객체를 전달받고 있습니다.

이는 FileEncryptor 객체를 생성하는 부분에서 Encryptor 객체를 전달해주어야 함을 의미합니다.

// 다른 코드에서 의존 객체를 생성
Encrpytor enc = new Encryptor();
// 의존 객체를 전달해 줌
FileEncryptor fileEnc = new FileEncryptor(enc);

위와 같은 방식을 Dependency Injection(의존 주입), DI라고 부릅니다.

또 다른 말로는 객체를 연결(wire)한다는 표현을 씁니다. 위 코드에서 FileEncrpytor 객체에 Encryptor 객체를 연결한다고 말하기도 합니다.

그럼 여기서 누가 객체를 생성하고 객체들을 서로 연결해주는 것일까요?

그런 역할을 수행하는 것이 바로 조립기입니다.

public clas Assembler{
	private FileEncryptor fileEnc;
    private Encryptor enc;
    
    public Assembler(){
    	enc = new Encryptor();
        fileEnc = new FileEncryptor(enc);
    }
    
    public FileEncryptor fileEncryptor(){
    	return fileEnc;
    }
}

위 코드는 간단한 조립기의 구현 코드예 입니다.

이제 FileEncryptor가 필요한 곳에서는 조립기를 이용해서 FileEncryptor를 구하면 됩니다.

Assembler assembler = new Assembler();
FileEncryptor fileEnc = assembler.fileEncryptor();
fileEnc.encrypt(srcFile, targetFile);

만약 FileEncryptor가 사용해야 할 Encryptor 객체의 타입이 FastEncryptor로 변경하려면 조립기의 코드를 바꾸면 됩니다. FastEncryptor 객체를 사용하도록 변경해주어야 하는 곳이 많더라도 변경되는 부분은 조립기로 제한됩니다.

스프링(Spring)이 바로 이 조립기에 해당합니다.

또한 DI를 사용할 때는 의존하는 클래스의 구현이 완성되어 있지 않더라도 테스트를 할 수 있다는 장점이 있습니다.

이상으로 DI에 대해서 간단히 알아봤습니다.

profile
꾸준함으로 성장하는 개발자 지망생

0개의 댓글