본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 5 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 - 핵심 정리

by 개발인생 2022. 9. 21.
반응형

아이템 5 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 - 핵심 정리

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

모든 경우에 의존 객체 주입을 사용하는 것이 아니라

사용하는 자원에 따라 동작이 달라지는 클래스의 경우에는 의존 객체 주입을 사용해야한다.

public class Dictionary {
   public boolean contains(String word) {
      return true;
   }

   public List<String> closeWordsTo(String typo) {
      return Arrays.asList(typo);
   }
}

Dictionary 클래스가 있고

public class SpellChecker {

   private static final Dictionary dictionary = new Dictionary();

   private SpellChecker() {}

   public static boolean isValid(String word) {
      // TODO 여기 SpellChecker 코드
      return dictionary.contains(word);
   }

   public static List<String> suggestions(String typo) {
      // TODO 여기 SpellChecker 코드
      return dictionary.closeWordsTo(typo);
   }
}

Dictionary 클래스를 사용하는 SpellChecker 클래스가 있다.

기능은 어떠한 단어에 대해 Dictionary 클래스를 통해 올바른 단어인지, 비슷한 단어가 있는지

꺼내주는 기능이라고 가정한다.

Dictionary 클래스는 영어에 관련된 사전인지, 독일어에 관련된 사전인이 달라질 수 있다.

사전이 달라질때마다 기능은 똑같지만 새로운 SpellChecker 클래스가 만들어질 것이다.

또한, 테스트시에도 어려움이 많다.

Dictionary 클래스를 바꿔 넣을 수 없기 때문에 Dictionary 클래스를 생성해야한다.

만약 Dictionary 클래스를 생성하는데 많은 비용(CPU, Memory)이 든다면 테스트하는게 비효율적이게 된다.

class SpellCheckerTest {

   @Test
   void isValid() {
      assertTrue(SpellChecker.isValid("test"));
   }
}

위와 같이 테스트를 할시에 SpellChecker 클래스에 있는 Dictionary 클래스 인스턴스를 매번 만들게 된다.

자원을 직접 명시한다는건

 private static final Dictionary dictionary = new Dictionary(); // 자원을 직접 생성 == 자원을 직접 명시

위처럼 자원을 직접 생성한다는 뜻이다.

본인이 가지고있는 리소스에 따라 동작이 달라질 경우에는 의존성 주입 을 사용해야한다.

public class SpellChecker {

   private final Dictionary dictionary = new Dictionary();

   public static final SpellChecker INSTANCE = new SpellChecker();

   private SpellChecker() {}

   public boolean isValid(String word) {
      // TODO 여기 SpellChecker 코드
      return dictionary.contains(word);
   }

   public List<String> suggestions(String typo) {
      // TODO 여기 SpellChecker 코드
      return dictionary.closeWordsTo(typo);
   }
}

위처럼 싱글톤으로 작성해도 동일한 문제가 생긴다.

Dictionary 없이는 테스트하기가 어려워진다.

또한, 유연성과 재사용성이 떨어지게 된다.

Dictionary 가 바뀔때마다 SpellChecker 를 새로 만들어야 하기 때문이다.

public class SpellChecker {

   private final Dictionary dictionary;

   public SpellChecker(Dictionary dictionary) {
      this.dictionary = dictionary;
   }

   public boolean isValid(String word) {
      // TODO 여기 SpellChecker 코드
      return dictionary.contains(word);
   }

   public List<String> suggestions(String typo) {
      // TODO 여기 SpellChecker 코드
      return dictionary.closeWordsTo(typo);
   }
}

의존성 주입을 통해 코드를 리펙토링 해보면 다음과 같다.

   private final Dictionary dictionary;

   public SpellChecker(Dictionary dictionary) {
      this.dictionary = dictionary;
   }

위의 부분이 의존성 주입부분이다.

어딘가에서 Dictionary 를 받을 수 있는 코드를 만들어 놓고

Dictionary 를 명시한 코드를 없앴다.

이렇게 되면 SpellChecker 의 모든 코드가 재사용이 가능해진다.

물론, Dictionary 인터페이스일 때라는 전제조건이 있다.

Dictionary 가 인터페이스가 아니라면 메서드에 대한 규약 이 없기때문에

SpellChecker 의 코드를 재사용하기가 불가능해진다.

public interface Dictionary {
   boolean contains(String word);
   List<String> closeWordsTo(String typo);
}

Dictionary 인터페이스를 만든다.

이렇게하면 SpellChecker 의 코드를 재사용할 수 있게된다.

어떤 형식의 Dictionary 의 구현체가 오더라도 메서드의 이름이 변경되지 않기 때문이다.

public class DefaultDictionary implements Dictionary{

   @Override
   public boolean contains(String word) {
      return false;
   }

   @Override
   public List<String> closeWordsTo(String typo) {
      return null;
   }
}

Dictionary 인터페이스를 구현한 DefaultDictionary 를 생성한다.

코드를 테스트하기도 간단해진다.

class SpellCheckerTest {

   @Test
   void isValid() {
      SpellChecker spellChecker = new SpellChecker(new DefaultDictionary());

      assertTrue(spellChecker.isValid("test"));
   }
}

의존객체 주입을 사용한 클래스의 경우에는

이렇게 SpellChecker 클래스의 외부에서 SpellChecker 에서 사용할 객체를 주입하기 때문에

Mock 객체를 만들어 테스트 하기가 쉬워진다.

반응형

댓글