보통 클래스의 인스턴스는 public 생성자를 활용하여 생성한다. 그런데 클래스 자체는 생성자와 별도로 아래와 같은 정적 팩토리 메소드를 제공할 수 있다.
public static int testMethod() {
return 0;
}
위에서 설명한 바와 같이 클래스에서는 클라이언트에게 public 생성자를 제공해주지만 이 뿐만 아니라 정적 팩토리 메소드도 제공해줄 수 있다.
//public 생성자
public class MyBook {
public MyBook() {}
}
//static factory method
public class MyBook {
private MyBook() {}
public static MyBook getInstance() {
return new MyBook();
}
}
그럼 public 생성자와 static factory method를 사용했을 때의 장점과 단점을 살펴보자.
정적 팩토리 메소드(static factory method)가 생성자보다 좋은 이유
장점 1. 이름을 가질 수 있다.
public 생성자의 경우 생성자가 가지는 파라미터와 반환될 생성자 객체의 특성을 제대로 알 수가 없다. 즉, 반대로 말하면 생성자가 가지는 파라미터와 반환될 생성자의 특성을 제대로 알아야만 해당 생성자를 사용할 수 있다는 것이다.
반면, 정적 팩토리 메소드의 경우 메소드의 네이밍만 잘 짓는다면 어떤 객체가 반환되고 해당 메소드가 어떤 행동을 가지고 있는지 쉽게 파악이 가능할 것이다.
예를 들어, 생성자인 Result(int, int)과 정적 팩토리 메소드 Result.sum 중 어느 쪽이 '덧셈을 위한 값을 반환한다'라는 의미를 더 잘 갖고 있는 지 알 수 있을 것이다.
public Result(int a, int b) {}
public static Result sum(int a, int b) {
return new Result(a, b);
}
또한, 한 클래스의 시그니처(메소드 명, 메소드 파라미터가 같은 것)가 같은 생성자가 여러 개 필요할 시에는 생성자를 오버로딩하여 구현하는 것이 아니라, 정적 팩토리 메소드를 활용하여 구현하는 것이 구분하기 더욱 편한 것이다.
장점 2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
불변 클래스(객체가 가지는 값마다 새로운 인스턴스가 필요) 같은 경우 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하고 있다.
//1) 미리 만들어 놓은 인스턴스 활용
Boolean flg = new Boolean(true); //(x)
Boolean flg = Boolean.valueOf(true); //(o) 미리 만들어 놓은 인스턴스를 반환
//2) 생성한 인스턴스 재활용
String str = "test"; //1. "test" 인스턴스를 생성
String str2 = "test"; //2. 생성된 "test" 인스턴스를 재활용
이로 인해서 생성 비용이 크고 반복적으로 호출되는 객체의 경우 메모리 성능을 끌어올려줄 수 있다.
이와 비슷한 기법으로 플라이웨이트 패턴이라는 것이 있다.
플라이웨이트 패턴에 대해서 간략히 설명하면, String Constant Pool 처럼 클라이언트의 요청에 의해 객체를 생성할 때 생성하려는 객체가 Pool에 존재하면 반환만 해주고, 존재하지 않으면 생성 후 반환해주는 패턴 방식이다.
이런 방식들을 다시 말해 인스턴스 통제 방식이라고 할 수 있다. 인스턴스 통제를 하는 이유는 클래스를 싱글톤 으로 만들 수 있고, 인스턴스화 불가 로 만들 수도 있으며, 불변 클래스에서 동치인 인스턴스가 단 하나 뿐임을 보장(a == b일 때만 a.equals(b)가 성립) 할 수 있다.
열거 타입(Enum)의 경우 인스턴스가 하나만 만들어짐을 보장한다.
장점 3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
이 능력에서 가장 중요한 것은 반환할 객체의 클래스를 자유롭게 선택할 수 있다는 것이다.
public class MyBook{
public MyBook(){}
public static MyBook getChildInstance() {
return MyBookChild.getInstance();
}
}
public class MyBookChild extends MyBook{
private MyBookChild(){}
public static MyBookChild getInstance() {
return new MyBookChild();
}
}
소스에서 보이는 것처럼 반환할 객체의 타입을 자식의 타입으로 반환하듯 자유롭게 선택할 수 있는 것이다.
이런 방법으로 인해, API를 만들 때 이런 유연성을 응용하면 구현 클래스(MyBookChild)를 공개하지 않고도 MyBook을 통해 구현 클래스를 반환할 수 있어 API와 소통이 가능하다.
장점 4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관이 없다는 것이다.
예를 들어, EnumSet 클래스는 public 생성자 없이 오직 정적 팩토리 메소드만 제공하는데, 원소의 수에 따라 두 가지 하위 클래스 중 하나의 인스턴스를 반환한다.
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);
}
소스에서 보는 바와 같이 원소의 개수가 64개 이하면 RegularEnumSet의 인스턴스를 반환하고, 원소의 개수가 65개 이상이면 JumboEnumSet 인스턴스를 반환하고 있다.
클라이언트는 하위 클래스의 존재를 알 필요 없이 그저 반환해주는 클래스에게 메시지를 전송해 클래스의 반환 값만 받아 클라이언트가 설계한 로직으로 구성만 하면 되는 것이다.
장점 5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
인터페이스나 클래스의 정적 팩터리 메서드가 만들어지는 시점에서 반환 탸입의 클래스가 존재하지 않아도 된다는 것이다.
public class MyBook{
public static List<MyBookInterface> getChildInstance() {
new ArrayList<>();
}
}
public interface MyBookInterface{
//구현체는 아직 구현되지 않았다.
}
//1. 추 후 구현 클래스 생성
public class MyBookImpl implements MyBookInterface {}
//2. 클라이언트에서 활용
public class client{
public static void main(String [] args) {
List<MyBookInterface> myBookImpls = MyBook.getChildInstance();
//추 후에 구현한 클래스를 생성 후 List에 추가
MyBookInterface myBookImpl = new MyBookImpl();
myBookImpls.add(myBookImpl);
}
}
MyBook의 정적 팩터리 메서드의 반환할 객체의 클래스는 MyBookInterface의 구현 클래스인데 아직 구현되지 않은 것을 볼 수 있다. 즉, 추후의 정적 팩터리 메서드의 변경 없이 List에 구현된 클래스를 add하여 사용하면 되는 것이다.
단점 1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.
상속을 하기 위해서는 생성자가 필요없더라도 필수적으로 필요하다.
이 제약으로 인해 상속보다 컴포지션(합성) 사용을 유도할 수 있고 불변 타입으로 만들기 위해 해당 제약을 지켜야 한다는 점에서는 장점으로 받아들일 수 있다.
단점 2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.
생성자 처럼 API 설명에 명확히 드러나있지 않으므로 프로그래머는 정적 팩터리 메서드를 활용하여 클래스를 인스턴스화 할 방법을 알아내야 한다.
정리
public 생성자을 사용할 때와 정적 팩터리 메서드를 사용할 때 모두 장단점이 존재하지만, 일반적으로 정적 팩터리 메서드 형식으로 사용하는 것이 장점이 더 많다. 실무에서 메서드 이름과 선택적인 인스턴스화는 매우 큰 장점을 가지고 있기 때문이다.
그러므로 필요에 따라 public 생성자 보다는 정적 팩터리 메서드를 사용하는 방법을 익혀두는 것도 실무에서 큰 도움이 될 것으로 생각된다.
Ref
![]() |
|
Code Link
https://github.com/FourTeamProject/Charmander_EffectiveJava/tree/master/FourTeamEffectiveJavaStudy
FourTeamProject/Charmander_EffectiveJava
이펙티브 자바 스터디 입니다. Contribute to FourTeamProject/Charmander_EffectiveJava development by creating an account on GitHub.
github.com
'자바' 카테고리의 다른 글
(이펙티브 자바) 아이템 06. 불필요한 객체 생성을 피하라 (0) | 2020.07.27 |
---|---|
(이펙티브 자바) 아이템 08. finalizer와 cleaner 사용을 피하라 (0) | 2020.07.26 |
(이펙티브 자바) 아이템 02. 생성자에 매개변수가 많다면 빌더를 고려하라 (3) | 2020.07.21 |
(이펙티브 자바) 아이템 03. private 생성자나 열거타입으로 싱글턴임을 보증하라 (0) | 2020.07.20 |
(이펙티브 자바) 아이템 04. 인스턴스화를 막으려거든 private 생성자를 사용하라 (1) | 2020.07.19 |