DIP(Dependency Inversion Principle)
DIP?
DIP는 의존성 역전 원칙(Dependency Inversion Principle)의 약자로 구체화가 아닌 추상화에 의존해야한다는 원칙이다.
즉, 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안되며 오히려 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야함을 의미한다.
상/하위 모듈
-
고수준 모듈(상위 모듈): 어떤 의미있는 단일 기능을 제공하는 모듈 (interface, 추상 클래스)
-
저수준 모듈(하위 모듈) : 고수준 모듈의 기능을 구현하는데 필요한 하위 기능,모듈 (메인 클래스, 객체)
즉, 고수준 모듈이 사용하는 모듈을 의미한다.
주의점
사실 고수준 모듈에서 저수준 모듈을 사용하는것이니, 고수준 모듈이 저수준 모듈을 의존하는게 더 자연스러워 보인다.
하지만 저수준 클래스는 빈번하게 변경되는데, 고수준 클래스가 직접 의존한다면 너무 영향을 받기 쉬우므로 의존 관계를 역전 시켜야한다.
예제
package SOLID;
// 상위 모듈
class Kid{
private Robot toy ; // 하위모듈 직접의존
private Lego toy2 ; // 직접 추가해줘야함 **
public void setToy(Robot toy) {
this.toy = toy ;
}
public void setToy(Lego toy2) {
this.toy2 = toy2 ;
}
public void play() {
System.out.println(toy.toString());
}
}
// 하위 모듈
class Robot{}
class Lego{}
public class DIP {
public static void main(String[] args) {
Robot robot = new Robot() ;
Kid k = new Kid();
k.setToy(robot);
k.play();
// 기능 수정 : 레고를 갖고놀고 싶다
Lego lego = new Lego() ;
Kid k = new Kid() ;
k.setToy(lego);
k.play();
}
}
문제
장난감을 하나 더 추가하기 위해 Kid 클래스를 매번 변경해줘야 함
이는 변하기 쉬운 장난감이라는 저수준 모듈(하위 모듈)에의해 아이라는 고수준 모듈(상위 모듈)이 영향에 직접적으로 노출.
해결
아이가 개별적인 장난감을 의존하는 것이 아닌 장난감이 아이를,
정확히 말하면 아이가 정의한 추상 타입에 의존해야한다.
package SOLID;
// 상위 모듈
class Kid{
private Toy toy ;
// 의존: 상위 모듈에서 정의한 추상 타입 (이게 필요해! 라는 설계 자체를 의미)
// ==> 여기에 의존
public void setToy(Toy toy) {
this.toy = toy ;
}
public void play() {
System.out.println(toy.toString());
}
}
// 추상 클래스
abstract class Toy{}
// 하위 개별 모듈
class Robot extends Toy{ // 의존
public String toString() {
return "Robot" ;
}
}
class Lego extends Toy{ // 의존
public String toString() {
return "Lego" ;
}
}
public class DIP {
public static void main(String[] args) {
Toy robot = new Robot() ;
Kid k = new Kid();
k.setToy(robot);
k.play();
// 기능 수정 : 레고를 갖고놀고 싶다
Toy lego = new Lego() ;
k.setToy(lego);
}
}
이렇게 하면 아이가 구체적인 객체들(로봇, 레고, 자동차) 등을 직접 의존하지 않고 장난감이라는 추상클래스를 의존한다.
동시에, 그런 구체적인 객체들도 상위개념인 장난감 추상 클래스에 의존한다.
이렇게 설계하면 장난감의 종류가 추가되고 변경되어도 상위 클래스인 ‘아이’의 코드엔 변화가 없다
사례 : Controller, Service, Repository
해당 원칙에 따르면, 이전 프로젝트에서 Service 계층에서 코드가 구현된 Repository class를 직접 참조한 코드는 위반된 코드이다.
그래서 보통 Spring, Jpa에선 Repository 계층을 interface로 우선 정의해두고, 해당 인터페이스를 구현한 RepositoryImpl을 정의하여 사용한다.
interface를 두지 않더라도, Spring에서 기본적으로 사용하는 ‘DI’, 즉 의존성 주입 디자인 패턴은 이러한 DIP의 원칙에 따른다. Controller 코드 내에서 직접 new 연산자로 해당 객체를 생성(의존)하는 것이 아닌 생성자를 사용해 외부 객체를 주입 받는다. 이를 통해 만약 주입하는 외부 객체가 변경되더라도 Controller 내부의 코드를 변경할 필요 없다.
유사 개념 : IOC
IOC는 ‘제어의 역전(Inversion Of Control)’을 의미한다.
어떤 클래스 내부에서 다른 객체를 생성하고 이용할 때, 직접 코드를 생성하여 ‘제어’라고 한다.
이러한 제어 방식을 역전한다는 것은, 클래스 내부에서 하는 것이 아닌 외부에서부터 인자로 받아 초기화 한다는 것이다.
즉 클래스 외부에서 제어권을 갖도록 해, 변경에 유연한 코드가 될 수 있도록 한다.
결론
DIP는 의존 관계를 맺을 때 자신보다 변화하기 쉬운 것을 의존해서는 안되고, 거의 변화가 없는 ‘개념’에 의존해야한다는 기본 원리를 가진다.
즉, 상위 모듈은 구체적인 객체가 아닌 추상화에 의존하며, 하위 모듈들 또한 해당 추상클래스를 의존(구현)하는 방식이 바람직하다.
참고 사이트
💠 완벽하게 이해하는 OCP (개방 폐쇄 원칙) (tistory.com)
댓글남기기