책/Effective Java

다중 instance 방지 & 의존성 주입[Dependency Injection]

728x90
반응형

# 다중 인스턴스 방지 

인스턴스화를 막기 위해서 private 생성자 사용하자



정적 메서드와 정적 필드만을 가진 클래스

  • 기본 타입 , 배열 관련 메서드만 존재하는 클래스
    • ex) java.lang.Math , java.util.Arrays 
  • 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(팩토리)를 모아놓은 클래스 
    • ex) java.util.Collections 
  • final 클래스와 관련한 메서드를 모아놓은 클래스
    • final 클래스를 상속해서 하위 클래스에 메서드를 넣는 것은 불가능 하기 때문 

 

위와 같이, 정적 멤버만 담은 utility 클래스는 인스턴스로 만들려고 설계한 것이 아님 
→ 따라서 외부에서 해당 클래스의 생성자에 접근하지 못하게 해야함 

하지만 생성자를 명시하지 않으면 자동으로 컴파일러는 default 생성자를 만들어준다. 
public Hello(){}

 

  • Q. 그렇다면 추상 클래스로 만들면 되지 않을까 ? 
    • 물론 개념적으로 추상클래스는 인스턴스로 만들 수 없다.  
    • 하지만 해당 추상 클래스를 상속받아 하위 클래스를 만들어 인스턴스화하면 장땡 
    • 오히려 사용자에게 상속해서 쓰라는 메시지를 잘못 전달하는 경우가 발생할 수 있다. 

해결 방법 

 

private 생성자를 통해 클래스의 인스턴스 화를 막을 수 있다. 

 

  • 명시적 생성자가 Private으로 설정되어있기 때문에 클래스 외부에서는 해당 생성자에 접근할 수 없다. 
  • 해당 방식을 통해 상속이 불가능하게 할 수 있다. 
// 해당 클래스의 생성자의 인스턴스화를 막는다. 
private NoInstance(){
	// 주석을 달아 다른 이가 이해할 수 있도록 도움.
	throw new AssertionError();
}

 

 

 


# 의존성 주입 [ Dependency Injection ] 

 

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

 

클래스들이 자원(혹은 다른 클래스)에 의존하는 경우가 존재한다.

책에 나온 dictionary를 사용하는 오탈자확인(SpellChecker) 예제를 통해 해당 사례를 확인해보자 

 

# 정적 유틸리티 클래스 사용 

public class SpellChecker {
			
	private static final Lexicon dictionary = ...;
    
    private SpellChecker() {} //인스턴스화 방지 (아이템 4)
    
    public static boolean isVaild(String word) { //해당 단어가 유효한가 
                    ...
    }
    
    public static List<String> suggestions(String typo){ // 단어 제시 
    				...	
     }
  } 
  
  // 클래스 외부에서 사용  
  
  SpellChecker.isValid(word);

 

# 싱글턴 방식 사용 

public class SpellChecker {
			
	private final Lexicon dictionary = ...;
    
    private SpellChecker() {} //인스턴스화 방지 (아이템 4)
    
    public static SpellChecker INSTANCE = new SpellChecker(...);
    
    public static boolean isVaild(String word) { //해당 단어가 유효한가 
                    ...
    }
    
    public static List<String> suggestions(String typo){ // 단어 제시 
    				...	
     }
  } 
  
  // 클래스 외부에서 사용  
  
  SpellChecker.INSTANCE.isValid(word);

 

앞서 소개한 두 방식은 사전(dictionary)가 달라질 수 있기 때문에 확장에 유연하지 않고 테스트 또한 어렵다.

 

Q. 그러면 SpellChecker가 여러 사전을 사용할 수 있도록 final 키워드를 없애면 교체할 수 있지 않나요?

 

A. 가능은 하지만 해당 방법으로 해결해도 오류를 발생시키기 쉬우며, 멀티스레드 환경에서는 사용할 수 없는 문제가 있다. 

 

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

 

# 의존성(객체) 주입 형태 

  • 인스턴스 생성시, 생성자에 필요한 자원을 넘겨주는 방식 
public class SpellChecker {
			
	private static final Lexicon dictionary = ...;
    
   	public SpellChecker(Lexicon dict) { // DI(의존성 주입) 
    	this.dictionary = Obejcts.requiredNonNull(dict);
    
    }
    
    public static boolean isVaild(String word) { //해당 단어가 유효한가 
                    ...
    }
    
    public static List<String> suggestions(String typo){ // 단어 제시 
    				...	
     }
  } 
  
  interface Lexicon {}
  
  //Lexicon을 상속받은 새로운 사용자 사전 구현
  public class UserDictionary implements Lexicon {
  					...
  }
  
  
  // 클래스 외부에서 사용  
  Lexicon dic = new UserDictionary();
  SpellChecker spellChecker = new SpellChecker(dic);
  
  spellChecker.isValid(word);

 

  • 해당 패턴의 응용 방법을 통해 생성자에 자원 팩토리를 넘겨주는 방식 존재 
  • 팩토리 메소드 패턴 
  • ex. Supplier<T> 인터페이스 - 해당 방식을 통해 Client는 자신이 명시한 타입의 하위 타입도 생성할 수 있는 팩토리 주입 가능 

 

의존성 주입은 유연성과 테스트 용이성을 개선해주지만, 의존성의 수가 증가하게 되면 코드가 장황해질 수 있다.
Spring에서는 IOC 를 통해 이러한 문제를 해결해준다. 

 

 

728x90
반응형

' > Effective Java' 카테고리의 다른 글

다 쓴 객체 참조 해제하기  (0) 2022.01.10
불필요한 객체 생성 피하는 방법  (0) 2022.01.09
Singleton 보장 방법  (0) 2022.01.03
Builder 패턴  (0) 2022.01.03
정적 팩토리 메서드 [ static factory method ]  (0) 2022.01.01