CS 공부

# 의존성 주입과 의존관계 역전 원칙

박태정 입니다. 2025. 2. 20. 13:56
반응형

🔗 의존성 주입과 의존관계 역전 원칙

요즘 의존성 주입(DI, Dependency Injection)의존관계 역전 원칙(DIP, Dependency Inversion Principle) 에 대해 공부하고 있다.
이 개념들을 제대로 이해하면 코드의 결합도를 낮추고 유지보수하기 쉬운 구조를 만들 수 있다고 하는데, 아직 머릿속이 정리되지 않은 느낌이다.
일단 하나씩 정리해보자.


🏗️ 의존성 주입(DI, Dependency Injection)이란?

어떤 프로그램에서 "A가 B를 직접 생성하고 사용한다" 라는 구조를 생각해보자.
이 경우, B가 변하면 A도 영향을 받는다. 즉, A는 B에 강하게 의존 하고 있는 것이다.

class B {
    public void go() {
        System.out.println("B의 go() 함수");
    }
}

class A {
    public void go() {
        new B().go();  // A가 직접 B를 생성하고 사용
    }
}

위 코드에서 B 클래스의 go() 메서드 이름을 변경하면, A 클래스도 반드시 변경해줘야 한다.
이게 바로 강한 결합(의존성이 높음) 상태이다.

🔄 의존성 주입을 사용하면?

의존성 주입(DI) 을 사용하면 A가 B를 직접 생성하지 않고, 외부에서 B를 주입받을 수 있다.

class B {
    public void go() {
        System.out.println("B의 go() 함수");
    }
}

class A {
    private B b;  // A는 B를 직접 생성하지 않는다.

    public A(B b) { // 생성자를 통해 B를 주입받음
        this.b = b;
    }

    public void go() {
        b.go();
    }
}

public class Main {
    public static void main(String[] args) {
        B b = new B();
        A a = new A(b);  // 외부에서 B를 생성하고 A에 주입
        a.go();
    }
}

이제 B가 변경되더라도 A를 직접 수정할 필요가 없다.
즉, A와 B의 결합도를 낮춰 모듈을 더 유연하게 만들 수 있다.


🏛️ 의존관계 역전 원칙(DIP, Dependency Inversion Principle)

의존성 주입을 적용할 때는 의존관계 역전 원칙(DIP) 이라는 개념이 적용된다.
이 원칙을 따르려면 두 가지 규칙 을 지켜야 한다.

1️⃣ 상위 모듈은 하위 모듈에 의존해서는 안 된다.
→ 둘 다 추상화(인터페이스) 에 의존해야 한다.

2️⃣ 추상화는 세부사항에 의존해서는 안 된다.
세부 사항(구현체)은 추상화에 따라 달라져야 한다.

🤔 의존성 역전이 필요한 이유

기존에는 다음과 같이 상위 모듈(Project)이 하위 모듈(BackendDeveloper, FrontendDeveloper)에 의존하는 구조였다.

class BackendDeveloper {
    public void develop() {
        System.out.println("백엔드 개발 중...");
    }
}

class FrontendDeveloper {
    public void develop() {
        System.out.println("프론트엔드 개발 중...");
    }
}

class Project {
    private BackendDeveloper backendDeveloper;
    private FrontendDeveloper frontendDeveloper;

    public Project() {
        this.backendDeveloper = new BackendDeveloper();
        this.frontendDeveloper = new FrontendDeveloper();
    }

    public void start() {
        backendDeveloper.develop();
        frontendDeveloper.develop();
    }
}

이렇게 구현하면 프로젝트가 특정 개발자(백엔드, 프론트엔드)에 직접 의존 하기 때문에, 새로운 개발자가 추가되면 Project 클래스도 수정해야 한다.

🔄 인터페이스를 이용한 DIP 적용

이를 해결하려면 "개발자"라는 공통적인 개념을 추상화(인터페이스)로 만들고 각 개발자가 이를 구현하면 된다.

interface Developer {
    void develop();
}

class BackendDeveloper implements Developer {
    @Override
    public void develop() {
        System.out.println("백엔드 개발 중...");
    }
}

class FrontendDeveloper implements Developer {
    @Override
    public void develop() {
        System.out.println("프론트엔드 개발 중...");
    }
}

class Project {
    private List<Developer> developers;

    public Project(List<Developer> developers) {
        this.developers = developers;
    }

    public void start() {
        for (Developer developer : developers) {
            developer.develop();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        List<Developer> devs = new ArrayList<>();
        devs.add(new BackendDeveloper());
        devs.add(new FrontendDeveloper());

        Project project = new Project(devs);
        project.start();
    }
}

이제 Project 클래스는 Developer 인터페이스에만 의존하고,
어떤 개발자가 추가되든 Project 코드를 수정할 필요가 없다.
이것이 바로 의존관계 역전 원칙이 적용된 구조! 🎉


✅ 의존성 주입의 장점

결합도가 낮아진다

  • 특정 구현체가 아니라 인터페이스 에 의존하기 때문에 모듈 교체가 쉽다.
  • 예를 들어 BackendDeveloper 대신 AIDeveloper 를 추가해도 Project 코드를 바꿀 필요가 없다.

테스트하기 쉬워진다

  • Project 클래스에서 가짜 개발자(Mock 객체) 를 주입하여 단위 테스트를 쉽게 할 수 있다.

코드 유지보수가 편하다

  • 코드 변경이 필요할 때 한 곳만 수정하면 되므로 코드 추론이 쉬워진다.
  • 예를 들어, 데이터베이스를 MySQL → PostgreSQL 로 변경할 때, 기존 코드를 수정할 필요 없이 주입된 객체만 변경하면 된다.

마이그레이션(환경 이전)이 용이하다

  • 특정 구현체가 아닌 추상화된 개념에 의존하므로 DB 이전, 프레임워크 변경 같은 작업이 용이하다.

⚠️ 의존성 주입의 단점

코드가 더 복잡해질 수 있다.

  • 클래스가 늘어나고, DI 컨테이너(Spring 등)가 필요할 수도 있다.

런타임 오류 발생 가능성

  • 컴파일 시점이 아닌 실행 시점 에 의존성이 주입되므로,
    주입되지 않은 객체를 호출할 경우 NullPointerException 이 발생할 수도 있다.
반응형

'CS 공부' 카테고리의 다른 글

# 옵저버 패턴  (0) 2025.02.20
# 전략 패턴  (0) 2025.02.20
# 이터레이터(iterator) 패턴  (0) 2025.02.20
# 팩토리 패턴  (1) 2025.02.20
# 싱글톤 패턴 구현 방법 7가지  (0) 2025.02.20