들어가며
현재 우아한테크코스 8기 1주차 문제를 열심히 풀어보는 중이다! 1주차라 그런지 문제 자체의 난이도는 크게 어렵지 않다(?). 그래서 앞으로 어려워질 것을 대비해서 자바의 객체지향을 적극 활용할 수 있게끔 다양한 개념들을 적용해보며 구현중이다. 그 개념들을 내 것으로 만들 수 있게 자세하게 정리해본다!!
그중 가장 첫번째로 적용해본 개념이 정적 팩토리 메서드이다. 사실 전 기수의 코드들을 참고하며 작성하면서 되게 좋은 객체 생성 방법이다...!! 하고 썻었는데, 알고보니 엄청 유명한 객체 생성 패턴이었다. 이 개념을 적용해보면서 "객체 생성의 역할 자체가 중요한 경우" 라면 정적 팩토리 메서드를 고려해볼만 하구나!를 느꼈다.
정적 팩토리 메서드가 무엇인지 자세하게 적어보며 단점까지 알아보자!!

본론으로
단도직입적으로 말하자면 정적 팩토리 메서드는 객체의 생성을 담당하는 static method이다.
그러면 드는 생각은? 엥? 객체 생성은 new 연산자 아닌가? 객체를 생성하는 메서드가 따로 있다고? 사실은 정적 팩토리 메서드도 내부에서 new연산자를 이용해 객체를 만들어서 반환해준다. 에이.. 김빠져~ 그럼 뭐가 다른거야??? 이해를 위해 아래 코드를 보자.
예제 코드 - 생성자 new 방식
public class Burger {
private String name;
private int price;
public Burger(String name, int price) {
this.name = name;
this.price = price;
}
public Burger(String name) {
this.name = name;
this.price = 5000; // 기본 버거 5000원!
}
}
첫번째 생성자는 입력받은 이름과 가격으로 버거를 생성해준다. 다양한 버거와 가격을 정해줄 수 있을 것 같다.
오버로딩한 두번째 생성자는 입력받은 이름으로 5000원의 버거를 생성해줄 수 있을 것 같다.
public class Application {
public static void main(String[] args) {
Burger cheeseBurger = new Burger("치즈버거", 6000); // 치즈 1000원추가!
Burger origianlburger = new Burger("기본버거"); // 기본 버거 가격은 어떻게..?
}
}
그럼 위의 코드처럼 필요한 클래스에서 버거를 생성할 수 있을 것이다. 글을 쭈욱 읽어온 우리는 기본버거가 5000원인 것을 알겠지만, 복잡한 코드를 보고 온 나의 팀동료는 위의 생성자 코드만 보고나서 기본버거의 가격은 얼마지...? 결국 어떤식으로 가격을 생성중이지..? 라는 생각이 들어 코드를 파고 들어갈 것이다.
예시가 이해되는가?!?, 즉 생성자를 이용한 new 연산자는 객체의 생성 이유가 명확하지 않다는 것이다!
여기서 드는 생각이 뭔가 객체를 생성할 때 의도를 명확히 하면 어떻겠나? -> 객체를 생성하는 메서드를 만들면 어떨까? 라는 생각에서 출발한 것이 정적 팩토리 메서드 라고 생각한다..(제 생각..)
자바를 공부하는 사람들이라면 [이펙티브 자바]는 한번씩 들어봤을 것이다!! 이 책에서 말하는 정적 팩토리 메서드의 장점 5가지가 있다. 하나씩 말하면서 더 깊게 파고 들어가보자.
1. 이름을 가질 수 있다.
2. 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
1. 이름을 가질 수 있다.
위의 코드를 정적 팩토리 메서드를 통해서 리팩토링 해보겠다!
예제 코드 - 정적 팩토리 사용방식
public class Burger {
private final String name;
private final int price;
private Burger(String name, int price) {
this.name = name;
this.price = price;
}
public static Burger createCheeseBurger(String name, int price) {
return new Burger(name, price);
}
public static Burger createOriginalBurger(String name) {
return new Burger(name, 5000);
}
}
아래처럼 정적 팩토리 메서드를 활용해서 버거를 생성해주고 있다. 어떠한가? 생성 메서드에 이름을 붙히니깐 자연스럽게 의도가 보이지 않는가?!?!?!
public class Application {
public static void main(String[] args) {
Burger cheeseBurger = Burger.createCheeseBurger("치즈버거", 6000);
Burger OriginalBurger = Burger.createOriginalBurger("기본버거");
}
}
이름으로 의도가 명확해지니, 매개변수에 버거 이름과 가격을 굳이 넣어줘야 할까? 라는 생각이 드는 것도 당연한 수순이다.
2. 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다.
이에 대한 예시는 enum이 대표적이다.
사용되는 값들의 개수가 정해져 있으면 해당 값을 미리 생성해놓고 조회(캐싱) 할 수 있는 구조로 만들 수 있다.
예제 - Store Class
public class Store {
private static final Map<String, Store> stores = new HashMap<>();
static {
stores.put("King", new Store("BurgerKing"));
stores.put("Moms", new Store("MomsTouch"));
stores.put("KFC", new Store("KFC"));
stores.put("Lotte", new Store("LotteLia"));
}
public static Store from(String store) {
return stores.get(store);
}
private final String store;
private Store(String store) {
this.store = store;
}
public String getStore() {
return store;
}
}
public static void main(String[] args) {
Store store = Store.from("King");
System.out.println(store.getStore()); // "BurgerKing"
}
위 코드를 살펴보면 Store.from을 실행하면 King에 해당하는 "Burgerking"을 찾아 반환한다. 이때 각 가게이름을 stacic 을 통해 미리 생성하였기에, 새로 만드는 것이 아닌 이미 만들어 놓은 것을 찾아서 반환한다.
따라서 정적 팩토리 메서드를 사용한다면 미리 생성된 Store객체만 반환하면 되는 것이다. 이렇게 해서 매번 객체를 생성할 필요가 없기에 코드 중복도 줄이고, 메모리 절약도 하였다!
3. 하위 자료형 객체를 반환할 수 있다.
만약 Burger의 하위 클래스가 McDonals, BurgerKing, LotteLia 라고 하자! (살짝 억지 예시인데... 글 작성하는 지금 햄버거가 너무 먹고싶다...) 예시이니 만큼 간단한게 랜덤숫자에 따라서 가게가 정해진다고 하겠다. ㅎㅎ
public class Burger {
...
private static Burger of(int randomNumber) {
if (0 < randomNumber && randomNumber <= 5) {
return new McDonals();
}
if (5 < randomNumber && randomNumber <= 10) {
return new BurgerKing();
}
if (10 < randomNumber && randomNumber <= 15) {
return new LotteLia();
}
if ...
}
...
}
위 코드를 확인해보자. of () 라는 정적 팩토리 메서드를 사용하여 객체 생성 시, 랜덤 숫자에 따라 하위 객체 타입을 유연하게 반환 중이다!!
이는 정적 팩터리 메서드를 이용하면 "객체 생성"시, 분기처리를 통해 하위 타입의 객체를 반환할 수 있다는 것을 알 수 있다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
뭔가 딱 장점 제목 보자마자 유연해진다는 거 같은데... 라는 생각이 들지 않는가! 매개변수에 따라 매번 다른 클래스 객체를 반환할 수가 있다고...?
이해를 위해 실제로 쓰고 있는 자바의 정적 팩토리 메서드를 캡처해봤다! 아래는 Enum의 정적 팩토리 메서드인데, 매개변수에 길이에 따라 RegularEnumSet, JumboEnumSet을 반환하고 있는 것을 볼 수 있다.

즉, 입력 매개변수에 따라 다른 클래스의 객체를 반환 할 수 있다. -> 엄청 유용하지 않겠는가!
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
이 장점은 프레임워크를 만드는 근간이 된다고 한다!! 이번 우테코에서는 크게 쓸일은 없어보이지만 엄청 핵심인 부분이다. 아래 코드를 보자
public interface StoreService {
void sell();
}
public class StoreServiceFactory {
// 구현체가 아직 없어도 선언 가능
public static StoreService getInstance() {
return new SomeStoreServiceImpl(); // 나중에 구현체가 생기면 연결
}
}
메서드 작성 시점에는 어떤 ‘구체 클래스(구현체)’가 만들어지지 않았더라도, 그 메서드는 이미 설계상 유효하다는 뜻이다.
왜냐하면 정적 팩토리 메서드는 이렇게 “상위 타입”을 반환할 수 있기 때문이다..!!
지금 시점엔 SomeStoreServiceImpl이 없어도, “StoreService를 반환하는 정적 팩토리 메서드”라는 설계 자체는 완전히 유효하다고 한다. 그러면 나중에 구현체만 외부에서 주입해주면 로봇이 조립되듯이 촥촥촥 잘 작동할 거 같다. 완전 스프링이 좋아하는 방식 같다!
그래서 좀 더 찾아보니 저번에 스프링의 DB 커넥션 얻어오는 방법에 대해서 잠깐 공부했었을 때 아래의 코드를 그냥 일종의 프레임워크가 제공해주는 규칙? 방식? 정도로 이해하고 썻던 거 같다.
Connection conn = DriverManager.getConnection(url, user, pass);
지금은 이게 DiverManager의 정적 팩토리 메서드이고, 어떤 의존성을 주입 받을지 기다리는 중이구나! 를 눈치 챌 수 있다. 구현체는 개발자가 선택하는 것이니!!(JDBC, JPA, MyBatis.. 등등)
또 여기서 확장되는 생각이... 생각해보니 getBean(), getContext() 등등 스프링의 리소스를 반환해주는 메서드들은 다 정적 클래스였다....!!!! 대박,,, 스프링은 정적 팩토리 메서드로 설계해놓고 어떤 구현체가 들어오느냐에 따라 유연하게 다 대비하고 있었던 거구나....!
public interface ApplicationContext { ... }
public class ApplicationContextFactory {
public static ApplicationContext getContext(Config config) {
// 나중에 어떤 구현체든 여기서 반환 가능
}
}
위에는 찾아본 그 예시 코드이다. 스프링은 Applicationcontext를 인터페이스로 두고, 정적 팩토리 메서드로 의존성 주입 받을 수 있게끔 설계상 문제없이 대기중이었던 것이다. (실제로는 new AnnotationConfigApplicationContext() 이걸 주입하겠지.)
6. 객체 생성을 캡슐화할 수 있다.
이것은 블로그에 많이 떠돌아다니는 장점인데, 엄청 큰 이점이라 생각해 적어본다.
Dto라는 개념이 들어갔다는 것은 Entity와 Dto간의 변환이 자유롭게 이루어져야하고 많은 로직에서 변환로직이 쓰일텐데, 정적 팩토리 메서드를 이용하면 그 내부를 모르더라도 손쉽게 변환할 수 있다!!! -> 너무 중요하지 않겠나!
public class BurgerDto {
private String name;
private int price;
pulbic static BurgerDto from(Burger burger) {
return new BurgerDto(burger.getName(), burger.getPrice());
}
}
// Burger -> BurgerDto 로 변환
BurgerDto burgerDto = BurgerDto.from(burger);
만약 아래에서 정.팩.메를 안쓴다면, 내부를 다 드러내면서~ 코드 길어지면서~ butgerDto를 얻어와야 할 것이다.
Burger burgerDto = BurgerDto.from(burger); // 정적 팩토리 메서드를 쓴 경우
BurgerDto burgerDto = new BurgerDto(burger.getName(), burger.getPrice); // 생성자를 쓴 경우
이렇게 크게 6가지의 장점을 알아보았다.
장점만 있을까 단점도 물론 있다!!
1. 상속에는 public 혹은 protected 생성자가 필요하므로 정적 팩토리 메서드만 제공할 경우, 상속이 불가능하다.
2. 정적 팩토리 메서드를 다른 개발자들이 찾기 어렵다. -> 개발자가 커스텀하여 의미를 부여하기 위해 만든 것이기에 마음대로 만든다면 매우 찾기 어려워질 것이다.
하지만 이는 코드 문서화와 많은 대화 그리고 암묵적으로 생성하는 정적 팩토리 코드 컨벤션을 이용하면 단점보다 장점이 훠얼씬 크다고 생각한다. 아래는 암묵적으로 사용하는 정팩메 코드 컨벤션이다.
정적 팩토리 메서드 네이밍 컨벤션
- from : 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드
- of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드
- valueOf : from과 Of의 더 자세한 버전
- instance 혹은 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장 하지 않음
- create 혹은 newInstance :instance 혹은 getInstance 와 같으나 매번 새로운 인스턴스를 생성해 반환 함을 보장
마무리하며
정적 팩토리 메서드는 코드의 개념을 이용해서 개발자의 의도를 넣을 수 있고 이렇게나 활용도를 높힐 수 있다는 점이 정말 매력적으로 느껴진다. 활용도가 정말 높아보여서 이곳저곳 상황에 따라 적용해볼 생각이다.
그리고 실제 프레임워크에서도 이 개념을 적극 활용하고 있으니 잘하는 개발자들도 이런 방식으로 구현해서 활용하는구나를 직접적으로 느낀다!!(오픈소스 기여 해보고 싶을지도)
사실 요즘 개발시장이 빠르게 구현하고 새로 나온 기술 써보고 하느라 급급한데, 객체지향적인 코드라는 목표로 문제를 쳐다보게끔 해준 우테코에게 무한 감사하다. 우테코라는 목표가 생겼기에 하나의 문제를 보고 진득하게 생각하고 객체지향이라는 개념과 더더욱 친해지는 것 같다. 앞으로도 객체지향과 관련된 모든 개념 흡수해서 문제 구현에 적용해봐야지 ㅎㅎㅎ. 파이팅!!

'Java & Kotlin' 카테고리의 다른 글
| [JAVA] final 키워드 정복하기! (0) | 2025.10.16 |
|---|---|
| [JAVA] 정적 중첩 클래스를 활용하여 계층간 독립적인 Validation을 적용해보자 (0) | 2025.10.16 |
| [JAVA] String trim() 메서드 : 문자열의 앞뒤에 있는 모든 공백 문자를 제거해주는 아주 좋은친구 소개 (0) | 2024.11.09 |
| [JAVA] 빠른 입출력을 위한 BufferdReader, BufferWriter (0) | 2024.10.16 |
| [JAVA] charAt() 메서드(문자열에서 문자 뽑기) (0) | 2024.09.17 |
