본문 바로가기

카테고리 없음

의존성 역전의 원칙(DIP, Dependency Inversion Principle)

상위 계층(정책 결정)이 하위 계층(세부 사항)에 의존하는 전통적인 의존관계를 반전(역전)시킴으로써 상위 계층이 하위 계층의 구현으로부터 독립되게 할 수 있다.
public class CheeseBun extends Bun{
}
public class EggBun implements Bun2{
}
이 원칙은 다음과 같은 내용을 담고 있다.[1]
 
첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
 
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
 
이 원칙은 '상위와 하위 객체 모두가 동일한 추상화에 의존해야 한다'는 객체 지향적 설계의 대원칙을 제공한다.[2]
 
위키백과 中에서..
 
나는 DIP 공부를 끝내고 나서 DIP의 핵심을 이렇게 정의했다.
"하위 모듈은 인터페이스나 추상클래스에 의존해야 한다."
(사실 하위 모듈 뿐만이 아니라 상위 모듈 또한 그렇게 해야 한다. 그렇게 해야지 코드의 결합도가 낮아진다.)
 
상위모듈이란? : 추상화 역할을 해주는 인터페이스나 추상클래스를 의미.
 
하위모듈이란? : 인터페이스나 추상클래스를 사용해서 만들어진 구현체, 혹은 객체를 의미한다.
 
위의 말을 쉽게 예시를 들어서 설명해보겠다.
 
우선 의존성 역전이 왜 필요한지 설명하려면 의존성 역전이 없는 상황의 코드를 봐야 한다.
 
의존 역전이 되지 않은 예시코드를 보자.
 
아래의 예시는 제빵사가 로티번이라고 하는 빵을 만드는 코드다.
public class Baker {
    RottyBun rottyBun;
    
    void bakeBraed(RottyBun rottyBun){
        this.rottyBun = rottyBun;
    }
    
}
만약 제빵사가 다른 빵들(계란빵, 치즈빵)을 더 만들줄 알게되면 코드는 아래와 같이 늘어난다.
public class Baker {
    RottyBun rottyBun;
    EggBun eggBun;
    
    CheeseBun cheeseBun;

    void bakeBraed(RottyBun rottyBun){
        this.rottyBun = rottyBun;
    }

    void bakeBraed(EggBun eggBun){
        this.eggBun = eggBun;
    }

    void bakeBraed(CheeseBun cheeseBun){
        this.cheeseBun = cheeseBun;
    }
    
    //만약 제빵사가 만들줄 아는 빵이 늘어나면 그에 따라서 코드도 우후죽순 늘어나야 한다.
}
제빵사가 만들줄 아는 빵이 늘어날때마다 코드는 엄청나게 늘어난다.
가독성도 좋지 않고 유지보수하기도 어려워진다.
 
이제 의존성 역전 원칙(DIP)를 적용해보도록 하겠다.
 
DIP에서 의존관계를 맺을때는 변화가 잦은 것보다는 거의 없는 것에 의존해야 한다고 말한다.
여기서 변하기 쉬운 것은 계란빵, 치즈빵, 로티번 등이 있다.
제빵사가 빵자체를 만드는 사실은 변하기 어렵다.

 

위의 상황을 코드로 표현해보겠다.
public class Baker{
    private Bun bun;
    
    private Bun2 bun2;

    public void setBun(Bun bun){
        this.bun = bun;
    }

    public void setBun(Bun2 bun2){
        this.bun2 = bun2;
    }
}
abstract class Bun{

}
public interface Bun2 {
}
public class RottyBun extends Bun{

}
public class Main {
    public static void main(String[] args) {

        RottyBun rottyBun = new RottyBun();
        CheeseBun cheeseBun = new CheeseBun();
        
        Baker baker = new Baker();
        
        // 추상클래스를 상속받은 로티번과 치즈번 사용
        baker.setBun(rottyBun);
        baker.setBun(cheeseBun);

        // 인터페이스의 구현체 에그번을 사용
        EggBun eggBun = new EggBun();
        baker.setBun(eggBun);
    }
}
필요없는 부분은 전부 도려낸 코드여서 생략된 내용이 많다...
여하튼 중요한 것은 하위모듈인 RottyBun 이나 EggBun (코드에는 안적었지만 CheeseBun 또한)이 추상클래스(Bun)나 인터페이스(Bun2) 에 의존하고 있다.
 
이렇게 코드를 작성하면 빵의 종류가 많아지거나 변경되어도 Baker 객체에는 영향을 미치지 않는다.