많은 클래스가 하나 이상의 자원에 의존한다.(컴포지션)

이 의존하는 클래스를 정적 유틸리티 클래스(아이템 4)로 구현하게 되면 유연하지 않고 테스트 하기 어려운 구조가 된다.

 

  • 정적 유틸리티의 안좋은 예- 유연하지 않고 테스트 어렵다.

public class SpellChecker {

  private final Lexicon dictionary = ...;

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

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

 

  • 싱글톤의 안좋은 예- 유연하지 않고 테스트 어렵다.
public class SpellChecker {

  private final Lexicon dictionary = ...;

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

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

 

  • SpellChecker가 여러 사전을 사용할 수 있도록 하기 위해서 좋지 않은 방법 - 멀티스레드 환경X
public class SpellChecker {

  private Lexicon dictionary = ...;

  public static void changeDictionary(){
		this.dictionary = ...
  }
}

 

클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준 다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋다. 

이 자원들을 클래스가 직접 만들게 해서도 안 된다. 

 

 

대신 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식을 사용한다.(불변 보장)

이는 의존 객체 주입의 한 형태로, 객체를 생성할 때 의존 객체를 주입해주면 된다. => c.f. Strategy Pattern

public class SpellChecker {

  private final Lexicon dictionary;

  public SpellChecker(Lexicon dictionary) { // 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨준다
    this.dictionary = dictionary;
  }

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

}

 

 

이 패턴의 변형으로, 생성자에 자원 팩터리를 넘겨주는 방식이 있다.(Factory Method Pattern)

팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다.

 

ex) Supplier<T> interface(java8)

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

이 방식을 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무엇이든 생성할 수 있는 팩터리를 넘길 수 있다. 

// 클라이언트가 제공한 팩터리가 생성한 타일(Tile)들로 구성된 모자이크 (Mosaic)를 만드는 메서드
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

 

예를 들어 어떤 사람의 이름과 생일을 입력해두고 getAge()로 나이를 가져오는 Person 클래스를 만든다고 하자.

package com.example.sypark9646.item5;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class Person {

	final String name;
	private final LocalDate dateOfBirth;
	private final LocalDate currentDate;

	public Person(String name, LocalDate dateOfBirth) {
		this(name, dateOfBirth, LocalDate.now());
	}

	public Person(String name, LocalDate dateOfBirth, LocalDate currentDate) {
		this.name = name;
		this.dateOfBirth = dateOfBirth;
		this.currentDate = currentDate;
	}

	long getAge() {
		return ChronoUnit.YEARS.between(dateOfBirth, currentDate);
	}

	public static void printAge(PersonSupplierConstruct person) {
		System.out.println(person.name + " is " + person.getAge());
	}
}

위 방법의 경우, getAge ()는 현재 날짜가 아닌 Person 객체가 생성 된 시기를 기반으로 한다.

이 문제는 Supplier <LocalDate>를 사용하면 해결된다. 현재 시간을 Supplier를 이용하여 주입하는 것이다.

package com.example.sypark9646.item5;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.function.Supplier;

public class PersonSupplierConstruct {

	final String name;
	private final LocalDate dateOfBirth;
	private final Supplier<LocalDate> currentDate;

	public PersonSupplierConstruct(String name, LocalDate dateOfBirth) {
		this(name, dateOfBirth, LocalDate::now);
	}

	public PersonSupplierConstruct(String name, LocalDate dateOfBirth, Supplier<LocalDate> currentDate) {
		this.name = name;
		this.dateOfBirth = dateOfBirth;
		this.currentDate = currentDate;
	}

	public long getAge() {
		return ChronoUnit.YEARS.between(dateOfBirth, currentDate.get());
	}

	public static void printAge(PersonSupplierConstruct person) {
		System.out.println(person.name + " is " + person.getAge());
	}
}

 

 

용어

  • 한정적 와일드카드 타입(bounded wildcard type, 아이템 31)

+ Recent posts