본문 바로가기
책/이펙티브 자바

생성자 대신 정적 팩토리 메서드를 고려하라

by jeounpar 2023. 7. 1.

정적 팩토리 메서드?

: 객체 생성의 역할을 하는 클래스 메서드

 

이펙티브 자바 Item-01에서는 생성자 대신 정적 팩토리 메서드를 고려하라고 한다.

생성자 대신 정적 팩토리 메서드를 사용하면 얻는 이점은 무엇일까?

코드 예시를 통해서 정적 팩토리 메서드를 알아보자.

 

특징

1. 이름을 가질 수 있다.

다음과 같은 상품(Item) 클래스와 생성자가 있다고 하면

public class Item01Data {
	private String itemName;
	private Integer price;
	private Long id;
    
    // id는 Repository가 부여함
    public Item01Data(String itemName, Integer price) {
		this.itemName = itemName;
		this.price = price;
	}
}

새로운 상품 인스턴스를 생성할 때 new를 사용하여 인스턴스를 생성할 것이다.

Item01Data itemA = new Item01Data("ItemA", 1000);

Item01Data itemB = new Item01Data("ItemB", 1000);

하지만 만약 아직 가격이 정해지지 않은 상품이면 생성자 인자로 0을 넣고 '가격이 0인 상품은 아직 가격이 정해지지 않음'이라는 약속을 했다고 했을 때, 이 약속을 모르는 개발자가 봤을 때는 '가격이 0인 상품은 공짜인가?' 라는 생각이 들 수 있고 또는 상품 인스턴스를 생성할 때 공짜인 상품은 가격을 0이 아닌 무엇으로 해야 할지 헷갈리는 상황이 만들어질 수 있다.

이때, 정적 팩토리 메서드를 사용하면 모호함을 해결 할 수 있다.

public static Item01Data createItem(String itemName, Integer price) {
	return new Item01Data(itemName, price);
}

public static Item01Data createPriceNullItem(String itemName) {
	return new Item01Data(itemName, null);
}

가격이 정해져 있는 상품 인스턴스를 생성할 때는 creatItem 메서드를 호출하고 가격이 아직 정해지지 않은 상품은 createPriceNullItem 메서드를 호출한다.

그리고 Item01Data의 생성자를 public에서 private으로 변경하여 다른 개발자가 무분별하게 상품 인스턴스를 생성하는 것을 방지한다.

 

2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

두 번째 장점은 디자인패턴 중 하나인 싱글톤 패턴과 같이 사용될 수 있다.

상품을 저장하는 Repository 클래스를 싱글톤 패턴을 사용하여 만들어보자.

public class Item01Repository {
	private static final Map<Long, Item01Data> store = new HashMap<>();
	private static long sequence = 0L;

	private static final Item01Repository instance = new Item01Repository();
	
	private Item01Repository(){}

	public static Item01Repository getInstance(){
		return instance;
	}

	public Item01Data save(Item01Data item) {
		item.setId(++sequence);
		store.put(item.getId(), item);
		return item;
	}

	public Item01Data findById(Long id) {
		return store.get(id);
	}
}

Item01Repository 클래스는 외부 클래스에서의 생성을 막기위해 생성자를 public이 아닌 private을 사용했다. 그리고 static을 사용하여 Item01Repository 인스턴스는 메모리에 올라갈때 한번만 초기화 되도록 했다.

상품을 저장하기 위해서 Item01Repository의 store변수에 접근하기 위해서는 getInstance() 정적 팩토리 메서드를 호출하여 사용해야한다.

public static void main(String[] args) {
	Item01Repository item01Repository = Item01Repository.getInstance();
	Item01Data itemA = Item01Data.createItem("itemA", 1000);
	item01Repository.save(itemA);
	Item01Data findItemA = item01Repository.findById(itemA.getId());
}

getInstance 메서드를 사용하여 Item01Repository 인스턴스를 새로 만들지 않고 기존에 생성된 것을 가져와서 상품을 저장하고 조회한다.

 

3. 매개변수에 따라 적합한 타입의 인스턴스를 반환할 수 있다.

Bronze, Silver, Gold 클래스가 PriceLevel 라는 상위 타입을 상속 받고 있다고 했을때 상품 가격에 따라 해당하는 클래스를 반환할 수 있다.

public class PriceLevel {
  public static PriceLevel of(Integer price) {
    if (price < 1000) {
      return new Bronze();
    } else if (price < 10000) {
      return new Silver();
    } else {
      return new Gold();
    }
  }
}