본문 바로가기

JAVA/OOP

의존역전원칙

반응형

의존역전원칙(Dependency inversion principle)

- 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다. 저수준 모듈이 고수준 모듈에서 정의한 추상타입에 의존해야 한다.


고수준모듈?

-> 어떤 의미있는 단일 기능을 제공하는 모듈

저수준모듈?

-> 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현


예) 암호화

고수준모듈

- 바이트 데이터를 읽어와

- 암호화하고

- 결과 바이트 데이터를 쓴다.


저수준모듈

-파일에서 바이트 데이터를 읽어온다.

-AES 알고리즘으로 암호화 한다.

-파일에서 바이트 데이터를 쓴다.


바이트 데이터를 암호화한다는 것이 이 프로그램의 의미 있는 단일 기능으로서 고수준 모듈에 해당한다.

고수준 모듈은 데이터읽기, 암호화, 데이터 쓰기라는 하위 기능으로 구성되는데, 저수준 모듈은 이 하위 기능을 실제로 어떻게 구현할지에 대한 내용을 다룬다.

고수준 모듈에서 암호화라는 하위 기능을 AES알고리즘이라는 저수준 모듈로 구현하게 된다.


고수준모듈이 저수준모듈에 의존할 때

고수준모듈은 상대적적으로 큰틀(상위 수준)에서 프로그램을 다루다면, 저수준 모듈은 각 개별요소(상세)가 어떻게 구현될지에 대해 다룬다.

프로젝트 초기에 요구사항이 어느정도 안정화되면 이후부터는 큰 틀에서 프로그램이 변경되기 보다는 상세 수준에서의 변경이 발생할 가능성이 높아진다.


예) 상품가격을 결정하는 정책

- 쿠폰을 적용해서 가격할인을 받을 수 있다.

- 쿠폰은 동시에 한 개만 적용 가능하다.


고수준 모듈의 정책으로, 상세 내용으로 들어가면 일정금액 할인 쿠폰에서 비율 할인 쿠폰 등 다양한 쿠폰이 존재할 수 있다.

상위 수준에서 쿠폰정책은 한번 안정화되면 쉽게 변하지 않지만, 쿠폰은 상황에 따라 다양한 종류가 추가될 수 있다.


쿠폰을 이용한 가격 계산 모듈이 개별적인 쿠폰 구현에 의존하게 되면, 새로운 쿠폰 구현이 추가되거나 변경될때마다, 가격 계산 모듈이 변경되는 상황이 된다.


public int claculate() {

if() {

//기존 쿠폰 가격 계산 모듈

CouponType1 type1

} else {

//쿠폰2 추가에 따라 가격 계산 모듈 변경

CouponType2 type2

}

}


저수준 모듈이 변경되더라도 고수준 모듈은 변경되지 않는것이 의존역전원칙이다.


의존역전원칙은 이런 문제를 저수준 모듈이 고수준 모듈에 의존하게 만들어서 해결한다.

고수준 모듈에서 저수준 모듈을 사용한다는 것은 고수준 모듈이 저수준 모듈에 의존한다는 의미인데, 추상화를 통해 할 수 있다.


예) 암호화


최초 FlowController 클래스는 FileDataReader 클래스에 직접 의존하고 있으나, ByteSource 추상 타입을 도출함으로써 FlowController와 FileDataReader가 모두 추상 타입은 ByteSourc에 의존하도록 하였다.(상위 타입의 변화가 하위 타입에 영향을 주므로 FileDataReader가 ByteSource에 의존한다고 볼수 있다.)


FlowController -> FileDataReader


//추상타입에 의존

FlowController -> ByteSouce(Interface)

                           FileDataReader



고수준 모듈인 FlowController와 저수준모듈인 FileDataReader가 모두 추상타입인 ByteSource에 의존함으로써, 고수준 모듈의 변경없이 저수준모듈을 변경할 수 있는 유연함을 얻을 수 있게 된다.

의존원칙은 리스코프 치환 원칙과 함께 개방 폐쇄원칙을 따르는 설계를 만들어주는 기반이 되는 것이다.


소스코드 의존과 런타임의존

의존역전원칙은 소스코드에서의 의존을 역전시키는 원칙이다.


의존역전원칙을 적용하기 전, FlowController 클래스의 소스는 FileDataReader를 의존하고 있다.


public class FlowController {

public void process() {

//소스 코드에서 FileDataReader에 대한 의존

FileDataReader reader = new FileDataReader();

}

}


의존역전원칙을 적용 후, FileDataReader 클래스의 소스 코드가 추상화 타입은 ByteSource에 의존하게 되었다.


//상세구현에서 추상 타입에 의존

public class FileDataReader implements ByteSource {

}


ByteSource 인터페이스는 저수준 모듈보다는 고수준모듈인 FlowController 입장에서 만들어지는데, 이것은 고수준모듈이 저수준모듈에 의존했던 상황이 역전되어 저수준 모듈이 고수준모듈에 의존하게 된다는 것을 의미한다.


소스코드상에서의 의존은 역전되어있지만, 런타임에서의 의존은 고수준모듈의 객체에서 저수준모듈의 객체로 향한다.


소스코드의 의존

FlowController -> ByteSource <IF>

   ↑

                          FileDataReader


런타임에서의 객체 의존

FlowController -> FileDataReader


의존역전원칙은 런타임의 의존이 아닌 소스 코드의 의존을 역전시킴으로써 변경의 유연함을 확보할 수 있게 만들어 주는 원칙이지, 런타임에서의 의존을 역전시키는 것은 아니다.


의존역적원칙과 패키지

의존역전원칙은 타입의 소유도 역전시킨다.

의존역전원칙 적용하기전, 데이터 읽기 타입은 FileDataReader를 소유한 패키지가 소유하고 있었다.

의존역전원칙 적용함으로써, 데이터 읽기을 위한 타입을 고수준 모듈이 소유하게 되었다.


 package dataencryptor

 FlowController

  ->

 package filedata

 FileDataReader


 package dataencrypor

 FlowController

  ->

 ByteSource <IF>

 

 

         ↑

 

 

 package filedata

 FileDataReader


타입의 소유 역전은 각 패키지를 독립적으로 배포할 수 있도록 만들어준다.(독립적으로 배포한다는 건 jar파일이나 DLL등의 파일로 배포한다는 것을 뜻한다)


파일이 아닌 소켓으로부터 데이터를 읽어오는 기능으로 변경해야한다면, 배포기준이 되는 패키지는 별도의 jar파일로 만들어질수 있을 것이다.

기존의 filedata.jar 파일을 socketdata.jar 파일로 교체함으로서 데이터를 파일에서 소켓으로부터 읽어 오도록 변경할 수 있게 된다.


 package dataencryptor

 dataencryptor.jar

 FlowController

 

      ------------>

 ByteSource <IF>


 

 

 

           ┌------------

 ---------↑-----------

 ----------┐

   package filedata  package socketdata

 

 

 filedata.jar

 FileDataReader

 

 socketdata.jar

 SocketDataReader


독립적배포가 가능하게 된다.    


타입의 소유가 고수준 모듈로 이동하지 않고 filedata 패키지에 그대로 있었다면, 기존 파일 구현 대신 소켓구현을 사용하게 되면 socketdata.jar 파일뿐만 아니라 기능상 필요없는 filedata.jar도 필요하게 된다.


package dataencryptor

 

 

 dataencryptor.jar

 FlowController 

 

 

         ↓

 

 

 package filedata

 

 package socketdata

 filedata.jar

 ByteSource

 ←

 socketdata.jar

 SocketDataReader

         ↑

  
 FileDataReader  


타입 소유 역전을 하지 않을경우, 상세 구현의 독립적 배포가 안된다.  


의존역전원칙은 개방폐쇄원칙을 클래스 수준뿐만 아니라 패키지 수준까지 확장시켜주는 디딤돌이 된다.


**정리

단일책임원칙과 인터페이스분리원칙은 객체가 커지지 않도록 막아준다.

객체가 많은 기능을 가지게 되면, 객체가 가진 기능의 변경의 여파가 그 객체의 다른 기능까지 번지게 되고 이는 다시 다른 기능을 사용하는 클라이언트에게까지 영향을 준다.

객체가 단일책임원칙을 갖게하고 클라이언트마다 다른 인터페이스를 사용하게 함으로써 한 기능의 변경이 다른곳에까지 미치는 영향을 최소화할 수 있고, 기능 변경을 보다 쉽게 할 수 있도록 만들어준다.


리스코프치환원칙과 의존역전원칙은 개방폐쇄원칙을 지원한다.

개방폐쇄원칙은 변화되는 부분을 추상화하고 다형성을 이용함으로써 기능확장을 하면서도 기존 코드를 수정하지 않도록 만들어준다.

변화되는 추상화할 수 있도록 도와준느 원칙이 바로 의존역전원칙이고, 다형성을 도와주는 원칙은 리스코프치환 원칙인것이다.


SOLID원칙은 사용자 입장에서의 기능 사용을 중시한다.

인터페이스분리원칙은 클라이언트 입장에서 인터페이스를 분리하고 있으며, 의존역적원칙 역시 저수준모듈을 사용하고 고수준모듈 입장에서 추상화 타입을 도출하도록 유도한다. 또한 리스코프치환원칙은 사용자에게 기능 명세를 제공하고 그 명세에 따라 기능을 구현할 것을 약속한다.

사용자 관점에서 설계를 지향하고 있다.


반응형

'JAVA > OOP' 카테고리의 다른 글

단일책임원칙  (0) 2018.11.06
객체지향  (0) 2018.10.16
절차지향  (0) 2018.10.16
컴포넌트  (0) 2018.04.21
다형성  (0) 2018.04.20