-
아이템5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라Java/이펙티브 자바 2022. 11. 1. 13:47
핵심정리 : 자원을 직접 명시하지말고 의존 객체 주입을 사용하라
- 사용하는 자원에 따라 동작이 달라지는 클래스는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다
- 의존 객체 주입이란 인스턴스를 생성할 때 필요한 자원을 넘겨주는 방식이다
- 이 방식의 변형으로 생성자에 자원 팩터리를 넘겨줄 수 있다
- 의존 객체 주입을 사용하면 클래스의 유연성, 재사용성, 테스트 용이성을 개선할 수 있다.
public interface Dictionary { boolean contains(String word); List<String> closeWordsTo(String type); }
public class SpellChecker { // private static final Dictionary dictionary = new Dictionary(); // 자원 명시 private final Dictionary dictionary; public SpellChecker(Dictionary dictionary) { this.dictionary = dictionary; } public boolean isValid(String word) { return dictionary.contains(word); } public List<String> suggestions(String type) { return dictionary.closeWordsTo(type); } }
ㆍ자원을 직접 명시한다 : 객체 생성을 해준다.
class SpellCheckerTest { @Test void isValid() { SpellChecker spellChecker = new SpellChecker(new DefaultDictionary()); spellChecker.isValid("test"); } }
완벽공략
p29, 이 패턴의 쓸만한 변형으로 생성자에 자원 팩터리를 넘겨주는 방식이 있다
p29, 자바 8에서 소개한 Supplier<T> 인터페이스가 팩터리를 표현한 완벽한 예다.
public class SpellChecker { private final Dictionary dictionary; // 1 public SpellChecker(Dictionary dictionary) { this.dictionary = dictionary; } // 2 public SpellChecker(DictionaryFactory dictionaryFactory){ this.dictionary = dictionaryFactory.get(); } // 3 public SpellChecker(Supplier<Dictionary> dictionarySupplier) { this.dictionary = dictionarySupplier.get(); } ... }
// 3. supplier를 쓴 경우 @Test void isValid() { // SpellChecker spellChecker = new SpellChecker(() -> new DefaultDictionary()); SpellChecker spellChecker = new SpellChecker(DefaultDictionary::new); spellChecker.isValid("test") }
public class DictionaryFactory { public static Dictionary get() { return new DefaultDictionary(); } }
// 2. factory 쓴 경우 @Test void isValid() { // SpellChecker spellChecker = new SpellChecker(() -> DictionaryFactory.get()); SpellChecker spellChecker = new SpellChecker(DictionaryFactory::get); spellChecker.isValid("test") }
p29, 한정적 와일드카드 타입을 사용해 팩터리의 타입 매개변수를 제한해야 한다
public SpellChecker(Supplier<? extends Dictionary> dictionarySupplier) { this.dictionary = dictionarySupplier.get(); }
p29, 팩터리 메소드 패턴
p30, 의존 객체가 많은 경우에 Dagger, Guice, 스프링 같은 프레임워크 도입을 고려할 수 있다.
완벽공략 14. 팩터리 메소드 패턴
: 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다
- 새로운 Product를 제공하는 팩토리를 추가하더라도, 팩토리를 사용하는 클라이언트 코드는 변경할 필요가 없다.
public class SpellChecker { private Dictionary dictionary; public SpellChecker(DictionaryFactory dictionaryFactory) { this.dictionary = dictionaryFactory.getDictionary(); } public boolean isValid(String word) { return dictionary.contains(word); } public List<String> suggestions(String type) { return dictionary.closeWordsTo(type); } }
public interface DictionaryFactory { Dictionary getDictionary(); }
public class DefaultDictionaryFactory implements DictionaryFactory{ @Override public Dictionary getDictionary() { return new DefaultDictionary(); } }
public class MockDictionaryFactory implements DictionaryFactory{ @Override public Dictionary getDictionary() { return new MockDictionary(); } }
- 확장에 열려있고 확장에 닫혀있다. ( OCP )
완벽공략 15. 스프링IoC : BeanFactory 또는 ApplicationContext
- Inversion of Control : 뒤짚힌 제어권
ㆍ자기 코드에 대한 제어권을 자기 자신이 가지고 있지 않고 외부에서 제어하는 경우
ㆍ제어권? 인스턴스를 만들거나, 어떤 메소드를 실행하거나, 필요로 하는 의존성을 주입 받는 등
- 스프링 IoC 컨테이너 사용 장점
ㆍ수많은 개발자에게 검증되었으며 자바 표준 스팩(@Inject)도 지원한다
ㆍ손쉽게 싱글톤 Scope을 사용할 수 있다
ㆍ객체 생성 (Bean) 관련 라이프사이클 인터페이스를 제공한다.
○ ex) Spring AOP
public class App { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); SpellChecker spellChecker = applicationContext.getBean(SpellChecker.class); spellChecker.isValid("test"); } }
@Configuration @ComponentScan(basePackageClasses = AppConfig.class) public class AppConfig { // // @Bean // public SpellChecker spellChecker(Dictionary dictionary) { // return new SpellChecker(dictionary); // } // // @Bean // public Dictionary dictionary() { // return new SpringDictionary(); // } }
@Component public class SpellChecker { private Dictionary dictionary; public SpellChecker(Dictionary dictionary) { this.dictionary = dictionary; } public boolean isValid(String word) { return dictionary.contains(word); } public List<String> suggestions(String type) { return dictionary.closeWordsTo(type); } }
@Component public class SpringDictionary implements Dictionary { @Override public boolean contains(String word) { System.out.println("contains " + word); return false; } @Override public List<String> closeWordsTo(String type) { return null; } }
* POJO ( Plain Of Java Object )
- 스프링을 쓴다고 해서 인터페이스를 구현해야 한다거나 클래스를 상속받아야 한다던가 (침투적 프레임워크) 할 필요가 없다. (비침투적 프레임워크)
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템7. 다 쓴 객체 참조를 해제하라. (0) 2023.06.01 아이템6. 불필요한 객체 생성을 피하라 (0) 2022.11.10 아이템4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) 2022.11.01 아이템3. 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) 2022.10.31 아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) 2022.10.31