자바 API에서 LocalDate 객체를 살펴보면 객체를 생성할 때 static 메서드를 통해 얻도록 되어 있다. 예를 들어 now()를 통해 현재 날짜를 얻을 수 있고, 또는 of()를 통해 원하는 일자를 얻을 수 있다. 이러한 몇몇 클래스들의 구조를 살펴보면 대부분 생성자를 private으로 막고 static 메서드를 통해 생성하도록 구현되어 있다. 이러한 방식을 정적 팩토리 메서드라 부른다.

Modifier and Type Method
static LocalDate from(TemporalAccessor temporal)
static LocalDate now()
static LocalDate now(Clock clock)
static LocalDate now(ZoneId zone)
static LocalDate of(int year, int month, int dayOfMonth)
static LocalDate of(int year, Month month, int dayOfMonth)
static LocalDate ofEpochDay(long epochDay)
static LocalDate ofYearDay(int year, int dayOfYear)
static LocalDate parse(CharSequence text)
static LocalDate parse(CharSequence text, DateTimeFormatter formatter)

왜 생성자 대신 이러한 정적 팩토리 메서드를 사용하는가? 정적 팩토리 메서드로 생성자를 대체할 때 얻을 수 있는 이점이 존재한다.

  1. 생성자의 파라미터가 많은 경우 순서를 알기 어렵다.

실은 IDE의 단축키가 너무 잘 되어 있어 그리 크게 느껴지는 불편함은 아니지만, 만약 String, String처럼 연속되는 타입의 파라미터를 삽입할 때 순서를 바꿔 넣는 등 실수할 가능성이 여전히 있다.

  1. 메서드의 이름으로 생성 목적을 명확하게 전달할 수 있다.

나의 경우 페이징 객체를 직접 만들어 사용하는 데 활용했다. 아래 withOrderOf의 파라미터가 많아 깔끔한 예시는 아니지만, 이렇게 하면 순서가 있는 페이징 객체를 생성하는지, 또는 기본값을 사용하는 페이징 객체를 생성하는지 함수명으로 알 수 있다.

// 매개변수 하나라면 from 네이밍, 여러개라면 of 네이밍
// withOrderOF : 요청으로 온 orderBy와 orderDirection을 사용
// of : 기본값으로 가지는 orderBy와 orderDirection을 사용
return PagingRequest.withOrderOf(requestPage, offset, limit, orderBy, orderDirection, keyword);
return PagingRequest.of(requestPage, offset, limit, keyword);
  1. 캐싱 등 여러 분기를 처리할 수 있다.

Integer.valueOf()는 내부적으로 캐싱으로 동작한다. valueOf() 또한 정적 팩토리 메서드의 네이밍 규약 중 하나이며 메서드로 작성하는 만큼 여러 분기를 처리할 수 있다. Integer.valueOf()와 IntegerCache

또한 singleton 패턴의 예제에 존재하는 getInstance() 또한 정적 팩토리 메서드 네이밍 규약 일부다. Singleton 패턴, DCLP

내가 개발 과정에서 체감한 장점은 위의 세 가지이고, 더 깊은 내용은 최하단의 블로그를 참조하자. 더불어 “하위 타입의 구현체를 생성할 수 있다”, “캡슐화한다”는 장점도 있었는데, 정적 팩토리 메서드 개념에서 이하의 내용부터는 바로 이해할 실력이 못 되어 gpt를 통해 쉬운 예제를 뽑아 공부했다.

  1. 하위 타입의 구현체를 생성할 수 있다.
public enum Vendor { KAKAO, TOSS, NAVER, DUMMY }

public **interface PaymentClient** {
    void pay(int amount);
}

public class KakaoPaymentClient **implements PaymentClient** {

    @Override
    public void pay(int amount) {
        System.out.println("[KAKAO] 결제 성공: " + amount + "원");
    }
}

public class TossPaymentClient **implements PaymentClient** {

    @Override
    public void pay(int amount) {
        System.out.println("[TOSS] 결제 성공: " + amount + "원");
    }
} // 이하 NaverPaymentClient, DummyPaymentClient 동일