Java

[객체지향설계원칙] DIP

shininghyunho 2024. 5. 31. 21:09

DIP

Dependency Inversion Principle.

의존관계 역전 원칙이다.

이름에서 느껴지듯 의존관계가 역전된다는 말이다.

 

A->B 처럼 A가 B를 의존한다고 할때

A<-B 로 의존관계가 바뀐다는 말이 아니다.

A와 B 사이에 C를 추가해 A,B 모두 새로운 C에 의존한다는 말이다.

A-> C <-B.

 

서적에서는 고수준 모델이 저수준 모델을 의존하는 것이 아닌

새로운 인터페이스를 추가해 고수준, 저수준 모두 인터페이스에 의존해야한다. 라고 정의되어 있다.

 

구체적으로 살펴보자.

DIP 적용 전

공장 클래스에서 여러 일꾼들을 데리고 일을 한다고 해보자.

그러면 공장 클래스는 Boy, Girl, Robot 클래스에 의존하게된다.

(의존한다는 말은 공장 클래스를 생성하기 위해서는 Boy, Girl, Robot 클래스들이 필요하다는 말이다.)

 

일꾼들 클래스들은 열심히 일을 할 수 있다.

class Boy {
    public void work() {
        System.out.println("Boy is working");
    }
}

class Girl {
    public void work() {
        System.out.println("Girl is working");
    }
}

class Robot {
    public void work() {
        System.out.println("Robot is working");
    }
}

 

그리고 이 일꾼들 클래스들을 Factory 클래스에서 사용한다.

(Factory 클래스가 일꾼 클래스들에게 의존함.)

class Factory {
    private Boy boy;
    private Girl girl;
    private Robot robot;

    public Factory(Boy boy, Girl girl, Robot robot) {
        this.boy = boy;
        this.girl = girl;
        this.robot = robot;
    }

    public void produce() {
        boy.work();
        girl.work();
        robot.work();
    }
}

 

 

Factory 클래스에서 직접 구현체들에게 의존하고있다.

그래서 새로운 구현체를 추가하거나 수정할때

직접 Factory 클래스도 같이 수정해줘야한다.

OCP 원칙을 위반하고 있는것이다.

 

DIP 적용 후

해결책은 간단하다 중간에 Worker 인터페이스를 만들어

모두 인터페이스에만 의존하는것이다.

 

// Worker 인터페이스 정의
interface Worker {
    void work();
}

 

실제 구현체에서는 Worker 인터페이스의 내용을 따른다.

// Boy 클래스는 Worker 인터페이스를 구현
class Boy implements Worker {
    @Override
    public void work() {
        System.out.println("Boy is working");
    }
}

// Girl 클래스는 Worker 인터페이스를 구현
class Girl implements Worker {
    @Override
    public void work() {
        System.out.println("Girl is working");
    }
}

// Robot 클래스는 Worker 인터페이스를 구현
class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Robot is working");
    }
}

 

그리고 비지니스 로직을 담당하는 Factory 클래스도 Worker 인터페이스에만 의존한다.

// Factory 클래스는 Worker 인터페이스에 의존
class Factory {
    private Worker[] workers;

    public Factory(Worker[] workers) {
        this.workers = workers;
    }

    public void produce() {
        for (Worker worker : workers) {
            worker.work();
        }
    }
}

 

차이점이 느껴진다.

Factory, 구현체 클래스(Boy, Girl, Robot) 모두 Worker 인터페이스만 의존하고있다.

코드들만 봐도 Worker 인터페이스 외에는 다른 정보들은 알아서도 알 필요도 없다.

 

DI와 IoC

Java 개발자라면 Spring Framework의 Bean에 익숙할것이다.

클래스들을 단일 객체로만 만든것이 Bean인데,

이를 하나의 클래스에서 호출해준다.

이 클래스를 IoC(Inversion Of Control) Container, Spring Container 라고 한다. (Spring 자체라고 보기도한다.)

DI를 설명할때 항상 같이 나오는게 IoC Container인데,

DI를 해주는 메서드라고 보면된다.

 

해당 예제에서는 Factory, Boy, Girl, Robot 이 Bean 이 될것이고,

이를 호출하는(생성주기를 관리해주는) IoC Container 가 메인 메서드다.

 

메인 메서드로 생각한다면 다음과 같은 일을 할것이다. (정확히 IoC Container 업무와 같진 않다.)

public class Main {
    public static void main(String[] args) {
        Worker boy = new Boy();
        Worker girl = new Girl();
        Worker robot = new Robot();
        Worker[] workers = {boy, girl, robot};
        Factory factory = new Factory(workers);
        factory.produce();
    }
}

 

DIP 장점

DI를 적용하면 클래스 간의 결합도가 낮아져서,

시스템을 변경하거나 확장할 때 유연성이 증가한다.

예를 들어, 새로운 Worker가 생겨도 기존 코드에는 전혀 변함이 없다.

 

즉 클래스들간의 의존성이 사라져 결합도Coupling)가 줄어든다.

DI를 적용하기 전에는 클래스들간에 실제 구현체가 노출되었다.

그래서 실제 동작을 알아야만했고 구현체가 변경되고 호출하는 클래스도 변경되어야만했다.

 

여담으로 프레임워크의 기본 요건으로 DI 가 가능해야한다.

그만큼 DI가 굉장히 중요하고 효율적이라고 생각할 수 있다.

'Java' 카테고리의 다른 글

Stream API(임시저장용)  (0) 2024.08.08
[객체지향설계원칙] ISP  (0) 2024.05.31
[객체지향설계원칙] LSP  (0) 2024.05.29
[객체지향설계원칙] OCP  (0) 2024.05.23
추상클래스, 인터페이스의 문법적,의미적 차이  (0) 2024.05.22