의존 역전 원칙 (DIP)

의존 역전 원칙(Dependency Inversion Principle)란 ?

  • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
  • 추상화된 것은 구체적인 것에 의존하면 안 된다. 구체적인 것이 추상화된 것에 의존해야 한다.
  • 자신보다 변하기 쉬운 것에 의존하지 마라. 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스에 의존하도록 함으로써, 변하기 쉬운 것의 변화에 영향을 받지 않게 해야 한다.

    상위 클래스일수록, 인터페이스일수록, 추상 클래스일수록 변하지 않을 가능성이 높기 때문에, 하위 클래스나 구체 클래스가 아닌 상위 클래스, 인터페이스, 추상 클래스를 통해 의존하라는 것이다.

의존 역전 원칙을 사용하는 이유

고수준 모듈은 상대적으로 큰 틀(즉, 상위수준)에서 프로그램을 다룬다면, 저수준 모듈은 각 개별 요소(즉, 상세)가 어떻게 구현될지에 대해서 다룬다. 프로젝트 초기에 요구 사항이 어느 정도 안정화되면 이후부터는 큰 틀에서 프로그램이 변경되기보다는 상세 수준에서 변경이 발생할 가능성이 높다.

이러한 상황을 고려했을 때, 저수준 모듈이 변경되더라도 고수준 모듈은 변경되지 않도록 함으로써 유지보수를 조금 더 편하게 하기 위해서 의존 역전 원칙을 지키라고 강조하는 것이다.

의존 역전 원칙을 적용한 예시

예제 1

DIP를 적용하지 않은 구조

https://github.com/binghe819/TIL/raw/master/OOP/SOLID/image/21784C415698700A05.png

  • 타이어의 종류나 타이어 내부의 코드가 자주 바뀔 수 있는 상황이라고 가정했을 때, 자동차스노우 타이어에 직접 의존하는 것은 좋지 않은 설계이다.
  • 자동차스노우 타이어에 의존하고 있기 때문에, 스노우 타이어일반 타이어로 교체할 때 자동차도 같이 코드를 고쳐야 할 가능성이 높다.

DIP를 적용한 코드

https://github.com/binghe819/TIL/raw/master/OOP/SOLID/image/235E853E569870EA35.png

  • 자동차구체적인 타이어가 아닌 추상화된 타이어 인터페이스에만 의존하게 함으로써 구체적인 타이어가 변경돼도 자동차는 그 영향을 받지 않는다.
  • 자동차스노우 타이어에의존했다가, 타이어 인터페이스에 의존하게 됐다. 즉, 고수준 모듈이 저수준 모듈에 의존했던 상황이 역전되어 저수준 모듈이 고수준 모듈에 의존하게 됐다. 이를 보고 ‘의존 역전 원칙’라는 이름을 붙이게 되었다.

예제 2

ArrayList list = new ArrayList(); 대신에 List list = new ArrayList();라고 하는 것이 좋다. 왜냐하면 ArrayList보다 List가 더 추상적(포괄적)인 개념이기 때문이다.

의존 역전 원칙을 지키기 위해 매번 인터페이스를 만들어야 하는것인가 ?

모든 모듈에 대해 추상화를 해서 인터페이스를 만드는 것은 YAGNI 원칙을 위반하는 것이고 비효율적인 방식이다. 왜냐하면 모든 구체적인 클래스 대신에 인터페이스를 전달한다는 것은 변경에 대한 유연성을 확보하기 때문인데, 많은 부분에 있어서 실질적으로 이러한 종류의 유연성을 필요로 하지 않는 경우가 많다. 즉, 오버엔지니어링을 한 것이다. 그러므로 변경될 가능성이 있는 항목에 한해서 SOLID 개념을 사용하고, 변경될 가능성이 작은 부분에 한해서는 YAGNI로 접근하는 것을 추천한다. 정리하자면 다음과 같다.

인터페이스를 사용해야 하는 경우

  • 변화가 자주 일어날 것 같다고 생각이 될 경우
  • 2개 이상의 구현부(콘크리트 클래스)를 가지는 경우
  • 테스트를 가능하게 하기 위해서 인터페이스를 도입해야 하는 경우 (mocking을 사용하기 위해)

인터페이스를 사용하지 않아도 되는 경우

  • 미래에 변화가 자주 일어날 지 아닐 지 모르는 경우

    → 나중에 인터페이스가 필요하다고 느껴질 때 리팩토링을 통해 인터페이스를 도입해도 늦지 않다.

References

binghe819/TIL

Does dependency inversion principle mean that I have to create an interface for every module?

When NOT to apply the Dependency Inversion Principle?

Should you always Code To Interfaces In Java