클래스와 자원

• 클래스는 보통 하나 이상의 자원에 의존한다.

    ◦  의존한다 = 관계성이 있다 / 자원이 클래스의 동작에 영향을 준다.

    ◦  예 : 맞춤법 검사기는 사전 객체에 의존한다. (사전에 존재하는 단어가 아니면 밑줄)


하나 이상의 자원에 존재하는 클래스(1) : 맞춤법 검사기의 잘못된 구현 방식

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

	public static boolean isValid(String word) { ... }
}
public class SpellChecker {
	private final Lexicon dictionary = ... ;

	public static SpellChecker INSTANCE = new SpellChecker();
	private SpellChecker() {}
	
	public static boolean isValid(String word) { ... }
}

• 위의 두가지 방식은 클래스가 단 하나의 사전 객체에만 의존한다고 가정하고 있다 BAD

    ◦  일반적으로 사전은 하나로 구성되어 있지 않고, 여러개로 나누어져 있다. (전세계 언어 사전 X)

    ◦  제안) final 한정자를 제거하고, 다른 사전으로 교체하는 메서드를 추가?

         -  어색하고 오류를 내기 쉽다 + 멀티스레드 환경에서는 사용할 수 없다.

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

  public static SpellChecker INSTANCE = new SpellChecker();
	private SpellChecker() {}

	public static boolean isValid(String word) { ... }
  public static void changeDictionary(Lexicon new) { dictionary = new; }
}

의존 객체 주입 방법

• 해당 클래스의 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방법

public class SpellChecker {
		private final Lexicon dictionary;
		
		public SpellChecker(Lexicon dictionary){
				this.dictionary = Objects.requireNonNull(dictionary);
		}
		public boolean is Valid(String word) { ... }
}
public class KoreanDict implements Lexicon { ... }
public class EnglishDict implements Lexicon { ... }

Lexicon kordict = new KoreanDict();
Lexicon engdict = new EnglishDict();

SpellChecker korchecker = new SpellChecker(kordict);
SpellChecker engchecker = new SpellChecker(engdict);

korcheker.isVaild("한국말");
engcheker.isVaild("English");

불변성을 보장하기 때문에 여러 클래스가 같은 의존 객체들을 공유할 수 있다

    ◦  this.dictionary 로 받아서 사용했기 때문

• 정적 팩토리와 빌더에서도 이런 방식으로 의존 객체를 넘겨줄 수 있다.


응용 : 생성자에 자원 팩토리를 넘겨주는 방식 (팩토리 메서드 패턴)

 팩토리 = 호출될 때 마다 특정 타입의 인스턴스를 만들어주는 객체

 Supplier<T> 인터페이스 = 팩토리의 완벽한 표현

    ◦  함수형 인터페이스 : 매개변수는 없고, 반환값만 있다.

~~String supplier= new String();
supplier =  "Hello World";~~

Supplier<String> supplier= () -> "Hello World";

String result = supplier.get();
System.out.println(result); // Hello World

• Supplier<T>를 입력으로 받는 메서드는 한정적 와일드카드 타입(bounded wildcard type)을 사용하여
    팩터리의 타입 매개변수를 제한해야 한다.

    ◦  와일드 카드 : 제네릭 코드에서 물음표(?) 로 표기되어 있는 것. 아직 알려지지 않은 타입 의미.

    ◦  한정적 와일드카드 : (?) 가 무언가를 extend 한다.

    ◦  명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩토리를 넘길 수 있게 됨

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

 


의존 객체 주입 방식의 장점

• 코드의 유연성과 재사용성, 테스트의 용이성을 개선시킴

• 그러나 의존성이 많은 큰 프로젝트에서는 코드를 어지럽게 만들기도 함

    ◦  Spring 등의 의존 객체 주입 프레임워크를 사용하는 것이 좋음

          -  의존 객체를 직접 주입하도록 설계된 API를 사용한다.

@Controller
public class MemberController {
		private final MemberService memberService;

		@Autowired
		public MemberController(MemberService memberService) {
				this.memberService = memberService;
		}
}

• 생성자에 @Autowired가 있으면 스프링이 연관된 객체를 스프링 컨테이너에서 찾아서 넣어준다.

• 이렇게 객체 의존관계를 외부에서 넣어주는 것을 DI(Dependency Injection), 의존성 주입이라고 한다.