아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

Bobby·2022년 5월 9일
0

이펙티브 자바

목록 보기
1/7
post-thumbnail

Q. 클래스의 인스턴스를 어떻게 얻을 수 있나요?
A. 클래스의 public 생성자를 통해 생성합니다!!

보통의 경우 생성자를 이용해 인스턴스를 생성해서 사용한다.

Item item = new Item();

또 다른 방법으로 생성자 대신 정적 팩터리 메서드(static factory method)를 사용하여 인스턴스를 제공할 수 있다.

private Item() {};  // 생성자를 통한 인스턴스 생성은 막아 주는 것이 좋다.

public static Item newInstance() {
	return new Item();
}

이렇게 생성자 말고 정적 메소드를 사용하여 인스턴스를 생성 했을 때 어떤 이점이 있을까?


장점 1. 이름을 가질 수 있다.

  • 생성자로 인스턴스를 생성할 경우에는 이름을 정할 수 없다.
  • 클래스의 이름과 생성자에 넘기는 매개변수 만으로는 반환되는 객체의 특성을 파악하기 힘들다.
public class Item {
	private String name;
    private boolean important;

	public Item(String name, boolean important) {
		this.name = name;
        this.important = important
    }
}
  • 정적 메소드로 인스턴스를 생성할 경우 메소드 이름을 정할 수 있다.

public class Item {
	private String name;
    private boolean important;

	public static Item normalItem(String name) {
		Item item = new Item()
        item.name = name;
        item.important = false;
        return item;
	}

	public static Item importantItem(String name) {
		Item item = new Item()
        item.name = name;
        item.important = true;
        return item;
	}
}
  • 클라이언트에서 사용할 경우 생성자 보다 특성 파악이 쉽다.
Item item = new Item("name", true); // 생성자

Item item = Item.importantItem("name"); // 정적 메소드

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

  • 생성자를 사용하여 인스턴스를 만들면 항상 새로운 인스턴스를 반환한다.
  • 정적 메소드를 사용하면 새로운 인스턴스가 필요하다면 새로 생성할 수도 있고 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수도 있다.(인스턴스 통제 클래스)
  • 인스턴스를 통제하면 클래스를 싱글톤, 인스턴스화 불가로 만들 수도 있다.

public class Item {
	private static final Item INSTANCE = new Item(); // 미리 생성
    
    public class Item newInstance() {
    	return new Item(); // 새로 생성
    }
    
    public class Item getInstance() {
    	return INSTANCE; // 미리 생성한 인스턴스 반환
    }
}

장점 3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

  • 반환할 객체의 클래스를 자유롭게 선택할 수 있는 유연성이 생긴다.
  • API를 만들 때 구현 클래스를 공개하지 않고 그 객체를 반환 할 수 있다.

public interface Item {
    
    // 하위 클래스를 반환 할 수 있다.
    static Item newInstance() {
    	return new ImportantItem();
    }

}

public class ImportantItem implements Item {
	...
}

장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

  • 매개변수에 따라서 여러 하위 타입 객체 중 선택하여 반환 할 수 있다.
  • 어떤 구현 객체가 반환 되었는지 알 필요 없이 상위타입의 객체의 메소드를 사용하여 실행 할 수있다.

public interface Item {

	String type();
    
    // 매개 변수에 따라 하위 타입 객체를 선택하여 반환할 수 있다.
    static Item of(boolean important) {
    	return important ? new ImportantItem() : new NormalItem();
    }

}

public class ImportantItem implements Item {

	@Override
    public String type() {
    	return "important";
    }
}

public class NormalItem implements Item {
	
    @Override
    public String type() {
    	return "normal";
    }
}

장점 5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.


public interface Item {

	String type();
    
    static Item getInstance() {
        // Item 타입의 클래스를 불러온다. ImportantItem, NormalItem 클래스가 로드 된다.
        ServiceLoader<Item> serviceLoader = ServiceLoader.load(Item.class);

        // 해당 타입이 없을 수도 있기 때문에 Optional 이다.
        Optional<Item> item = serviceLoader.findFirst(); 
    
    	return item.get();
    }
}

public class ImportantItem implements Item {

	@Override
    public String type() {
    	return "important";
    }
}

public class NormalItem implements Item {
	
    @Override
    public String type() {
    	return "normal";
    }
}
  • 정적 팩토리 메소드에는 구현 클래스에 의존하지 않고 상위 인터페이스만 의존하고 있다.
  • 구현 클래스가 외부 라이브러리에서 가져온다고 가정했을 때 외부 라이브러리를 바꿔도 클라이언트 코드의 수정은 일어나지 않는다.

단점 1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

단점 2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

  • 생성자 처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화 할 방법을 알아내야 한다.
  • 알려진 규약을 따라 짓는 식으로 문제를 완화 할 수 있다.

< 정적 팩터리 메서드에 흔히 사용하는 명명 방식들 >

  • from: 매개변수를 하나 받아서 해당 타입의 인스턴스를 반환하는 형변환 메서드
    Date d = Date.from(instant);
  • of: 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
	Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf: from과 of의 더 자세한 버전
	BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance 혹은 getInstance: (매개변수를 받는다면) 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지는 않는다.
	StackWalker luke = StackWalker.getInstance(options);
  • create 혹은 newInstance: 매번 새로운 인스턴스를 생성해 반환함을 보장한다.
	Object newArray = Array.newInstance(classObject, arrayLen);
  • getType: getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다.
	FileStore fs = Files.getFileStore(path);
  • newType: newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩터리 메서드를 정의할 때 쓴다. "Type"은 팩터리 메서드가 반환할 객체의 타입이다.
	BufferedReader br = Files.newBufferedReader(path);
  • type: getType, newType 의 간결한 버전
	List<Complaint> litany = Collections.list(legacyLitany);

핵심정리

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 고려하는 것이 좋다.

profile
물흐르듯 개발하다 대박나기

0개의 댓글