[디자인 패턴] 팩토리 메소드 패턴

이현경·2025년 10월 14일

팩토리 메소드 패턴이란?

팩토리 메소드 패턴은 객체를 생성하는 코드를 서브클래스에 위임하는 패턴이다. 사용자가 new를 사용해 객체를 직접 생성하는 대신 ‘공장’에 요청하면, 공장의 구체적인 구현체(서브클래스)가 상황에 맞는 객체를 만들어주는 방식이다. 팩토리 메소드 패턴은 OCP, SRP 준수 및 느슨한 결합을 위해 사용한다.

그렇다면 팩토리 메소드 패턴은 언제 사용할까?

  • 생성로직을 캡슐화하고 싶을 때
  • 어떤 종류의 객체를 생성해야 할지 미리 알 수 없을 때

팩토리 메소드 패턴은 다음의 과정을 따라 수행된다.

  1. Product: 생성될 객체의 인터페이스 정의
  2. Concrete Products: 실제 생성될 객체들 구현
  3. Creator: 팩토리의 추상 클래스 정의
  4. Concrete Creators: 실제 팩토리 구현

아래의 예제를 보며 더 정확히 알아보자.




커피 전문점 예제

  1. Product: 생성될 객체의 인터페이스 정의

공장에서 만들어낼 ‘제품(Product)’의 공통 인터페이스를 정의한다

// Product 인터페이스
public interface Coffee {
    void brew();
}

  1. Concrete Products: 실제 생성될 객체들 구현

Coffee 인터페이스를 구현하는 실제 ‘제품’들을 만든다

// Concrete Product 1
public class Latte implements Coffee {
    @Override
    public void brew() {
        System.out.println("부드러운 라떼를 만듭니다 🥛");
    }
}

// Concrete Product 2
public class Americano implements Coffee {
    @Override
    public void brew() {
        System.out.println("깔끔한 아메리카노를 만듭니다 ☕");
    }
}

  1. Creator: 팩토리의 추상 클래스 정의

‘공장(Creator)’의 역할을 하는 추상 클래스를 만든다. 이 클래스는 제품을 생성하는 createCoffee()를 추상 메소드로 가진다. 실제 생성 로직은 서브클래스에게 위임한다.

// Creator (추상 팩토리)
public abstract class CoffeeShop {

    // 팩토리 메소드: 어떤 커피를 만들지는 서브클래스가 결정한다.
    protected abstract Coffee createCoffee(String type);

    // 팩토리 메소드를 사용하여 제품을 생성하고 사용하는 비즈니스 로직
    public Coffee orderCoffee(String type) {
        Coffee coffee = createCoffee(type); // 팩토리 메소드 호출
        System.out.println(type + " 주문이 들어왔습니다.");
        coffee.brew();
        return coffee;
    }
}

  1. Concrete Creators: 실제 팩토리 구현

CoffeeShop을 상속받아, 팩토리 메소드 createCoffee()를 실제로 구현하는 구체적인 ‘공장’들을 만든다.

// Concrete Creator (구체적인 팩토리)
public class Starbucks extends CoffeeShop {

    // 스타벅스만의 커피 제조 방식을 구현
    @Override
    protected Coffee createCoffee(String type) {
        if ("latte".equals(type)) {
            return new Latte();
        } else if ("americano".equals(type)) {
            return new Americano();
        } 
        throw new IllegalArgumentException("존재하지 않는 메뉴 타입입니다.");
}

  1. Main

사용자는 구체적인 커피(Latte)가 아닌 Starbucks라는 공장에만 의존하여 커피를 주문한다.

public class Main {
    public static void main(String[] args) {
        // 클라이언트는 '스타벅스'라는 공장만 알면 됨
        CoffeeShop starbucks = new Starbucks(<);

        // 공장에 "라떼"를 주문
        Coffee latte = starbucks.orderCoffee("latte");

        // 공장에 "아메리카노"를 주문
        Coffee americano = starbucks.orderCoffee("americano");
    }
}




팩토리 메소드 핵심: 캡슐화

사실 팩토리 메소드 패턴을 처음 공부할 때 Concrete Creator에서 반복적인 if-else문을 쓰길래 약간의 의문이 들었다. 분명 if-else문을 안 쓰기 위해 노력한다고 들었는데 왜 여기서는 이런 방법을 사용하는 지 궁금했는데, 여기서 더 중요한 것은 if-else문을 ‘누가’ 책임지고 있는지였다.

팩토리 패턴은 if-else문 자체를 없애는 것이 아니라, 객체 생성이라는 복잡한 책임을 한 곳에 모아 캡슐화하는 데에 목적이 있다. 위 코드에서 if-else문은 객체 생성만 전담으로 하는 팩토리가 가지고 있으므로, 새로운 클래스가 추가될 때 팩토리 코드만 수정하면 된다. 즉, main 메소드와 같은 클라이언트 코드는 수정할 필요가 없으므로 OCP를 준수한다고 볼 수 있다.

정리하자면, 팩토리 메소드 패턴은 if-else를 제거하는 것이 아니라, 올바른 위치로 이동시키고 격리하여 변경의 파급 효과를 최소화하는 패턴이다.




조금만 더: if-else문 없애기

만약 생성해야 할 객체의 종류가 많아진다면 팩토리의 if-else문조차 관리하기 버거워질 수 있다. 이때 Map 등을 이용하면 더 유연한 팩토리를 만들 수 있다.

// Map을 이용한 팩토리 예시
public class CoffeeFactory {
    private static final Map<String, Supplier<Coffee>> coffeeMap = new HashMap<>();

    // static 블록을 이용해 만들 수 있는 커피 종류를 미리 등록
    static {
        coffeeMap.put("latte", Latte::new);
        coffeeMap.put("americano", Americano::new);
    }

    public static Coffee createCoffee(String type) {
        Supplier<Coffee> coffeeSupplier = coffeeMap.get(type.toLowerCase());
        if (coffeeSupplier == null) {
            throw new IllegalArgumentException("존재하지 않는 메뉴입니다.");
        }
        return coffeeSupplier.get();
    }
}

이런 방식을 사용하면 새로운 커피 종류가 추가될 때 static 블록에 한 줄만 추가하면 되므로 if-else문이나 switch문 없이도 팩토리를 확장할 수 있다.




소감

팩토리 메소드 패턴을 배우게 되면서 반복적인 if-else문이 무조건 나쁜 것이 아니라는 것을 알게 되었다. 제일 중요한 것은 그런 반복적인 행위를 누가 책임지고 있느냐가 가장 중요한 것이었다. 혼자 공부하면서 if-else문을 없애는 방법도 알게되고 Supplier라는 새로운 함수형 인터페이스도 접하게 되어 보람찼다. 아직 이를 자유자재로 활용할 순 없지만 디자인 패턴을 하나씩 배우는 재미가 쏠쏠하다.

profile
커피 한 잔의 여유를 아는 품격있는 여자

0개의 댓글