Clean Architecture 정리 - 3부 11장 의존성 역전 원칙
2025. 6. 15. 15:26

의미

  • 기본적으로 컴포넌트는 다른 컴포넌트를 직접 참조하여 사용한다.
    예) A -> B (A가 B를 직접 참조)
  • 하지만 DIP에서는 다음과 같은 구조를 권장한다:
    A -> Interface <- B
    즉, 구체 클래스가 아닌 추상 인터페이스를 사이에 두고 의존한다.
    why? 구체 클래스보다 추상 인터페이스가 변동성이 낮다.

 

이 방향 전환, 즉 구체가 추상에 의존하는 구조가 바로 "의존성 역전(Dependency Inversion)"이다.


의존성 역전을 고려하지 않아도 되는 경우

  • 매우 안정적이고 변동성이 없는 요소들에는 DIP를 적용하지 않아도 된다.
    예: String, int 같은 기본 타입, 운영체제 API, 플랫폼 API 등

 

변경될 가능성이 없다면 의존해도 된다.

따라서 우리가 보통 개발하는 대부분의 비즈니스 로직 관련 클래스는 DIP 적용이 필요하다.


책에서 제시하는 원칙 요약

  • 변동성이 큰 구체 클래스를 직접 참조하지 말 것
    • 대신 추상 인터페이스를 참조하라.
  • 변동성이 큰 구체 클래스로부터의 상속은 신중히
    • 상속도 의존성이다. 변경의 영향을 그대로 받는다.
  • 구체 함수를 오버라이드하지 말 것
    • 구현된 메서드를 자식 클래스에서 재정의하면, 부모 변경이 자식에 직접 전파된다.
    • 가능하면 추상 메서드만 상속해서 자식에서 구현하는 것이 안전하다.

의존성을 처리하는 방법

책에서는 추상 팩토리(Abstract Factory)를 제시한다.

  • 구체 클래스 생성을 팩토리에 위임함으로써, 클라이언트가 구체 구현을 알 필요가 없어진다.
  • 팩토리는 추상 인터페이스를 반환하고, 클라이언트는 이 인터페이스만 사용한다.

하지만 이 글에서는 의존성 주입(DI, Dependency Injection) 방식을 설명하겠다.

 

의존성 주입 (DI)

의존성 주입은 클라이언트 객체가 사용할 구체 구현을 스스로 생성하지 않고, 외부에서 주입받는 방식이다.
이 방식은 클라이언트가 구체 클래스에 직접 의존하지 않도록 만들기 위한 도구이며, DIP를 실천할 수 있게 해준다.

 

예시 코드 (Java, 생성자 주입)

// 추상 인터페이스
interface MessageService {
    void send(String msg);
}

// 구체 구현체 A
class EmailService implements MessageService {
    public void send(String msg) {
        System.out.println("Email: " + msg);
    }
}

class Notification {
    private final MessageService service;

    public Notification(MessageService service) {
        this.service = service;
    }

    public void alert(String msg) {
        service.send(msg);
    }
}

// 구성 루트(main)
public class Main {
    public static void main(String[] args) {
        MessageService service = new EmailService(); // 구체 클래스 생성은 여기서만
        Notification notification = new Notification(service);
        notification.alert("DIP 적용 완료");
    }
}

이 예시에서는 Notification이 EmailService에 직접 의존하지 않고, 추상 MessageService에 의존한다.
EmailService를 FileService, SMSService 등으로 쉽게 교체할 수 있고, 테스트도 수월해진다.


모든 부분을 DIP를 지켜야하는가?

DIP와 DI를 통해 많은 의존성을 추상화할 수 있지만, 모든 의존성을 추상화할 수는 없다.

대부분의 시스템은 적어도 하나의 구체 컴포넌트를 의존하는 컴포넌트를 포함하며, 이는 보통 main()을 포함하는 시작점이다.
그곳에서 구체 인스턴스를 생성하고, 나머지 시스템에 주입하는 구조로 만든다.

 

 

※ 본 글은 『Clean Architecture』(로버트 C. 마틴 저) 3부의 11장을 기반으로 학습 목적으로 요약한 글입니다.

※ 이 글은 책의 내용을 요약한 것으로, 원문 없이 읽을 경우 오해의 여지가 있을 수 있습니다. 정확한 이해를 위해 원서의 정독을 권장합니다.