[아이템 5] 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

2021. 3. 23. 12:42Java/Effective Java

 

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

  • 클래스가 내부적으로 하나 이상의 자원에 의존하고 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸 클래스는 사용하지 않는 것을 권장
  • 자원에 따라 동작이 달라진다면, 자원의 수만큼 인스턴스를 만들어 동작하게 하는 것이 좋음
  • 자원을 직접명시하지 말고 의존 객체 주입을 사용 권장
  • Spring 과 같은 프레임워크에서 DI의 개념으로 많이 쓰임

 

 

 

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.

  • 아래의 예제는 사전을 단 하나만 사용한다면 좋은 코드
  • 실전에서는 사전은 굉장히 여러 종류가 있으며 (한국어 사전, 영어 사전, 특수 어휘용 사전 등...) 특수 어휘용 사전을 별도로 두기도 한다. 심지어 테스트용 사전도 필요할 수 있다
  • dictionary 하나로만 이 역할을 모두 수행하기에는 어렵다.

 

정적 유틸리티를 잘못 사용한 예제

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

	// 객체 생성 방지 
	private SpellChecker() {
	} 

	public static boolean isValid(String word) { 
		... 
	} 

	public static List<String> suggestions (String typo) { 
		... 
	} 
}

// 사용
SpellChecker.isValid(word);
List<String> test = SpellChecker.suggestion(typo)

 

싱글톤 잘못 사용한 예제

public class SpellChecker {
	private final Lexicon dictionary = ...;
    
		// 객체 생성 방지
    private SpellChecker() {
    } 

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

    public static boolean isVaild(String word) {
		...
    }

    public static List<String> suggestions(String typo) {
		...
    }
}

// 사용
SpellChecker.INSTANCE.isValid(word);
List<String> test = SpellChecker.INSTANCE.suggestions(typo);

 

 

 

의존 객체 주입

  • 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방식
  • 자원을 (혹은 그 자원을 만들어주는 팩토리를) 생성자 (혹은 정적 팩토리나 빌더에) 넘겨주는 방식

장점

  • 불변을 보장
  • 클래스의 유연성, 재사용성, 테스트 용이성을 개선
  • 생성자, 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있다.

단점

  • 의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성이 너무 많아지면 코드가 장황해질 수도 있다.

 

의존 객체 주입 예제

  • 클래스(SpellChecker)가 여러 자원 인스턴스를 지원
  • 클라이언트가 원하는 자원(dictionary)을 사용
public class SpellChecker {
    private final Lexicon dictionary;
    
    // 여기서 의존성 주입을!
    public SpellChecker(Lexicon dictionary){
    	this.dictionary = Objects.requireNotNull(dictionary);
    }
    
    public static boolean isVaild(String word) {...}
    public static List<String> suggestions(String typo) {...}
}

// 인터페이스
interface Lexicon {}

// Lexicon 상속 받아서 구현
public class koreaDictionary implements Lexicon {
	...
}

Lexicon dictionary = new koreaDictionary();
SpellChecker spellChecker = new SpellChecker(dictionary);
spellChecker.isVaild(word);