정적 팩토리 메서드 (Static Factory Method)

Welcome to Seoyun Dev Log·2022년 12월 8일
0

정적 팩토리 메서드

: 객체의 생성을 담당하는 클래스 메서드
: Gof 디자인 패턴 중 팩토리 패턴에서 유래

  • 객체를 생성하는 역할을 분리하겠다는 취지(객체 생성의 역할을 하는 클래스 메서드)

사실상 new를 직접적으로 사용하지 않을 뿐, 정적 팩토리 메서드라는 클래스 내에 선언되어있는 메서드를 내부의 new를 이용해 객체를 생성해서 반환하는 것
즉. 정적 팩토리 메소드를 통해서 new를 간접적으로 사용한다

생성자와 정적 팩토리 메서드 차이점

  • 생성자를 통해 객체를 생성하는 방식 (new)
  • 정적 팩토리 메서드로 객체를 생성하는 방식
    : 둘은 비슷해보이지만 다르다

정적 팩토리 메서드가 생성자 생성 방법과 다른 5가지
1. 이름을 가질 수 있다
2. 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다


장점

  1. 이름을 가질 수 있다
    : 객체 지향 프로그래밍에서는 객체마다 주어진 역할과 책임이 있다
    new를 통해 (생성자) 객체를 생성한다면 개발자는 해당 생성자의 내부 구조를 파악해야 목적에 맞게 객체를 생성할 수 있다
    반면에 정적 팩토리 메서드를 사용하면 메서드 네이밍을 따라 반환될 객체의 특성을 묘사할 수 있다
    따라서 코드 가독성과 생산성을 높여준다
  • 예제) 로또 자동, 수동 : 생성의 목적을 이름을 통해 표현할 수 있어 가독성이 좋아진다
public class LottoFactory() {
  private static final int LOTTO_SIZE = 6;

  private static List<LottoNumber> allLottoNumbers = ...; // 1~45까지의 로또 넘버

  public static Lotto createAutoLotto() {
    Collections.shuffle(allLottoNumbers);
    return new Lotto(allLottoNumbers.stream()
            .limit(LOTTO_SIZE)
            .collect(Collectors.toList()));
  }

  public static Lotto createManualLotto(List<LottoNumber> lottoNumbers) {
    return new Lotto(lottoNumbers);
  }
  ...
}
  1. 호출할 때 마다 새로운 객체를 생성할 필요가 없다
    대표적인 예가 enum 타입이다
    사용되는 값의 개수가 정해져 있으면 해당 값을 미리 생성해 놓고 조회할 수 있는 구조로 만들 수 있다
    정적 팩토리 메서드와 캐싱 구조를 함께 사용하면 객체를 매번 새롭게 만들어 줄 필요가 없다

  2. 하위 자료형 객체를 반환할 수 있다
    : 상속을 사용할 때 확인 가능
    정적 팩토리 메서드가 반환할 때 상황에 따라 하위 클래스 타입의 객체 반환 가능

예제)

public class Grade {
...
    private static Grade of(int takenSemester) {
        if (0 < takenSemester && takenSemester <= 2) {
            return new Freshman();
        }
        if (2 < takenSemester && takenSemester <= 4) {
            return new Sophomore();
        }
        if (4 < takenSemester && takenSemester <= 6) {
            return new Junior();
        }
        if (6 < takenSemester &&<takenSemester <= 8){
            return new Senior();
        }
        ...
    }
...

of () 라는 정적 팩토리 메서드를 사용하여 객체 생성 시, 수강 학기에 따라 해당하는 학년 객체를 반환
, 분기처리를 통해 하위 타입의 객체를 반환할 수 있다는 것을 알 수 있다.

  • 예제
    1부터 45까지의 로또 번호를 enum으로도 만들 수 있지만 LottoNumber 클래스 안에서 반복문을 통해 쉽게 45개의 인스턴스를 만들 수 있으므로 후자의 방법을 사용했다.
public class LottoNumber {
  private static final int MIN_LOTTO_NUMBER = 1;
  private static final int MAX_LOTTO_NUMBER = 45;

  private static Map<Integer, LottoNumber> lottoNumberCache = new HashMap<>();

  static {
    IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
                .forEach(i -> lottoNumberCache.put(i, new LottoNumber(i)));
  }

  private int number;

  private LottoNumber(int number) {
    this.number = number;
  }

  public LottoNumber of(int number) {  // LottoNumber를 반환하는 정적 팩토리 메서드
    return lottoNumberCache.get(number);
  }

  ...
}

👉 미리 생성된 로또 번호 객체의 캐싱을 통해서 새로운 객체 생성의 부담을 덜 수 있다는 장점도 있지만, 생성자의 접근 제한자를 private으로 설정함으로써 객체 생성을 정적 팩토리 메서드로만 가능하도록 제한할 수 있다는 것
이를 통해 정해진 범위를 벗어나는 로또 번호의 생성을 막을 수 있다는 장점을 확보

  1. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다
    : 유연성이 높아진다는 것

반환 타입의 하위 타입이기만하면 어느 타입이든 객체를 반환해도 상관없다는 것
만약 특정 인터페이스를 상속받은 구현체들이 있을 때 객체 생성시 상황에 따라 유동적으로 해당하는 구현체 타입으로 반환이 가능하다고 생각하면 된다

EnumSet의 정적 팩토리 메서드로 파라미터로 받은 값의 길이에 따라 RegularEnumSet()이나 JumboEnumSet을 반환(입력 매개변수에 따라 다른 클래스의 객체를 반환)

  1. 객체 생성을 캡슐화 할 수 있다
    : 객체 생성을 캡슐화하는 방법
  • 캡슐화(Encapsulization)란?
    데이터의 은닉을 말한다. 여기서는 생성자를 클래스의 메서드 안으로 숨기면서 내부 상태를 외부에 드러낼 필요없이 객체 생성 인터페이스 단순화 시킬 수 있다.

웹 어플리케이션을 개발하다보면 계층간의 데이터를 전송하기 위한 객체로 DTO(data transfer object)를 정의해서 사용한다

DTO와 Entity간에는 자유롭게 형변환이 가능해야하는데 정적 팩토리 메서드를 사용하면
내부 구현을 모르더라도 쉽게 변환할 수 있다

public class CarDto {
  private String name;
  private int position;

  pulbic static CarDto from(Car car) {
    return new CarDto(car.getName(), car.getPosition());
  }
}


// Car -> CatDto 로 변환
CarDto carDto = CarDto.from(car);

👉 만약 정적 팩토리 메서드를 쓰기 않고 DTO로 변환하면,
외부에서 생성자의 내부 구현을 모두 드러낸 채 해야한다

Car carDto = CarDto.from(car); // 정적 팩토리 메서드를 쓴 경우
CarDto carDto = new CarDto(car.getName(), car.getPosition); // 생성자를 쓴 경우

정적 팩토리 메서드는 단순히 생성자의 역할을 대신하는 것 뿐만 아니라
우리가 좀 더 가독성 좋은 코드를 작성하고 객체지향적으로 프로그래밍을 할 수 있도록 도와준다
⭐️ 도메인에서 객체 생성의 역할 자체가 중요한 경우에는 따로 클래스를 분리하는 것이 좋다
factory.class

정적 팩토리 메서드의 단점

  1. 상속에는 pulic 혹은 protected 생성자가 필요하므로 정적 팩토리 메서드만 제공할 경우, 상속이 불가능하다
  2. 정적 팩토리 메서드를 다른 개발자들이 찾기 어렵다.
    개발자가 임의로 만든 정적 팩토리 메서드 특성 상, 다른 개발자들이 사용시에 정퍽 팩토리 메서드를 찾기가 어렵다고 생각 할 수 있다.

하지만 이는 암묵적으로 사용하는 정적 팩터리 메서드 컨벤션과, API 문서 작성을 잘 하면 해결 할 수 있다고 생각한다.


⭐️정적 팩토리 메서드 명명방식

  • from : 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드
  • of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
  • valueOf : from과 Of의 더 자세한 버전
  • instance 혹은 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장 하지 않음
  • create 혹은 newInstance :instance 혹은 getInstance 와 같으나 매번 새로운 인스턴스를 생성해 반환 함을 보장.

profile
하루 일지 보단 행동 고찰 과정에 대한 개발 블로그

0개의 댓글