의존성과 의존성 주입

@Hudi · April 18, 2022 · 2 min read

의존성이란?

의존대상 B가 변하면, 그것이 A에 영향을 미친다.

- 이일민, 토비의 스프링 3.1, 에이콘(2012), p113

의존성 주입은 말 그대로 객체에 의존성(Dependency)을 주입(Injection)하는 것 이다. 그런데, 의존성은 어떤 의미일까? 의존성은 코드에서 두 모듈간의 연결을 의미한다.

조금 추상적인 것 같으니 좀 더 풀어서 이야기 해보자. 클래스 A가 다른 클래스 (혹은 인터페이스) B를 사용할 때 A는 B에 의존한다고 이야기한다. 즉, 한 객체의 코드에서 다른 객체를 생성하거나 다른 객체의 메서드를 호출할 때, 그리고 파라미터로 객체를 전달받아 사용할 때 의존성이 발생한다고 할 수 있다.

이떄, 모듈과 모듈사이의 의존성의 정도를 결합도(Coupling) 라고 한다. 결합도의 Loose 하고 Tight 한 정도는 연속적으로 표현된다. 즉, 이분법적으로 표현할 수 없다.

의존성이 위험한 이유

A가 B에 의존중 일때, B의 변경은 A에게 영향을 끼친다. 즉, B의 변경이 A의 변경을 초래할 가능성이 존재한다. 이런 의존의 영향은 꼬리에 꼬리를 문 것처럼 도미노처럼 전파된다.

예를들어 A는 B에 의존하고, B는 C에 의존하고 있는 상황을 가정해보자. 이때, C가 변경되면 B를 변경해야하고, 이어서 A를 변경해야한다. 특히 이와 같은 문제는 순환 의존의 형태에서 심하게 발생할 수 있다. 앞서 말한 의존관계에서 추가로 C가 A에 의존하여 순환 의존을 하고 있다고 가정해보자. 그런 상황에서 C의 변경은 다시 C 자기자신에게까지 영향을 미칠 수 있다.

그리고 의존성은 유닛 테스트가 어려운 코드를 만든다. 유닛 테스트는 '특정 모듈'이 의도된대로 작동하는지 테스트하는 과정을 의미한다. 하지만 특정 모듈의 작동이 다른 모듈을 필요로 한다면, 즉 의존관계가 있다면 특정 모듈만을 독립적으로 떼어내어 테스트하기 어렵다.

또한 과도한 의존성은 모듈을 재사용 어렵게 만든다.

의존성 주입이란?

의존성 주입(DI, Dependency Injection)이란, 위에서 이야기한 의존성의 위험성을 해소하기 위해 사용되는 패턴이다. 의존성 주입은 필요한 객체를 직접 생성하거나 찾지 않고, 외부에서 넣어주는 방식이다. 즉, 객체의 의존관계를 내부에서 결정하는 것이 아니라 객체 외부에서, 런타임 시점에 결정하는 방식이다.

의존성 주입을 비유와 함께 이야기해보자. 우리는 사용자가 원하는대로 CPU를 교체할 수 있는 컴퓨터를 만들어서 판매하려 한다. 처음에는 고객들이 직접 컴퓨터 케이스를 뜯어 부품 업그레이드를 할 생각을 하지 못해서 CPU를 임의로 교체하지 못하는 컴퓨터로 만들었다.

public class Computer {
    private final SomeCpu someCpu = new SomeCpu();

    public Computer() {
        // 생략
    }
}

그런데 이 PC는 한번 생산되면 CPU를 교체할 수 없다. 또한, CPU의 스펙이 변경되면 컴퓨터도 그에 맞춰 설계를 변경해야한다. 이를 의존성 주입으로 해결해보자.

의존성 주입에는 크게 두가지 방식이 있다. 첫번째는 생성자를 통해 의존 객체를 전달 받는 '생성자 방식', 두번째는 의존성 주입을 받는 특별한 메소드를 만들어 의존 객체를 전달받는 '설정 메서드 방식' 이다.

생성자 방식

public class Computer {
    private final Cpu cpu;

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

설정 메서드 방식

public class Computer {
    private Cpu cpu;

    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }
}

의존성 주입을 사용하는 이유

  1. 모듈간 의존성이 줄어든다.

    모듈간 변경에 민감해지지 않아, SOLID 원칙 중 OCP(개방-폐쇄 원칙)을 만족하는 코드를 작성할 수 있다.

  2. 재사용성이 높아진다.
  3. 유닛 테스트를 쉽게 만들어준다.

    테스트하려는 A모듈이 의존하는 B모듈에 대한 Mock 객체 (이를 테스트 더블이라고 한다) 를 만들면, A모듈과 B모듈을 별개로 유닛 테스트할 수 있다.

  4. 가독성이 증가한다.

참고

@Hudi
꾸준히, 의미있는 학습을 기록하기 위한 공간입니다.