Skip to content

Latest commit

 

History

History
173 lines (98 loc) · 6.92 KB

File metadata and controls

173 lines (98 loc) · 6.92 KB

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

작성자: 박성민

작성일: 2022년 4월 9일


public class Foo {
    private String name;
    
    public Foo(String name) {
        this.name = name;
    }
    
    public static Foo withName(String name) {
        return new Foo(name);
    }
    
    public static void main(String[] args) {
        Foo foo1 = new Foo("tom");
        Foo foo2 = Foo.withName("tom");
    }
}

foo1 : 생성자 사용해서 인스턴스를 얻은 것.

foo2 : 정적 팩터리 메서드를 사용해서 인스턴스를 얻은 것.

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

  • 생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 쉽게 알 수 없다.

  • 잘 만든 이름을 가진 정적 팩토리를 사용하는 것이 객체의 특성을 쉽게 알 수 있고, 읽기 더 편하다.

  • 생성자는 시그니처에 제약이 있다. (파라미터가 같은 똑같은 생성자 두 개를 만들 수 없으니)

    • public Foo(String name) {
          this.name = name;
      }
      public Foo(String address) {
          this.address = address;
      }
  • 정적 팩토리 메서드에는 이런 제약이 없다.

    • public static Foo withName(String name) {
          Foo foo = new Foo();
          foo.setName(name);
          return foo;
      }
      
      public static Foo withAddress(String address) {
          Foo foo = new Foo();
          foo.setAddress(address);
          return foo;
      }

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

private static final Foo tom = new Foo();

public static Foo getFoo() {
    return tom;
}
  • 불변 클래스(immutable class; 아이템17)는 인스턴스를 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.

  • 따라서 객체 생성 비용이 큰 객체가 자주 요청되는 상황이라면 성능을 상당히 끌어올릴 수 있다.

  • 정적 팩토리 방식의 클래스는 언제 어느 인스턴스를 살아있게 할지를 철저히 통제할 수 있다. 이런 클래스를 인스턴스 통제(instance-controlled)클래스라 한다.

    • 인스턴스를 통제하면 클래스를 싱글턴(아이템 3)으로 만들 수도, 인스턴스화 불가(아이템 4)로 만들수도 있다.
    • 또한 불변 값 클래스(아이템 17)에서 동치인 인스턴스가 단 하나임을 보장할 수 있다. (a==b일 때만 a.equals(b)가 성립) (값이 같은 다른 인스턴스가 없다는 말)
    • 인스턴스 통제는 플라이웨이트 패턴의 근간이 되며, 열거 타입(아이템 34)은 인스턴스 하나만 만들어짐을 보장함.

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

  • 반환할 객체의 클래스를 선택할 수 있게 하는 '유연함'이 있다.

    • 실제 리턴하는 객체는 인터페이스의 구현체만 리턴한다. 이렇게 하면 클라이언트의 입장에서는 실제 구현체는 모르고 인터페이스만 가지고 코딩을 하게된다.
    • java.util.Collections가 그 예에 해당.
    • 인터페이스 기반 프레임워크(아이템 20)를 만드는 핵심 기술
  • 컬렉션 프레임워크는 45개의 구현체를 제공하는데, 이 45개 클래스를 공개하지 않다.

    • 제공해야 할 API가 줄었다.
    • 개념적인 무게, 즉 프로그래머가 API를 사용하기 위해 익혀야 하는 개념의 수와 낮이도도 낮춘다.
  • 자바8부터 인터페이스에 정적 메서드를 선언할 수 있기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 없다. 그냥 인터페이스에 두면 된다.

    • 예를 들어 Collections 라는 구현 클래스를 따로 정의 할 필요가 없다. 그냥 Collection 인터페이스에 정적 메서드를 선언하면 되니까.
    • Collections에 있는 sort, binarySearch같은 정적 메서드를 그냥 Collection 인터페이스에 정의하면 됨.
  • 자바9부터 private 정적메서드까지 허락하지만, 정적 필드와 정적 멤버 클래스는 여전히 public이어야 한다.

    • static 메서드가 필요한 이유 ? 외부에 노출할 필요가 없고, 현재 있는 클래스에서만 사용하기 때문에.
    • private static 메서드도 위와 같다. public static 메서드에서는 static 메서드만 사용 가능하기 때문.

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

  • 반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다.
public static Foo getFoo(boolean flag) {
    return flag ? new Foo() : new BarFoo();
}
static class BarFoo extends Foo {
}
  • EnumSet클래스는 생성자 없이 정적 팩터리만 제공한다.

    • 원소의 개수에 따라 RegularEnumSet의 인스턴스 또는 JumboEnumSet의 인스턴스를 반환하는데 사용자는 이걸 알 필요가 없다.
    • 사용자가 알 필요가 없기에 (RegularEnumSet < 이런 클래스의)추가/삭제가 용이하다.
    • 현재 클래스만 반환할 수 있다면 저런 클래스들을 사용자가 다 알아야 한다.

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

  • 이런 유연함은 서비스 제공자 프레임워크를 만드는 근간이 된다.

  • 예를 들어 JDBC가 있다. (JDBC가 ServiceLoader를 쓰진 않는다.)

    • JDBC의 getConnection이라는 메서드를 썼을 때 실제 리턴되어 나오는 Connection 객체는 DB(드라이버)마다 다르다.
    • java.util.ServiceLoader는 범용 서비스 제공자 프레임워크.
  • 정적 팩토리 메서드 안에서 반환할 객체가 정해지는 것이 아니라 외부에서 주입? 해주는 개념이다. 외부 파일이 바뀌면 반환되는 객체도 바뀐다.

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

  • 이 제약은 상속보다 컴포지션을 사용(아이템 18)하도록 유도하고 불변 타입(아이템 17)으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점일수도?

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

  • 생성자는 Javadoc (/** */) 상단에 모아서 보여주지만 정적 팩터리 메서드는 API 설명에 명확이 드러나지 않는다. 클래스 상단에 주석으로 설명을 달아주자.

핵심정리

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