← Posts

GoF · Creational · Factory Method

Factory Method

객체를 만드는 코드가 구체 타입에 박히지 않게. 무엇을 만들지 하위 클래스가 결정하도록 생성을 메서드로 빼는 생성 패턴.

목차
  1. 객체를 만드는 코드가 구체 타입에 묶인다
  2. 팩토리 메서드 = 만드는 걸 메서드로 빼서 하위가 정한다
  3. 무엇이 문제였나 - 만들기와 쓰기가 붙어 있다
  4. 만드는 단계를 메서드로 뺀다
  5. 하위 클래스가 무엇을 만들지 정한다
  6. 흐름은 한 번만 쓰고 재사용된다
  7. 단순 팩토리와는 다르다
  8. 실무: 뼈대는 고정, 생성만 열 때
  9. 정리

객체를 만드는 코드가 구체 타입에 묶인다

배송을 계획하는 코드가 있다.

java
public class Logistics {
    public void planDelivery() {
        Truck truck = new Truck();   // 트럭에 박혀 있다
        truck.deliver();
    }
}

지금은 트럭으로만 배송한다. 그런데 배로도 보내야 한다면? planDelivery()를 열어 if로 갈라 new Ship()을 끼운다. 항공이 또 생기면 또 연다.

문제는 무엇을 만들지가 이 메서드 안에 콕 박혀 있다는 것이다. 만드는 대상이 늘 때마다, 배송을 계획하는 로직이 든 코드를 계속 건드리게 된다.

팩토리 메서드 = 만드는 걸 메서드로 빼서 하위가 정한다

팩토리 메서드 패턴은 이렇게 말한다.

객체를 만드는 일을 메서드로 빼두고, 무엇을 만들지는 하위 클래스가 결정하게 한다.

new Truck()을 직접 쓰지 말고, “탈것을 하나 만들어라”라는 메서드만 부른다. 그 메서드가 실제로 트럭을 만들지 배를 만들지는, 그 클래스를 상속한 하위 클래스가 정한다.

무엇이 문제였나 - 만들기와 쓰기가 붙어 있다

원래 코드의 진짜 문제는 만드는 일과 쓰는 일이 한 메서드에 붙어 있던 것이다.

planDelivery()가 하고 싶은 건 “탈것으로 배송한다”는 흐름이다. 그 흐름은 트럭이든 배든 똑같다. 그런데 그 안에서 new Truck()으로 구체적인 종류까지 정해버리니, 흐름을 재사용하려 해도 트럭에 묶여 못 한다.

그러니 갈라야 할 건 분명하다. 흐름은 그대로 두고, “무엇을 만드는가”만 떼어낸다.

만드는 단계를 메서드로 뺀다

먼저 탈것을 인터페이스로 세운다.

java
public interface Transport {
    void deliver();
}

public class Truck implements Transport {
    public void deliver() { /* 도로로 */ }
}
public class Ship implements Transport {
    public void deliver() { /* 바다로 */ }
}

그리고 Logistics에서 만드는 일을 메서드로 빼고 비워둔다. 이게 팩토리 메서드다.

java
public abstract class Logistics {

    // 팩토리 메서드 - 무엇을 만들지는 하위가 정한다
    protected abstract Transport createTransport();

    // 공통 흐름 - 무엇이 오든 똑같다
    public void planDelivery() {
        Transport transport = createTransport();
        transport.deliver();
    }
}

planDelivery()는 이제 TruckShip도 모른다. createTransport()가 준 Transport를 쓸 뿐이다.

하위 클래스가 무엇을 만들지 정한다

빈 팩토리 메서드를 하위 클래스가 채운다.

java
public class RoadLogistics extends Logistics {
    protected Transport createTransport() { return new Truck(); }
}
public class SeaLogistics extends Logistics {
    protected Transport createTransport() { return new Ship(); }
}
diagramdiagram

RoadLogistics는 트럭을, SeaLogistics는 배를 만든다. 항공이 생기면 AirLogisticsPlane을 새로 더하면 된다. 기존 클래스는 하나도 안 건드린다.

흐름은 한 번만 쓰고 재사용된다

값어치가 여기서 나온다. planDelivery()라는 흐름은 Logistics딱 한 번 있다. 하위 클래스들은 그 흐름을 물려받아, “무엇을 만드는가”만 각자 정한다.

만약 배송 흐름에 로그를 남기거나 검증을 더하고 싶으면, planDelivery() 한 곳만 고치면 모든 하위가 같이 얻는다. 공통 흐름은 모으고, 달라지는 생성만 갈라둔 덕이다.

단순 팩토리와는 다르다

“팩토리”라는 말 때문에 자주 헷갈리는 게 있다. 이런 정적 메서드를 단순 팩토리라고 부르기도 한다.

java
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 정적 메서드(관용구), 이건 상속·다형성
언제하위별로 만드는 물건이 갈리고 상속이 자연스러울 때

무엇을 만들지를 흐름 코드에서 떼어, 하위 클래스에게 넘기는 것. 그래서 흐름은 재사용되고 종류는 자유롭게 느는 것 - 그게 팩토리 메서드다.

관련 글