생성자보다 정적 팩토리 메소드

JINNI·2025년 6월 19일
0

[TIL] Java+Spring

목록 보기
17/20

🤔 고민의 계기

평소 아래와 같이 Request를 Service단에서 Entity로 변환, Repository에서 사용하는 방향을 생각했다.

public record PostCreateRequest(
        @NotBlank(message = "제목은 필수입니다.") String title,
        @NotBlank(message = "내용은 필수입니다.") String content
) {
    public Post toPostEntity(User user) {
        return new Post(title, content, user);
    }
}

팀원의 코드리뷰를 달던 중 다음과 같이 static을 활용해 메소드를 작성한 것을 보게 됐다.

public record OrderDto(
        Long menuId,
        String menuName,
        String menuImg,
        Integer menuPrice
) {
    public static OrderDto of(Menu menu){
        return new OrderDto(
                menu.getMenuId(),
                menu.getMenuName(),
                menu.getSingleImgUrl(),
                menu.getSinglePrice()
        );
    }
}

어떤 방법이 더 좋은 방법일까?

☑️ Class Method(Static Method)

  • 객체 생성 없이 객채명.메소드명() 으로 사용할 수 있는 메소드
  • 어떠한 한 객체에 대해 작동하는 인스턴스 메소드가 아니라 클래스 전체에 대한 메소드
// Instance Method
Server server = new Server();
server.example();

// Class Method
Server.example();

☑️ 정적 팩토리 메소드

클래스 메소드를 통해 객체를 생성할 경우, 이러한 메소드를 정적 팩토리 메소드라고한다.

= 클래스의 인스턴스를 반환하는 단순한 정적 메소드

class Product {
	private Product() { }
  ...
  public static of() {
    return new Product()
  }
}

Product product = Product.of();

그렇다면 여기서 의문이 하나 생긴다.

우리한테는 생성자가 있는데 왜 정적 팩토리 메소드를 사용해야 하는가?

이펙티브 자바에 다음과 같은 구절이 있다고 한다.

Consider static factory methods instead of constructors
(생성자 보다 정적 팩토리 메소드를 고려하라)

지금부터 그 이유에 대해 공부해 보자.

☑️ 왜, 언제 필요할까?

1. 이름을 가질 수 있음

public class Car {
    private final CarName carName;
    private final CarPosition carPosition;

		private Car (String carName) {
		    this(new CarName(carName));
    }
    
    private Car (String carName, int carPosition) {
		    this(new CarName(carName), new CarPosition(carPosition));
    }
    
    public static Car carName (String carName) {
		    return new Car(carName);
		}
		
		public static Car carNameAndPosition (String carName, int carPosition) {
				return new Car(carName, carPosition);
		}
}
  • 생성자의 인자의 타입, 개수, 순서 등이 같다고 해도 메서드의 이름을 통해 명확히 나타낼 수 있음
  • 생성자의 이름을 명확하게 지정해줄 수 있음

2. 호출할 때마다 새로운 객체(인스턴스) 생성할 필요가 없음

  • new 키워드가 호출될 때마다 새로운 객체가 생성되는데, 정적 팩토리 메소드를 사용하면 메소드 내부에서 Singleton 구현 or 미리 생성된 객체(캐싱)를 사용하는 구조가 가능함.
    • 초기화 값이 사용된 적 있는 값인 경우 원래 사용하던 인스턴스를 반환하게 할 수 있음
      class Day {
          private String day;
      
          public Day(String day) { this.day = day; }
      
          public String getDay() { return day; }
      }
      
      // Day 객체를 생성하고 관리하는 경량 패턴 팩토리 클래스
      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;
              }
          }
      }
    • getInsance() 라는 정적 팩토리 메소드를 사용해 오로지 하나의 객체만 반환하도록 해 메모리를 아끼도록 유도할 수 있음
      class Singleton {
          private static Singleton instance;
      
          private Singleton() {}
      
          // 정적 팩토리 메서드
          public static synchronized Singleton getInstance() {
              if (instance == null) {
                  instance = new Singleton();
              }
              return instance;
          }
      }

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

  • 부모 클래스나 인터페이스를 반환할 수 있다.
public class Diamond {
    private final int score;
    
    public Diamond(int score) {
        this.score = score;
    }
    
    public String description() {
        return String.format("%s : %d", this.getClass().getSimpleName(), score);
    }
}

public class Silver {
    private final int score;
    
    public Silver(int score) {
        this.score = score;
    }
    
    public String description() {
        return String.format("%s : %d", this.getClass().getSimpleName(), score);
    }
}

public class App {
    public static void main(String[] args) {
        Diamond diamond = new Diamond(1900);
        Silver silver = new Silver(1300);
    
        System.out.println(diamond);
        System.out.println(silver);
    }
}

위와 같이 Diamond와 Silver를 main에서 직접적으로 사용한다.

여기서 발생하는 문제는 구현 클래스가 그대로 드러난다는 것이다.

다음과 같이 인터페이스를 반환하도록 수정할 수 있다.

4. 어떤 매개변수의 값이 오는지에 따라 다른 클래스 반환

5.

☑️ 정적 팩토리 메소드의 이름 규약

💡 결론!

암묵적으로 매개변수가 여러개일 때는 of, 1개일 때는 from을 사용하는 규약이 존재한다.

하지만 개인적으로 코드는 “명확성”이 중요하다고 생각하기 때문에..

팀원들과 논의해 네이밍을 수정하고, 해당 네이밍을 문서화해서 정리해두는 것도 나쁘지 않겠다는 생각이 든다.

왜 규약을 그렇게 했지.. ㅋ.ㅋ


📎 참고자료

정적 팩토리 메소드 정리

[자바, Java] 이펙티브 자바(Effective Java) - 아이템 01 정적 팩토리 메서드

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

profile
천재 개발자 되기

0개의 댓글