Backend 취업준비/Java

정적 팩토리 메서드

aammddkkzxc 2024. 2. 22. 22:42
  • List.of(),  Optional.of() 등의 메서드를 써보다가 of()라는 비슷한 이름을 가진 메서드가 있다는 것을 알게 되었다
  • 이 것들은 정적 팩토리 메서드 라고 칭하는 것 들 이었다.
  • 생성자에 private 접근제한을 두어 외부에서 new키워드를 이용하여 객체 생성을 막는 조치가 되어 있다.

 

정적 팩토리 메서드의 특징

 

1. 생성 목적에 대한 이름 표현이 가능하다

  • 클래스를 설계할때 다양한 타입의 객체를 생성하기 위해, 목적에 따라 생성자 오버로딩을 하였다
  • 오버로딩 된 생성자와 new키워드를 통해 객체를 생성하려면 개발자는 생성자의 인자 순서와 내부 구조를 알고 있어야 한다
  • 생성자로 넘기는 매개변수 만으로는 반환될 객체의 특성을 제대로 표현하기 어렵다(생성자 이름이 클래스 이름으로 고정되어 있어야 하는 규칙)
  • 적절한 네이밍으로 객체의 특성을 유추 할 수 있게 된다

2. 인스턴스에 대하여 관리하기 용이하다

  • 메서드를 통하여 한단계 거쳐 객체를 생성하기 때문에 기본적으로 객체 생성 및 통제 관리를 할 수 있게 된다
  • 필요에 따라 새로운 객체를 생성시키거나, 객체를 하나만 만들어 두고 이를 공유하여 불필요한 객체 생성을 막을 수도 있다.
    • 싱글톤 디자인 패턴 => getInstance() 정적 팩토리 메서드로 오로지 하나의 객체만 반환하도록 한다
    • 캐싱 구조 => 정적 초기화 블록 과 정적 팩토리 메서드로 캐싱 구조를 만들 수 있다. 자 주 사용되는 요소의 개수가 정해져있다면 해당 개수만큼 미리 생성해놓고 조회(캐싱)할 수 있는 구조

캐싱 예시1

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

  ...
}

 

캐싱 예시 2

class Day {
    private String day;

    public Day(String day) { this.day = day; }

    public String getDay() { return day; }
}

// Day 객체를 생성하고 관리하는 Flyweight 팩토리 클래스
class DayFactory {

	// Day 객체를 저장하는 캐싱 저장소 역할
    private static final Map<String, Day> cache = new HashMap<>();
	
    // 자주 사용될것 같은 Day 객체 몇가지를 미리 등록한다
    static { 
    	cache.put("Monday", new Day("Monday")); 
        cache.put("Tuesday", new Day("Tuesday")); 
        cache.put("Wednesday", new Day("Wednesday")); 
    }

    // 정적 팩토리 메서드 (인스턴스에 대해 철저한 관리)
    public static Day from(String day) {

        if(cache.containsKey(day)) {
            // 캐시 되어있으면 그대로 가져와 반환
            System.out.println("해당 요일은 캐싱되어 있습니다.");
            return cache.get(day);
        } else {
            // 캐시 되어 있지 않으면 새로 생성하고 캐싱하고 반환
            System.out.println("해당 요일은 캐싱되어 있지 않아 새로 생성하였습니다.");
            Day d = new Day(day);
            cache.put(day, d);
            return d;
        }
    }
}

 

 

3. 하위 자료형 객체를 반환할 수 있다.

 

  • Basic, Intermediate, Advanced 클래스가 Level라는 상위 타입을 상속받고 있는 구조.
  • 시험 점수에 따라 결정되는하위 등급 타입을 반환하는 정적 팩토리 메서드를 만들면, 다음과 같이 분기처리를 통해 하위 타입의 객체를 반환할 수 있다.
public class Level {
  ...
  public static Level of(int score) {
    if (score < 50) {
      return new Basic();
    } else if (score < 80) {
      return new Intermediate();
    } else {
      return new Advanced();
    }
  }
  ...
}

 

 

4. 객체 생성을 캡슐화 한다

 

  • .웹 어플리케이션을 개발하다보면 계층 간에 데이터를 전송하기 위한 객체로 DTO(Data transfer object)를 정의해서 사용한다.
  • DTO와 Entity간에는 자유롭게 형 변환이 가능해야 하는데, 정적 팩터리 메서드를 사용하면 내부 구현을 모르더라도 쉽게 변환할 수 있다.
  • 만약 정적 팩토리 메서드를 쓰지 않고 DTO로 변환한다면 외부에 생성자의 내부 구현이 모두 드러난다
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);

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




정적 팩토리 메서드 네이밍 컨벤션

 

정적 팩토리 메서드에서의 네이밍은 단순히 선호도 의미를 넘어서 거의 법칙 정도로 사용되고 있다고 하니 꼭 익히자

 

  • from : 하나의 매개 변수를 받아서 객체를 생성
  • of : 여러개의 매개 변수를 받아서 객체를 생성
  • getInstance | instance : 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • newInstance | create : 새로운 인스턴스를 생성
  • get[OtherType] : 다른 타입의 인스턴스를 생성. 이전에 반환했던 것과 같을 수 있음.
  • new[OtherType] : 다른 타입의 새로운 인스턴스를 생성.

 


열거형에서의 예시

public enum Size {
    SMALL, MEDIUM, LARGE, EXTRA_LARGE;

    public static Size of(String sizeName) {
        for (Size size : values()) {
            if (size.name().equalsIgnoreCase(sizeName)) {
                return size;
            }
        }
        throw new IllegalArgumentException("Invalid size name: " + sizeName);
    }
}

 

 

 

출처

 

https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90#2._%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4%EC%97%90_%EB%8C%80%ED%95%B4_%ED%86%B5%EC%A0%9C_%EB%B0%8F_%EA%B4%80%EB%A6%AC%EA%B0%80_%EA%B0%80%EB%8A%A5%ED%95%98%EB%8B%A4