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
를 구현하는 클래스를 새로 만들어, Foo
의 setter
메소드로 이 새로운 클래스 인스턴스 객체를 전달해주기만 하면 된다.
결론
의존 역전 원칙은 곧, 자신보다 변하기 쉬운 것에 의존하지 말라는 것과 같다. 상위 클래스, 인터페이스, 추상클래스일수록 변하지 않을 가능성이 높기에 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 약속을 갖고 있다.
참고자료