제육's 휘발성 코딩
article thumbnail
반응형

자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

많은 클래스가 하나 이상의 자원에 의존한다. 예를 들어 맞춤법 검사기는 사전(dictionary)이 없으면 검사를 할 수 없다. 이러한 맞춤법 검사기와 사전과의 관계를 의존 관계라고 하며, 이런 클래스를 정적 유틸 클래스로 구현한 모습을 자주 볼 수 있다.

정적 유틸 클래스를 잘못 사용한 예

import java.util.List;

public class SpellChecker {
  private static final KoreaDictionary dictionary = ...;

  private SpellChecker() {};

  public static boolean isValid(String word) {...};
  public static List<String> suggestions(String typo) {...};
}

 

싱글톤을 잘못 사용한 예

import java.util.List;

public class SpellChecker {
  private final KoreaDictionary dictionary = ...;

  private SpellChecker() {};

  public static SpeeChecker INSTANCE = new SpellChecker(...);

  public static boolean isValid(String word) {...};
  public static List<String> suggestions(String typo) {...};
}

해당 예시를 보면 두 경우 모두 KoreaDictionary 이라는 사전만 바라보고 있기 때문에 사전이 여러 개일 경우에 유연하지 않고 테스트하기 어렵다.

 

그렇다면 여러 사전을 사용하기 위해서 final 키워드를 제거하고 사전 교체 메서드를 만들면 어떨까?

해당 방식을 사용하면 교체는 가능하지만 오류 가능성과 멀티스레드 환경에 적합하지 않다. 즉, 사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글톤 방식이 적합하지 않다.

 

SpellChecker가 여러 사전 인스턴스를 지원할 수 있는 패턴이 바로 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 패턴이다.

public class SpellChecker {
  private final Dictionary dictionary;

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

  public void getName() {
    System.out.println(this.dictionary.getClass());
  }
}

interface Dictionary {}

class KoreaDictionary implements Dictionary {}
class EnglishDictionary implements Dictionary {}

class SpellCheckerMain {
  public static void main(String[] args) {
    SpellChecker spellChecker1 = new SpellChecker(new EnglishDictionary());
    spellChecker1.getName();
    SpellChecker spellChecker2 = new SpellChecker(new KoreaDictionary());
    spellChecker2.getName();
  }
}
  • 다음과 같이 의존 객체를 생성자를 통해 외부에서 주입하도록 설계하면 SpellChecker의 입장에서 어떤 사진오는지 몰라도 구현할 수 있다.
  • final 키워드를 통해 현재 의존 객체의 불변성을 보장하여 여러 클라이언트가 의존 객체를 안심하고 공유할 수 있다.
  • 해당 패턴의 응용으로 생성자에 자원 팩터리를 넘겨주는 방식이 있다. 팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 의미한다. 즉, 팩터리 메서드 패턴을 구현한 것이다. 자바 8에서 등장한 Supplier<T> 인터페이스가 팩터리를 표현한 완벽한 예이다.

 

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
  • Supplier<T> 인터페이스는 다음과 같이 매개 변수 없이 제네릭을 사용하여 get() 메서드를 통해 해당 타입을 반환해주는 기능이 있다.

 

package effective.item5;

import java.util.function.Supplier;

public class SpellChecker {
    private final Dictionary dictionary;

    public SpellChecker(Supplier<Dictionary> dictionary) {
        this.dictionary = dictionary.get();
    };

    public void getName() {
        System.out.println(this.dictionary.getClass());
    }
}

interface Dictionary {}

class KoreaDictionary implements Dictionary {}
class EnglishDictionary implements Dictionary {}

class SpellCheckerMain {
    public static void main(String[] args) {
        SpellChecker spellChecker1 = new SpellChecker(KoreaDictionary::new);
        spellChecker1.getName();
        SpellChecker spellChecker2 = new SpellChecker(EnglishDictionary::new);
        spellChecker2.getName();
    }
}
  • Supplier<T> 를 사용하여 생성자에 자원 팩터리를 주입할 수 있다.

의존 객체 주입이 유연성과 테스트 용이성을 개선해주지만, 프로젝트가 클수록 코드가 복잡해진다. Dagger, Guice, Spring 과 같은 의존 객체 주입 프레임워크를 사용하면 복잡한 문제를 해결할 수 있다.

클래스가 내부적으로 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글톤과 정적 유틸리티 클래스는 사용하지 않고, 의존 객체 주입을 사용하여 생성자, 빌더, 정적 팩터리 등에 넘겨주자.


REFERENCES

https://m.blog.naver.com/zzang9ha/222087025042

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^