Design pattern 6. static factory method

김미숙's avatar
Jul 22, 2025
Design pattern 6. static factory method

✅ static factory method

  • 자바에서 객체를 생성하는 한 가지 방법으로, new 키워드를 직접 쓰지 않고, 정적 메서드를 통해 객체를 반환하는 방식
 

✅ 기본 예시

public class User { private String name; private User(String name) { this.name = name; } // 정적 팩토리 메서드 public static User of(String name) { return new User(name); } }
User user = User.of("홍길동"); // new User("홍길동") 대신 사용
 

✅ 장점 6가지 (Effective Java 3판 기준)

1. 이름을 가질 수 있다 🏷️
  • 생성자보다 이름 있는 메서드가 더 의도를 잘 표현
BigInteger big1 = BigInteger.valueOf(1L); // 의미 명확 BigInteger big2 = new BigInteger("1"); // 상대적으로 불분명
 
2. 호출 시마다 새 객체를 생성하지 않아도 된다 ♻️
  • 캐싱, 싱글턴, 불변 객체 등에 유용
Boolean b = Boolean.valueOf(true); // 항상 동일 객체 반환 (true/false 재사용)
 
3. 하위 타입을 반환할 수 있다 🔁
  • 반환 타입을 인터페이스로 숨기고, 실제 구현은 내부에서 결정 가능
public static List<Integer> emptyList() { return Collections.emptyList(); // 실제로는 ImmutableEmptyList 반환 }
 
4. 제네릭 타입 추론이 가능 💡
Map<String, List<String>> map = Map.of(); // 타입 추론됨
 
5. 인스턴스화 로직을 한 곳에 모을 수 있다
  • of(), from(), valueOf() 등 여러 이름으로 상황별 분기 가능
 
6. 생성자를 감출 수 있다 🔒
  • 외부에서 new로 직접 생성 못하도록 제한
  • enum, 싱글턴, 도메인 객체 생성 등에서 유용
 

❌ 단점

단점
설명
생성자처럼 상속이 불가능
User.of()는 상속 구조에서 오버라이드 어려움
정적 메서드는 이름으로만 구분
오버로딩은 가능하지만 명확하지 않을 수 있음
개발자들이 of()의 의미를 이해해야 함
생성자보다 문서화가 중요함
 

✅ 자주 쓰는 네이밍 관례

메서드 이름
용도
of()
단순한 객체 생성
from()
변환
valueOf()
값 기반 생성
getInstance()
싱글턴 or 캐시
newInstance()
항상 새 객체 생성
instance()
싱글턴 반환 (요즘 잘 안 씀)
✅ 실제 예: Enum.valueOf()
DayOfWeek day = DayOfWeek.valueOf("MONDAY");
 

✅ 요약

특징
설명
메서드 기반 객체 생성
생성자 대신 정적 메서드로 생성
이름 부여 가능
의도를 명확히 표현
캐싱, 싱글턴 등 유연
new보다 제어 쉬움
하위 타입 반환 가능
인터페이스와 함께 많이 사용

예제

User
package ex96.type2; public class User { private Integer id; private String username; private String password; private String role; // Teacher, Student // 학생 private Integer score; private Integer level; // 선생 private Integer sal; private String position; public static User createStudent() { return new User(null, "ssar", "1234", "STUDENT", 100, 1, null, null); } public static User createTeacher() { return new User(null, "ssar", "1234", "TEACHER", null, null, 3000, "MANAGER"); } private User(Integer id, String username, String password, String role, Integer score, Integer level, Integer sal, String position) { this.id = id; this.username = username; this.password = password; this.role = role; this.score = score; this.level = level; this.sal = sal; this.position = position; } }
App
package ex96.type2; public class App { public static void main(String[] args) { User teacher = User.createTeacher(); User student = User.createStudent(); } }
 

💡 Of 와 From 의 차이

✅ 차이 요약

메서드 이름
사용 목적
예시
의미
of(...)
직접 구성 요소로부터 객체 생성
User.of(name, age)
“이 필드들로부터 만든다”
from(...)
다른 타입/객체로부터 변환
User.from(UserDTO dto)
“변환해서 만든다”
 

of(...)는 언제 쓰나?

  • 필드 값 자체를 받아 객체를 만들 때
  • 즉, 구성 요소들(primitive, enum, 값 객체 등)로 객체를 구성할 때
  • 보통 여러 매개변수가 있든 없든 직접 필드 값을 넘길 때
예시
public class Point { private final int x; private final int y; private Point(int x, int y) { this.x = x; this.y = y; } public static Point of(int x, int y) { return new Point(x, y); } }
✔️ 특징
  • 필드 하나만 있어도 쓸 수 있음 (매개변수 개수 상관 없음)
public static Boolean of(boolean value) { return value ? TRUE : FALSE; }
 

from(...)은 언제 쓰나?

  • 다른 객체 또는 형식을 변환할 때
  • 즉, 외부에서 들어온 DTO, JSON, API 응답 등에서 내부 도메인 객체로 바꿀 때
예시
public class User { private String name; private int age; public static User from(UserDTO dto) { return new User(dto.getName(), dto.getAge()); } }
✔️ 특징
  • 매개변수는 보통 1개 (변환 대상)
  • 변환의 의미를 명확히 함
 

✅ 보너스: valueOf(...)는?

  • 기존 값이나 문자열로부터 객체를 만들 때
  • 보통 기본 타입 → 객체로 바꿀 때 (Wrapper 클래스에서 자주 사용)
Integer i = Integer.valueOf("123"); DayOfWeek day = DayOfWeek.valueOf("MONDAY");
 

✅ 실전 구분 팁

상황
메서드 이름 추천
필드 값을 직접 넣어서 객체를 만들고 싶다
of(...)
DTO → Entity 변환하고 싶다
from(dto)
문자열, 숫자, enum 값 등으로 변환
valueOf(...)
 

✅ 요약 문장

  • of(...)객체를 구성하는 값들로 객체를 만들 때
  • from(...)다른 객체로부터 변환할 때
  • valueOf(...)기본 값(String, int 등)으로부터 변환할 때
 

예제

Rectangle
package ex96.type2; public class Rectangle { private double x; private double y; private double width; private Rectangle(double x, double y, double width) { this.x = x; this.y = y; this.width = width; } public static Rectangle from(String value) { if (value.equals("small")) { return new Rectangle(50, 50, 50 * 50); } else if (value.equals("medium")) { return new Rectangle(100, 100, 100 * 100); } else if (value.equals("large")) { return new Rectangle(200, 200, 200 * 200); } else { throw new IllegalArgumentException("Invalid rectangle value"); } } public static Rectangle of(double x, double y) { return new Rectangle(x, y, x * y); } }
App
package ex96.type2; public class App { public static void main(String[] args) { Rectangle smallR = Rectangle.from("small"); Rectangle mediumR = Rectangle.from("medium"); Rectangle largeR = Rectangle.from("large"); } }
Share article

parangdajavous