[Effective Java] 아이템 1 : 생성자 대신 정적 팩토리 메소드를 고려하라

2022. 3. 1. 00:50Java/Effective Java

728x90
반응형

 

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

 

클래스의 인스턴스를 얻는 전통 수단은 public 생성자이다.

하지만, 꼭 알아둬야 하는 기법 -> static factory method

public class item1 {

  private String name;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // static factory method
  public static item1 myName(String name) {
    return new item1(name);
  }

  public static void main(String[] args) {
    item1 my = myName("haessae0"); // static factory method
    item1 my2 = new item1("haessae0"); // public 생성자
  }
}

이렇게 클래스는 정적 팩토리 소드를 제공할 수 있다.

 

장점

- 이름을 가질 수 있다.

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

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

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

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

 

단점

- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

- 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.


장점

1. 이름을 가질 수 있다.

 

정적 팩토리 메소드는 이름만 잘 지으면 반환되는 객체의 특성을 명시적으로 표현할 수 있다.

public class item1 {

  private String name;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // static factory method
  public static item1 myName(String name) {
    return new item1(name);
  }

  public static void main(String[] args) {
    item1 my = myName("haessae0"); // static factory method
    item1 my2 = new item1("haessae0"); // public 생성자
  }
}

앞서 작성한 코드를 보자면

 

1.

public 생성자로 만든 것 -> 호출하였을 때, 'haessae0'라는 것이 어떤 인스턴스 변수인지 알기 어렵다. -> 클래스 이름인 'item1'에게 전달하여 생성하는 것이기 때문이다.

 

2. 

정적 팩토리 메소드로 만든 것 -> 호출하였을 때, 'haessae0'라는 것이 myName이라는 것을 알 수 있다. -> 내 이름이 haessae0라는 것을 알 수 있다.

 

당연히 알기 쉬운 정적 팩토리 메서드를 사용할 것이다.

 

또한, 

 

public 생성자는 하나의 시그니처로 하나만 생성할 수 있다.

public class item1 {

  private String name;
  private String birth;

  // public 생성자
  public item1(String name) {
    this.name = name;
  }

  // public 생성자 -> 불가
  public item1(String birth) {
    this.birth = birth;
  }

}

이미 name 변수를 받는 생성자가 존재 하기 때문에 birth를 받는 생성자는 생성할 경우 오류를 범하고 만다.

왜? 이미 하나의 변수를 받는 생성자가 존재하기 때문에 사용을 하여도 엉뚱한 메소드를 호출할 수 있다.

 

하지만! 정적 팩토리 메서드는 가능하게 해준다.

public class item1 {

  private String name;
  private String birth;

  // static factory method
  public static item1 myName(String name) {
    item1 my = new item1();
    my.name = name;
    return my;
  }

  // static factory method
  public static item1 myBirth(String birth) {
    item1 my = new item1();
    my.birth = birth;
    return my;
  }

}

이렇듯이 하나의 시그니처로 여러 가지를 구현할 수 있다.


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

 

불변 클래스는 인스턴스를 미리 만들거나 캐싱하여 재활용하는 방식이기에 불필요한 객체 생성을 막을 수 있다.

대표적으로 Boolean.valueOf(boolean) 메서드가 있다.

public final class Boolean implements java.io.Serializable,Comparable<Boolean> {
    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}

상수 객체로 선언해주기 때문에 새로운 객체를 매번 만들지 않는다.

 

-> 생성 비용이 큰 객체가 자주 불려지는 상황에 정적 팩토리 메서드를 사용하면 그 성능을 많이 이끌어 낼 수 있다.

 

또한, 반복되는 객체 요청을 언제 어느 순간에 인스턴스를 살고 죽게 할지 통제하는 인스턴스 통제 컨트롤이 있다.

 

왜? 사용할까?

- 인스턴스를 통제하면 싱글톤이나 인스턴스화 불가 상태로 만들 수 있다.

- 불변 값 클래스에서 동치인 인스턴스가 하나인 것을 보장 -> a == b일 때만, a.equals(b)가 성립된다.

- 플라이 웨이트 패턴의 근간이고, 열거 타입은 인스턴스가 하나만 만들어지는 것을 보장한다.

 

플라이웨이트 패턴은?

더보기

인스턴스를 하나만 가지고 여러 개의 가상 인스턴스를 제공하고 싶을 때 사용하는 패턴
즉, 인스턴스를 가능한 만큼 공유시켜 쓸데없이 new 연산자를 통한 메모리 낭비를 줄이는 방식입니다.


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

 

코드의 유연성을 제공해준다. -> 인터페이스를 정적 팩토리 메서드 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.

 

동반 클래스에서 주로 사용된다.

자바 1.8 이전 -> 인터페이스에 정적 메서드를 선언할 수 없었다. 따라서 인터페이스에 기능을 추가하기 위해서는 동반 클래스라는 것을 만들어 그 안에 정적 메서드를 추가했다.

-> 굳이 별도의 문서를 찾아가며 수현 클래스가 무엇인지 알아보지 않아도 된다.


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

 

반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 심지어 다른 클래스가 객체를 반환해도 된다.

예시로 EnumSet 클래스가 존재한다.

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);
    }

클래스에는 noneOf라는 메서드가 존재하는데 잘 보면 universe의 개수가 64개 이하면 RegularEnumset의 인스턴스를 반환하고, 65개 이상이면 JumboEnumset의 인스턴스를 반환한다.

 

두 타입 모두 알려지지 않기 때문에 클라이언트는 인지하지 않아도 되며 나중에 삭제하거나 새로운 타입을 만들어도 문제없이 사용할 수 있다. 그저 Enumset의 하위 클래스에만 존재하면 된다.


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

이러한 유연함은 서비스 제공자 프레임워크를 만드는 기반이 된다. -> 대표 JDBC

 

서비스 제공자 프레임워크의 구성요소

  • Service Interface : 구현체의 동작 정의
    • Connection
  • Provider Registration API : 제공자가 구현체를 등록할 때 사용
    • DriverManager.registerDriver
  • Service Access API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
    • DriverManager.getConnection
  • Service Provider Interface : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체
    • Driver

단점

- 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

- 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.

 

1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가

 

java.util.Collections로 만든 구현체는 상속할 수 없다.

 

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

 

Javadoc 문서에서 따로 정리하지 않는다. 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
instance & getInstance와 같지만, 매번 새로운 인스턴스
생성해 반환을 보장
Object newArray = 
Array.newInstance(classObject, arrayLen);
getType getInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
"Type"은 팩토리 메소드가 반환할 객체의 타입
FileStroe fs = Files.getFileStore(path);
newType newInstance와 같으나, 생성할 클래스가 아닌 다른 클래스에 팩토리 메소드를 정의할 때 쓴다.
"Type"은 팩토리 메소드가 반환할 객체의 타입
BufferedReader br =
Files.newBufferedReader(path);
type getType과 newType의 간결한 버전 List<Complaint> litany =
Collections.list(legacyLitany);

핵심

정적 팩토리 메서드와 생성자는 각자의 쓰임새가 있어 상대적인 장단점 이해 후 사용
그래도 정적 팩토리 메서드가 훨씬 좋다.
728x90
반응형