ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 의존 역전 원칙
    카테고리 없음 2023. 4. 22. 14:39
    아주 저번시간 인터페이스의 분리 원칙에 이어 오늘은 의존 역전 원칙에 대해서 알아보자. 원래는 단일책임원칙에 대해서 하려고 했는데 적당한 예제가 생각나지 않아서 적당한 예제가 생각날때 까지 잠시 보류..

    의존 역전 원칙 (DIP)

    의존 역전 원칙이란 다음과 같다
    고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다.
    흐으음.. 이게 무슨말인가? 고수준 모듈은 무엇이며 저수준 모듈은 또 무엇인가?.. 일단 고수준 모듈과 저수준 모듈에 대해서 살펴보자. 고수준 모듈은 어떤 의미가 있는 단일 기능을 제공하는 모듈이라고 생각하면되고, 저 수준 모듈은 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능을 실제로 구현한 것으로 정의 할 수 있다. 좀 더 풀어서 이야기 해보자. 예를들어 만약에 어떤 바이트 데이터를 읽어와 특정한 데이터로 결과를 반환하는 것으로 이야기를 해보자.
    1. 고수준 모듈
      • 바이트 데이터를 읽어 와서 특정한 데이터로 결과를 반환한다.
    2. 저수준 모듈
      • 파일에서 바이트 데이터를 읽어 온다.
      • Json 데이터로 결과를 반환한다.
    이렇게 저수준 모듈은 좀 더 구체적인 구현을 어떻게 할지에 대한 내용을 다루는 것이다. 코드를 보며 좀 더 살펴보도록 하자.
    public class Response {
    
      private JsonConverter jsonConverter = new JsonConverter();
    
      public String response()  {
        byte[] bytes = null; //파일은 생략
        return jsonConverter.convert(bytes);
      }
    
    }
    
    class JsonConverter {
    
      public String convert(byte[] bytes) {
        //json ...
        return "json";
      }
    }
    
    
    위는 DIP 원칙을 어기고 있는 코드이다. 아까 말했듯이 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안된다. 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다. 를 어기고 있다. 다시 말해 고수준모듈인 Response 는 저수준 모듈인 JsonConverter 구현에 직접적으로 의존하고 있다. 좀 더 DIP 원칙을 해결하기 위한 코드를 작성 해보록 하자.
    public class Response {
    
      private Converter converter = new JsonConverter();
    
      public String response() {
        byte[] bytes = null; //파일은 생략
        return converter.convert(bytes);
      }
    
    }
    
    interface Converter {
    
      String convert(byte[] bytes);
    
    }
    
    class JsonConverter implements Converter {
    
      @Override
      public String convert(byte[] bytes) {
        //json ...
        return "json";
      }
    }
    
    Converter 라는 인터페이스를 생성후에 JsonConverterConverter를 구현하였다. 그렇게 되므로써 JsonConverterConverter를 의존하고 있게 된다. 만약 요구사항이 변경되어 xml로 데이터를 반환해야 한다면 XmlConverter클래스를 만든 후에 갈아 끼우면 된다.
    public class Response {
    
      private Converter converter = new XmlConverter();
    
      public String response() {
        byte[] bytes = null; //파일은 생략
        return converter.convert(bytes);
      }
    
    }
    
    class XmlConverter implements Converter {
    
      @Override
      public String convert(byte[] bytes) {
        //xml ...
        return "xml";
      }
    }
    
    이렇게 우리는 손쉽게 원하는 형식의 데이터를 자유자재로 반환할 수 있게 되었다. 하지만 만약에 Response를 라이브러리로 배포를 한다면 말이 좀 달라진다. 각각의 스타일에 맞게 매번 배포를 해야한다. 왜냐하면 아직까지 Response 에는 구체적인 클래스인 JsonConverterXmlConverter를 의존하기 때문이다.

    DI (의존성 주입)

    우리는 해결할 문제가 남아있다. 아직도 구체적인 클래스인 JsonConverterXmlConverterResponse가 의존하고 있기 때문이다. 이것을 해결하기 위해 우리는 의존자 모듈에 의존성 모듈 인스턴스를 제공하면 되는데 이것이 의존성 주입이다.

    생성자 주입 (Constructor Injection)

    필자는 항상 사용하는 주입 방식이며 필자가 항상 사용하는 Spring도 권장하는 주입 방식이다. 의존자 인스턴스 생성됨과 동시에 생성되므로 좀 더 안전하게 사용할 수 있는 장점이 있다. 의존관계가 명확해서 누락될 시 에는 컴파일 에러가 나므로 필자도 추천하는 방식이다.
    public class Response {
    
      private final Converter converter;
    
      public Response(Converter converter) {
        this.converter = converter;
      }
    }
    

    속성 주입 (Property Injection)

    흔히 Spring에서는 setter 주입이라고 한다. 필수가 아닌 옵션의 형태의 의존성을 갖고 있을 때 사용하면 될 듯 싶다. 필자도 가끔 사용하는 주입 방식이다. 인스턴스가 생성후에 진행되어 만약 필요하다면 의존성을 체크도 해야 되는 부분이 있을 수 있다.
    public class Response {
    
      private Converter converter;
    
      public void setConverter(Converter converter) {
        this.converter = converter;
      }
    }
    

    인터페이스 주입(Interface injection)

    솔직히 써본적이 없어서 글을 남길 수 없다.. 필요하다면 자세한 내용은 인터넷에서 찾아보길 권장하며 필자 생각는 사용할 일이 없어서 찾아보지도 않았..

    필드 주입 (Field Injection)

    이건 솔직히 딱히 권장하지 않는 방법이고 일반적인 DI라하면 필드 주입은 없는 걸로 알고 있다. 하지만 JEE 스펙에는 버젓이 필드 주입이 있다. 그리고 Spring이나 구글주스나 여타 프로임워크에서 지원해주는 것이지 실제로 프레임워크 없이 사용하려면 리플렉션을 통해 접근 해야 되지 않나 싶다. 필자는 되도록이면 생성자 주입을 자주 사용한다. 이렇게 됨으로써 JsonConverterXmlConverter은 상세 구현에서 Converter를 의존한다는 것을 알 수있다. 이는 고수준 모듈이 저수준 모듈에 의존했던 상황이 역전되어 저수준 모듈이 고수준 모듈에 의존하게 된다는 것을 의미해 이것을 의존 역전원칙이라고 부른다. 여기서 또 중요한 것은 DIP(Dependency Inversion Principle)DI(Dependency Injection)는 다르다. 실제 DI(Dependency Injection)는 DIP(Dependency Inversion Principle) 을 구현하는 기법중 한개일 뿐이다. DIP를 구현하는 다른 기법은 서비스 로케이터(Service Locator)라고 불린다. 이것은 필자도 아직 잘 모르기에 기회가 된다면 공부후에 포스팅을 해보겠다. DIP가 상위 개념이라고 생각하면 될 듯 싶다. 오늘은 이렇게 어려운 DIP에 대해서 공부해봤다. 실제 DIP를 적용하면 나중에 다룰 개방폐쇄 원칙과 리스코프 치환 원칙을 따르게 만들어주는 기반이 된다. 내용이 쉬운거 같으면서 쉽지 않은 내용이다? 나름 쉽게 작성한다고 했는데..

    댓글

Designed by Tistory.