public 생성자
를 사용하는 방식과 정적 팩터리 메소드
를 사용하여 생성하는 방법이 있다. Item
클래스 내부의 함수이다.public 생성자
public Item(){
// 생성에 필요한 로직
}
정적 팩터리 메서드(Static Factory Method)
- public static 함수 내부에서 인스턴스를 생성하고, 그 인스턴스를 반환하는 방식이다.
- 생성에 대한 부분을 해당 함수에서 처리하므로, 기본 생성자는 외부에서 별도로 생성할 수 없게 제약을 거는 것이 좋다.
private Item() {
}
public static Item createItem(){
Item item = new Item();
// 생성에 필요한 로직
return item;
}
그렇다면 정적 팩터리 메소드가 무엇일까?
정적 팩터리 메서드(Static Factory Method)
란 클래스 내부에서 인스턴스를 반환하는 함수를 의미한다.// 예시
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
BigInteger(int, int, Random) // 생성자
BigInteger.probablePrime(); // 정적 팩토리 메서드
플라이웨이트 패턴
과 비슷한 기법이다.Boolean.valueOf(boolean) // 객체를 아예 생성하지 않음 (객체 참조)
플라이웨이트 패턴: 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화하는 소프트웨어 디자인 패턴
인터페이스 기반 프레임워크
를 만드는 핵심기술이다.// 구현체를 반환하지만 인터페이스만으로 다룸
List<Integer> list = Collections.unmodifiableList();
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
서비스 제공자 프레임워크
를 만드는 근간이 된다.서비스 제공자 프레임워크: 서비스 구현체(Provider)를 클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리
// 서비스 제공자 프레임워크 예시
// 인터페이스
public interface CarInterface {
}
public class Car {
// 서비스 접근 API
public List<CarInterface> getInstance() {
return new ArrayList<>();
}
}
public class client{
public static void main(String [] args) {
// 현재 CarInterface의 구현체가 없음에도 사용 가능함
List<CarInterface> list = Car.getInstance();
// 나중에 CarInterface의 구현체 CarImpl이 생기면 아래와 같이 사용 가능
CarInterface car = new CarImpl();
list.add(carImpl);
}
정적 팩터리 & 생성자
외에도 자바빈즈 패턴
, 빌더 패턴
이 있다.빌더 패턴
을 쓰는 것을 추천한다.점층적 생성자 패턴
으로 대응한다.점층적 생성자 패턴
: 매개변수를 1개 받는 생성자 ~ n개 받는 생성자까지 생성하는 패턴// 생성자 예시
public class NutritionFacts {
private final int servingSize;
private final int serving;
private final int calories;
...
public NutritionFacts(int servingSize) {
this(servingSize, 0)
}
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0)
}
...
}
자바빈즈 패턴
: 매개변수가 없는 생성자로 객체를 만든 후, 세터를 호출해 원하는 매개변수의 값을 설정하는 방식freezing
을 사용한다. freezing
: 생성이 끝난 객체를 수동으로 얼리고, 얼리기 전에는 사용할 수 없도록 한다. 빌더 패턴
은 점층적 생성자 패턴의 안정성과 자바 빈즈 패턴의 가독성을 겸비하였다.// 빌더 패턴 예시
public class NutritionFacts {
private final int servingSize;
private final int serving;
private final int calories;
...
public static class Builder {
private final int servingSize; // 필수 매개변수
private final int servings;
priavte int calories = 0; // 선택 매개변수
private int fat = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.serving = serving;
}
public Builder calories(int val) {
calories = val; return this;
}
...
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
}
}
}
플루언트 API
또는 메서드 연쇄
라고 한다.// 빌더 패턴 사용 예시
NutritionFacts cocaCola = new NutritionFact.Builder(240, 8)
.calories(100).fat(35).build();
// 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION }
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addToping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// 하위 클래스는 이 메서드를 재정의 하여 this를 반환함.
protected abstract T self();
}
}
// 뉴욕피자
public class NyPizza extends Pizza {
public enum Size {SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() {return this;}
}
}
장점
단점
싱글턴
: 인스턴스를 오직 하나만 생성할 수 있는 클래스public static 멤버가 final 필드인 방식
과 정적 팩터리 방식의 싱글턴
, 열거 타입 방식의 싱글턴
이다.public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ...}
}
Elvis.INSTANCE
를 초기화할 때 한 번만 호출된다.public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public static Elvis getInstance() {return INSTANCE;}
}
INSTANCE.getInstance()
는 항상 같은 객체의 참조를 반환하므로 제 2의 인스턴스는 만들어지지 않는다.private
생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다.@NoargsConstructor(AccessLevel.PRIVATE)
를 사용할 수도 있다.+@ ) 스프링에서 Class가 Private일 경우 객체를 어떻게 만들까?
// 맞춤법 검사기
private class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {}; // [아이템 4, 객체 생성 방지]
}
// 맞춤법 검사기
private class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker(...) {};
public static SpellChecker INSTANCE = new SpellChecker(...);
}
final
을 빼고 사전 교체 메서드를 만들 수도 있겠지만 어색하고 오류를 내기 쉬우며 멀티 스레드 환경에선 사용할 수 없다.의존 객체 주입
방식을 사용한다.의존 객체 주입
: 인스턴스를 생성할 때, 생성자에 필요한 자원을 넘겨주는 방식.// 맞춤법 검사기
private class SpellChecker {
private static final Lexicon dictionary;
private SpellChecker(Lexion dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
}