DIP - Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend upon details. Details should depend upon abstractions.

  • 고차원 모듈은 저차원 모듈에 의존하면 안된다. 이 두 모듈 모두 다른 추상화된 것에 의존해야 한다.
  • 추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.

여기서 말하는 고수준 모듈은, 어떤 의미있는 단일 기능을 제공하는 것을 말한다. 아래 예시를 들 수 있다.

  • 바이트 데이터를 읽어와
  • 암호화하고
  • 결과 바이트 데이터를 쓴다.

저수준 모듈은, 고수준 모듈의 기능을 구현하기 위해 필요한 개별 기능이다. 좀 더 작은 모듈이라고 할 수 있다.

  • 파일에서 데이터를 읽어온다.
  • AES 알고리즘으로 암호화 한다.
  • 파일에 바이트 데이터를 쓴다.

잘못된 예시와 개선 사례 (1)

어떠한 클래스에 로그를 적용한 사례이다.

// "Low level Module" Mechanism equivilant
public class Logger {
    public void logInformation(String logInfo) {
        System.out.println(logInfo);
    }
}

// "High level module" Policy equivalent.
public class Foo {
    // direct dependency of a low level module.
    private Logger logger = new Logger();

    public void doStuff() {
        logger.logInformation("Something important.");
    }
}

만일 로그의 출력 기능이 변경되어야 한다면 Logger클래스와 별개의 클래스를 만들어, 클라이언트 코드까지 Logger로 생성한 것에서 신규 생성한 클래스로 변경해주어야 하는 작업이 생긴다. 여기서 Foo는 저차원 모듈(로그방식)에 직접적으로 의존하기 때문에 좋지 않은 코드이다. 이를 추상화된 것에 의존하도록 아래와 같이 변경해보자.

public interface ILogger {
    void logInformation(String logInfo);
}

public class Logger implements ILogger {
    @Override
    public void logInformation(string logInfo) {
        System.out.println(logInfo);
    }
}

public class Foo {
    private ILogger logger;
    
    public void setLoggerImpl(ILogger loggerImpl) {
        this.logger = loggerImpl;
    }

    public void doStuff() {
        logger.logInformation("Something important.");
    }
}

// Usage
Foo foo = new Foo();
ILogger logger = new Logger();
foo.setLoggerImpl(logger);
foo.doStuff();

ILogger라는 인터페이스를 만들어 Logger의 추상화를 적용하였다. 그리고 Foo에서는 Ilogger에만 의존하도록 하여 고차원 모듈은 저차원 모듈에 의존하면 안된다라는 약속을 지켰다. 그리고 또 추가로 로그의 기능 확장을 원할 경우, Ilogger를 구현하는 클래스를 새로 만들어, Foosetter메소드로 이 새로운 클래스 인스턴스 객체를 전달해주기만 하면 된다.

결론

의존 역전 원칙은 곧, 자신보다 변하기 쉬운 것에 의존하지 말라는 것과 같다. 상위 클래스, 인터페이스, 추상클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 약속을 갖고 있다.


참고자료

  1. 스프링 입문을 위한 객체 지향의 원리와 이해
  2. 개발자가 정복해야 할 객체지향-4
  3. 객체지향의 5대 설계 원칙 - SOLID

oksusutea's blog

꾸준히 기록하려고 만든 블로그