목차
객체를 만드는 코드가 구체 타입에 묶인다
배송을 계획하는 코드가 있다.
public class Logistics {
public void planDelivery() {
Truck truck = new Truck(); // 트럭에 박혀 있다
truck.deliver();
}
}지금은 트럭으로만 배송한다. 그런데 배로도 보내야 한다면? planDelivery()를 열어 if로 갈라 new Ship()을 끼운다. 항공이 또 생기면 또 연다.
문제는 무엇을 만들지가 이 메서드 안에 콕 박혀 있다는 것이다. 만드는 대상이 늘 때마다, 배송을 계획하는 로직이 든 코드를 계속 건드리게 된다.
팩토리 메서드 = 만드는 걸 메서드로 빼서 하위가 정한다
팩토리 메서드 패턴은 이렇게 말한다.
객체를 만드는 일을 메서드로 빼두고, 무엇을 만들지는 하위 클래스가 결정하게 한다.
즉 new Truck()을 직접 쓰지 말고, “탈것을 하나 만들어라”라는 메서드만 부른다. 그 메서드가 실제로 트럭을 만들지 배를 만들지는, 그 클래스를 상속한 하위 클래스가 정한다.
무엇이 문제였나 - 만들기와 쓰기가 붙어 있다
원래 코드의 진짜 문제는 만드는 일과 쓰는 일이 한 메서드에 붙어 있던 것이다.
planDelivery()가 하고 싶은 건 “탈것으로 배송한다”는 흐름이다. 그 흐름은 트럭이든 배든 똑같다. 그런데 그 안에서 new Truck()으로 구체적인 종류까지 정해버리니, 흐름을 재사용하려 해도 트럭에 묶여 못 한다.
그러니 갈라야 할 건 분명하다. 흐름은 그대로 두고, “무엇을 만드는가”만 떼어낸다.
만드는 단계를 메서드로 뺀다
먼저 탈것을 인터페이스로 세운다.
public interface Transport {
void deliver();
}
public class Truck implements Transport {
public void deliver() { /* 도로로 */ }
}
public class Ship implements Transport {
public void deliver() { /* 바다로 */ }
}그리고 Logistics에서 만드는 일을 메서드로 빼고 비워둔다. 이게 팩토리 메서드다.
public abstract class Logistics {
// 팩토리 메서드 - 무엇을 만들지는 하위가 정한다
protected abstract Transport createTransport();
// 공통 흐름 - 무엇이 오든 똑같다
public void planDelivery() {
Transport transport = createTransport();
transport.deliver();
}
}planDelivery()는 이제 Truck도 Ship도 모른다. createTransport()가 준 Transport를 쓸 뿐이다.
하위 클래스가 무엇을 만들지 정한다
빈 팩토리 메서드를 하위 클래스가 채운다.
public class RoadLogistics extends Logistics {
protected Transport createTransport() { return new Truck(); }
}
public class SeaLogistics extends Logistics {
protected Transport createTransport() { return new Ship(); }
}RoadLogistics는 트럭을, SeaLogistics는 배를 만든다. 항공이 생기면 AirLogistics와 Plane을 새로 더하면 된다. 기존 클래스는 하나도 안 건드린다.
흐름은 한 번만 쓰고 재사용된다
값어치가 여기서 나온다. planDelivery()라는 흐름은 Logistics에 딱 한 번 있다. 하위 클래스들은 그 흐름을 물려받아, “무엇을 만드는가”만 각자 정한다.
만약 배송 흐름에 로그를 남기거나 검증을 더하고 싶으면, planDelivery() 한 곳만 고치면 모든 하위가 같이 얻는다. 공통 흐름은 모으고, 달라지는 생성만 갈라둔 덕이다.
단순 팩토리와는 다르다
“팩토리”라는 말 때문에 자주 헷갈리는 게 있다. 이런 정적 메서드를 단순 팩토리라고 부르기도 한다.
public static Transport create(String type) {
if (type.equals("truck")) return new Truck();
if (type.equals("ship")) return new Ship();
// ...
}편리하지만 이건 GoF의 팩토리 메서드가 아니다. 그냥 관용구다. 새 타입이 생기면 이 if-else를 계속 고쳐야 한다.
팩토리 메서드는 if-else가 아니라 상속으로 가른다. 만드는 걸 메서드로 두고 하위 클래스가 오버라이드하니, 새 타입은 새 하위 클래스로 더해지고 기존 코드는 안 바뀐다. 무엇을 만들지 고르는 힘이 런타임의 조건문이 아니라 어떤 하위 클래스를 쓰느냐에 있는 것이다.
실무: 뼈대는 고정, 생성만 열 때
실무에서 팩토리 메서드는 **“흐름의 뼈대는 내가 정하고, 무엇을 만들지는 너(하위)가 채워라”**가 필요할 때 쓴다. 프레임워크가 확장점을 열어주는 전형적인 방식이다.
다만 값을 하는 자리는 분명하다. 하위 클래스별로 만드는 물건이 갈리는 구조일 때다. 종류가 하나거나 앞으로도 안 늘면, 하위 클래스를 만드는 비용이 이득보다 크다. 그럴 땐 그냥 new나 단순 팩토리가 낫다. 또 요즘 앱에선 “무엇을 넣을지 밖에서 정하는” 방식으로도 같은 목적을 이루므로, 팩토리 메서드는 상속 구조가 자연스러운 곳에서 고른다.
정리
| 팩토리 메서드란 | 객체 생성을 메서드로 빼고, 무엇을 만들지는 하위 클래스가 결정 |
| 문제 | 만들기와 쓰기가 붙어 있어, 만드는 대상이 늘면 흐름 코드를 건드린다 |
| 고침 | 생성을 추상 메서드로 빼고, 하위가 오버라이드해 구체 타입을 정한다 |
| 효과 | 공통 흐름은 한 번만, 새 타입은 새 하위 클래스로 (기존 불변) |
| 단순 팩토리와 차이 | 저건 if-else 정적 메서드(관용구), 이건 상속·다형성 |
| 언제 | 하위별로 만드는 물건이 갈리고 상속이 자연스러울 때 |
무엇을 만들지를 흐름 코드에서 떼어, 하위 클래스에게 넘기는 것. 그래서 흐름은 재사용되고 종류는 자유롭게 느는 것 - 그게 팩토리 메서드다.