본문 바로가기
안드로이드

[안드로이드/JAVA] 의존성주입(DI)을 알아보자

by krapoi 2022. 6. 13.
반응형

오늘은 클린 아키텍처를 설계할 때 필요한 친구인 의존성 주입(DI)을 공부해왔다.

차근차근 알아보자.

 

의존성 주입이란?

의존성 주입( DI, Dependency Injection )이란 하나의 객체에 다른 객체의 의존성을 제공하는 기술을 말한다.

이 의존성 주입이란 단어를 처음 듣는다면 좀 어렵게 느껴질 수 있는데, 의존성과 주입이라는 단어를 나누어 생각하면 좀 더 이해하기 쉽다.

 

의존성은 객체 지향에서 두 클래스 간의 관계이다. 일반적으로 둘 중 하나가 다른 하나를 필요로 한다.

예제를 하나 살펴보자.

class CPU {}

public class Computer {
    private CPU cpu;
    
    public Computer() {
        cpu = new CPU();
    }
}

컴퓨터에 포함되는 CPU가 컴퓨터가 생성되는 단계부터 단단히 결합한 모습을 보인다.(생성자로 cpu 할당)

이때 다른 CPU로 변경하고 싶어도 변경할 수 없다. 이를 "Computer가 CPU에 의존성을 갖는다."라고 할 수 있다.

 

주입은 생성자나 메서드 등을 통해 외부로부터 생성된 객체를 전달받는 것을 의미한다. 

이것도 예제를 하나 살펴보자.

class CPU {}

public class Computer {
    private CPU cpu;

    public void setCPU(CPU cpu) {
        this.cpu = cpu;
    }
}

setCPU 메서드를 통해 외부로부터 생성된 객체를 전달받아 cpu 변수에 넣는다.

 

앞에서 설명한 의존성과 주입을 합쳐 보면 "의존 관계에 있는 클래스의 객체를 외부로부터 생성하여 주입받는다."라고 말할 수 있다.

 

의존성 주입의 필요성

방금 전에 의존성 주입이 무엇인지에 대해 알아보았지만 아직 의존성 주입이 무엇인지 이해가 되지 않고, 의존성 주입을 꼭 해야 하는 가에 대한 의문점이 생길 수도 있다. 그렇다면 필요성을 알아보자.

변경의 전이

다시 한번 컴퓨터와 CPU의 관계를 확인해 보자. 'Computer'는 'CPU'라는 한 가지 타입에 의존한다.

하지만 사용자는 다른 타입의 CPU를 사용하는 것을 원할 수 있다.

 

예를 들어 A사의 CPU로 컴퓨터를 조립하기를 원한다면 기존 CPU 클래스명을 A_CPU로 변경하거나 새로 만들어야 한다.

하나의 클래스를 변경하거나 새로 만드는 것은 어렵지 않지만, 여기서 문제점은 CPU클래스를 의존하던 Computer 클래스도 같이 변경해야 한다는 점이다.

public class Computer {
    private A_CPU cpu;

    public Computer() {
       cpu = new A_CPU();
    }
}

하나의 클래스를 변경함으로써 다른 의존 관계까지 변경 사항이 전이된다.

 

이를 해결할 방법은 Computer가 의존하는 CPU를 interface로 만드는 것이다. CPU를 구현한 어떤 클래스 간에 Computer의 CPU로 기능할 수 있다.

 

interface CPU {}

class A_CPU implements CPU{}

public class Computer {
    private CPU cpu;

    public Computer() {
       cpu = new A_CPU();
	// cpu = new I_CPU();
	}
}

하지만 여전히 문제점은 남아 있다. CPU를 인터페이스로 변경함에 따라 변경의 전이를 최소화했지만, Computer 클래스에서 CPU 객체를 생성하고 관리해 I 회사의 CPU를 사용한다면 또다시 Computer 클래스를 변경해야 한다.

 

제어의 역전( IoC, Inversion of Control )

제어의 역전은 어떠한 일을 수행하도록 만들어진 프레임워크에 제어권을 위임함으로써 관심사를 분리하는 것을 의미한다. 제어의 역전을 통해 앞의 코드들의 문제점을 해결해 본다.

 

public class Computer {
    private CPU cpu;

    public Computer() {}

    public Computer(CPU cpu) {
        this.cpu = cpu;
    }

    public void setCPU(CPU cpu) {
        this.cpu = cpu;
    }
}

public static void main(String[] args) {
        CPU cpu = new I_CPU();
        Computer computer1 = new Computer(cpu);
        //또는
        Computer computer2 = new Computer();
        computer2.setCPU(cpu);
    }

Computer 클래스의 생성자에서 CPU 객체를 만들지 않고, 외부로부터 CPU 객체를 생성한 뒤 Computer 생성자 또는 메서드의 매개 변수로 객체를 제공한다. 기존에는 Computer가 CPU의 객체를 생성하고 관리했으나 개선된 코드에서는 CPU 객체의 생성 및 관리를 외부에 위임했다. 이를 제어의 역전이라고 한다.

제어의 역전을 통해 결합도를 약하게 만들었고, Computer는 이제 CPU의 변경 사항에 의해 내부 필드나 메서드 매개 변수를 변경하지 않아도 된다.

 

의존성 주입의 장단점

 장점

  • 의존성 중입은 인터페이스를 기반으로 설계되며, 코드를 유연하게 함
  • 리팩토링이 수월함
  • stub이나 mock 객체를 사용하여 단위 테스트를 하기가 더욱 쉬워짐
  • 클래스 간의 결합도를 느슨하게 함
  • 여러 개발자가 서로 사용하는 클래스를 독립적으로 개발할 수 있다

 

 단점

  • 간단한 프로그램을 만들 때는 번거로움
  • 의존성 주입은 동작과 구성을 분리해 코드를 추적하기 어렵게 하고, 가독성을 떨어뜨릴 수 있다
  • 의존성 주입 프레임 워크는 에노테이션 프로세서를 이용하여 파일을 생성하므로 빌드 시간이 좀 더 소요된다

 

이렇게 이번에는 의존성 주입에 대해서 알아보았다.

반응형