DIP

2018, Dec 14    

의존성 뒤집기 원칙

첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
위키참조

실제로 업무에서 개발시 dip가 필요했던 상황을 토대로 풀어나가 보겠다.

요구사항

  1. 각 PG사로 부터 매 시간 정산 데이터를 가져와 우리 회사 DB에 저장하는 배치를 만든다.
  2. 각 PG사 마다 정산 데이터를 가져오는 방식이나 데이터 포맷이 다르기 때문에 개별적인 처리 구현이 필요함.
  3. 정산 연동을 위한 PG사는 계속해서 늘어날 예정임.
  • PG사별 처리 서비스 객체생성

      class Inicis {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
      class CakaoPay {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
      class LgUplus {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
  • pgId를 넘기면 개별PG 처리에 해당하는 서비스 객체를 생성하여 처리 하는 정산처리 서비스 객체(pgAccountService) 생성

     class PgAccountService {
    
        Pg pg;
    
        PgAccountService(String type) {
          if(type.equal("inicis")) {
            pg = new Inicis();
          }else if(type.equal("CakaoPay")) {
            pg = new CakaoPay();
          }else if(type.equal("LgUplus")) {
            pg = new LgUplus();
          }
        }
    
        batchRun() {    
          pg.connectApi();
          pg.convertData();
          pg.saveData();
        }  
      }
    

문제점

  • 하위 객체(Inicis, CakaoPay, LgUplus)가 변경 됨에 따라 pgAccountService에 영향을 미침.
  • PG사 늘어날 수록(직접 생성하는 하위 객체의 수가 많아 질 수록) 하위 객체에 대한 의존성이 높아짐.

해결 방법

고수준 객체에서 구현객체를 직접 생성하지 않고 PG객체들의 추상화 객체를 만들어 추상화 객체를 사용하도록 함.

  • 각 PG사 객체들의 추상화 클래스를 만든다.
      abstract PG {
        abstract connectAPi();
        abstract convertData();
        abstract saveData();
      }
    
      class Inicis extends PG {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
      class CakaoPay extends PG  {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
      class LgUplus extends PG  {
        connectApi(String dateTime) {}; 
        convertData(); 
        saveData();   
      }
    
  • pg정산 서비스 변경
      class PgAccountService {
        //Pg 인터페이스 타입의 변수 선언
        Pg pg; 
      
        //생성자를 통해 주입받음 
        PgAccountService(Pg pg) {
          this.pg = pg;             
        }
    
        batchRun() {    
          pg.connectApi();
          pg.convertData();
          pg.saveData();
        }  
      }
    
    • PgAccountService는 정산 처리를 수행하는 고수준 객체이다.
    • Inicis, CakaoPay, LgUplus 객체는 각 PG사에 대한 직접적인 정산 처리를 하는 객체로서 저수준 객체이다.
    • PG 객체는 각 개별 pg사들의 추상화 객체로서 고수준 객체에 해당된다.
    • 고수준인 pgAccountService 객체는 PG객체만 신경씀으로서 같은 고수준 객체끼리 소통 하게 된다.
    • 저수준인 Inicis, CakaoPay, LgUplus 객체들은 PG객체를 상속받음으로서 고수준 객체에 의존하게 된다.

    dip

    처음 저수준 객체들에 의존했던 것과 달리 저수준 객체들이 고수준 객체에 의존하게 됨으로서 의존성이 역전 되었다.

장점

  • 고수준 객체는 개별 구현 객체가 어떻게 변경 되든 추상객체(interface or abstract)의 규격만 신경쓰면 된다.
  • 새로운 구현객체가 추가되거나 개별 구현객체가 변경이 되어도 고수준 객체에 영향을 미치지 않는다.
    따라서, 변화에 대한 유연성이 높아진다.

DIP를 지키기 위한 가이드 라인

  • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않는다.
  • 구상 클래스에서 유도된 클래스를 만들지 않는다.
  • 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드하지 않는다.
    (이미 구현되어 있는 메소드를 오버라이드 한다는 것은 애초부터 베이스 클래스가 제대로 추상화 된 것이 아니었다고 볼 수 있다.
    베이스 클래스에서 메소드를 정의할때는 모든 서브 클래스에서 공유할 수 있는 것만 정의해야 한다.)