✅ 정적 팩토리 메서드
- 쉽게 말해 생성자로 인스턴스를 생성하지 않고, static Method를 사용해 인스턴스를 생성하는 방식이다.
- 간단한 예시로 여러 장르(genre)를 가지는 게임(Game) 객체에 대한 코드를 살펴보자.
생성자
public class Game {
private String genre;
public Game(String genre) {
this.genre = genre;
}
}
public static void main(String[] args) {
Game game = new Game("FPS");
}
정적 팩토리 메서드
public class Game {
private String genre;
private Game(String genre) {
}
public static Game from(String genre) {
return new Game(genre);
}
}
public static void main(String[] args) {
Game game = Game.from("RPG");
}
이렇게만 봤을 때는 "그냥 익숙한 생성자 방식을 사용해서 인스턴스를 생성하면 안돼?"라고 생각할 수 있다.
나도 처음에 이걸 굳이 왜 쓰는거지라는 생각이 먼저 들었고 겉멋인가 생각이들었다.
하지만 정적 팩토리 메서드 패턴이 아무 의미 없이 등장하진 않았을 것이다.
그래서 이번 포스팅에서는 정적 팩토리 메서드를 왜 사용하는가, 그리고 언제 사용하면 좋은지에 대해 다뤄보려한다.
✅ 왜 정적 팩토리 메서드를 사용하는가?
🖥️ 장점 1. 이름을 가질 수 있다.
게임별로 게임 장르와, 이용 등급이 있다고 가정해보자.
생성자를 사용할 때
public class Game {
private String genre;
private String rating;
public Game(String genre, String rating) {
this.genre = genre;
this.rating = rating;
}
}
public static void main(String[] args) {
Game fpsForAdult = new Game("fps", "청소년이용불가")
Game rpgForAll = new Game("rpg", "전체이용가")
}
Game 객체의 내부 구조를 모르는 사람은 Game 인스턴스를 생성할 때 몇 번째 인자에 어떤 값이 들어가야 하는지 모른다.
이는 자바 특성상 생성자는 클래스 이름으로 고정이기 때문에 발생하는 문제이다.
이 코드를 혼자서만 보면 상관없지만, 협업같은 환경에서 다른 사람이 코드만으로 의도가 파악할 수가 없다.
정적 팩토리 메서드를 사용한다면 어떻게 바뀔까?
정적 팩토리 메서드를 사용할 때
생성자로 인스턴스 생성하는 것을 방지하기 위해 생성자의 접근 제어자를 private로 설정한다.
public class Game {
private String genre;
private String rating;
private Game(String genre, String rating) {
this.genre = genre;
this.rating = rating;
}
public static Game adultAccessFrom(String genre) {
return new Game(genre, "청소년이용불가");
}
public static Game fullAccessFrom(String genre) {
return new Game(genre, "전체이용가");
}
}
public static void main(String[] args) {
Game fpsForAdult = Game.adultAccessFrom("fps");
Game rpgForAll = Game.fullAccessFrom("rpg");
}
이런식으로 어떤 인스턴스를 생성할 지 메서드 이름을 통해 파악이 가능해지기 때문에
Game 객체 내부 구조를 알 필요가 없다.
훨씬 가독성이 좋아진 것을 볼 수 있다.
🖥️ 장점 2. 호출될 때 마다 인스턴스를 새로 생성하지 않아도 된다. (싱글톤으로 활용 가능)
public class Game {
private static final Game instance = new Game();
private Game() {}
public static Game getInstance() {
return instance;
}
}
public static void main(String[] args) throws IOException {
Game game1 = Game.getInstance();
Game game2 = Game.getInstance();
System.out.println(game1);
System.out.println(game2);
System.out.println(game1 == game2);
}

위처럼 싱글톤으로 객체를 하나만 만들어두고 이를 재사용하여 불필요한 객체 생성을 막을 수 있다.
오로지 하나의 객체만 반환하기 때문에 메모리를 절약할 수 있다는 장점이 있다.
그렇다고 무조건 싱글톤 방식이 좋은 것은 아니다.
한번만 생성되어 상태를 공유되어야 하는 경우에 유용하게 사용할 수 있지만,
각 객체가 다른 상태를 가져야 한다면 위 방식은 적절하지 않을 것이다.
상황에 맞게 사용하자.
🖥️ 장점 3. 파라미터에 따라 다른 객체를 반환할 수 있다.
메서드이기 때문에 파라미터를 받을 수 있고, 그렇기 때문에 파라미터 별로 분기처리하여
다른 객체를 반환하도록 할 수 있다.
3번의 장점과 연결한다면 Car 인터페이스를 구현하는 구현체 클래스들을 파라미터 별로 반환할 수 있는 것이다.
public interface Car {}
class SportsCar implements Car {}
class SuvCar implements Car {}
public static Car createCar(String type) {
if (type.equals("sports")) {
return new SportsCar();
}
if (type.equals("suv")) {
return new SuvCar();
}
throw new IllegalArgumentException("해당 타입의 자동차가 존재하지 않습니다 : " + type);
}
🖥️ 장점 4. 정적 팩토리 메서드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 된다.
public class CarFactory {
private CarFactory() {
}
public static Car createCar(String type) {
if (type.equals("sports")) {
return new SportsCar();
}
if (type.equals("suv")) {
return new SuvCar();
}
throw new IllegalArgumentException("해당 타입의 자동차가 존재하지 않습니다 : " + type);
}
}
여기에 전기자동차 (ElectricCar)가 추가되었다고 가정해보자.
public class CarFactory {
private CarFactory() {
}
public static Car createCar(String type) {
if (type.equals("sports")) {
return new SportsCar();
}
if (type.equals("suv")) {
return new SuvCar();
}
if (type.equals("electric")) {
return new ElectricCar();
}
throw new IllegalArgumentException("해당 타입의 자동차가 존재하지 않습니다 : " + type);
}
}
위 처럼 ElectricCar 클래스를 만들고 CarFactory 클래스의 팩토리 메서드에 ElectricCar객체를 생성하는 메서드만 추가하면 된다.
✏️공부한 내용
어떤 타입의 객체를 반환할지 결정하는 로직이 팩토리 메서드 내부에 있기 때문에
클라이언트는 팩토리 메서드를 호출하기만 하면 되고, 클라이언트는 어떤 클래스의 객체가 생성되는지 알 필요가 없다.
새로운 타입의 객체가 필요해진다면 팩토리 메서드만 수정하면 된다.
즉, 유연한 확장성을 가질 수 있고, 내부 구현을 드러내지 않아 캡슐화할 수 있다는 장점도 있다.
'Back-end > Java' 카테고리의 다른 글
| [Java] 가비지 컬렉터(GC) 이해하기 (5) | 2025.08.04 |
|---|---|
| [JAVA] 문자열 앞뒤 공백 제거하기 trim() vs strip() 차이 (0) | 2024.12.11 |
| [JAVA] Record Class (1) | 2024.11.29 |
| [Java - 기본] 인터페이스 - 다중 구현 (0) | 2024.05.06 |
| [Java - 기본] 인터페이스 (2) | 2024.05.05 |