Factory Pattern 정리

테사벨로그·2025년 10월 23일

Design Pattern

목록 보기
5/19
post-thumbnail

1. 왜 Factory Pattern이 생겨났는가?

문제 상황

// ❌ 나쁜 예: new 연산자에 직접 의존
Duck duck;
if (picnic)
    duck = new MallardDuck();
else if (hunting)
    duck = new DecoyDuck();
else if (inBathTub)
    duck = new RubberDuck();

문제점:

  • 구체적인 클래스에 직접 의존 (강한 결합)
  • 새로운 타입 추가 시 코드 수정 필요
  • 실행 중에 객체 타입 변경 불가능
  • 객체 생성 로직이 여러 곳에 중복

2. Simple Factory (비공식 패턴)

개념

"객체 생성 로직을 별도의 클래스로 분리"

// Factory 클래스
public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("cheese"))
            pizza = new CheesePizza();
        else if (type.equals("pepperoni"))
            pizza = new PepperoniPizza();
        return pizza;
    }
}

// Client 클래스
public class PizzaStore {
    SimplePizzaFactory factory;
    
    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }
    
    public Pizza orderPizza(String type) {
        Pizza pizza = factory.createPizza(type);  // Factory에 위임
        pizza.prepare();
        pizza.bake();
        return pizza;
    }
}

장점:

  • 객체 생성 로직을 한 곳에 캡슐화
  • 여러 클라이언트에서 재사용 가능

단점:

  • 여전히 if-else 문 필요
  • 지역별 다른 스타일(NY, Chicago) 지원 어려움

3. Factory Method Pattern

왜 Factory Method인가?

핵심 아이디어:

  • "객체 생성을 서브클래스에게 위임"
  • 상속을 사용하여 객체 생성 결정

Creator Class (추상 클래스)

public abstract class PizzaStore {
    // Template Method - 변하지 않는 알고리즘
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);  // Factory Method 호출
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    
    // Factory Method - 서브클래스가 구현
    protected abstract Pizza createPizza(String type);
}

왜 추상 클래스인가?

  • orderPizza()는 공통 알고리즘 (모든 Store에서 동일)
  • createPizza()는 변하는 부분 (각 Store마다 다름)
  • Template Method 패턴과 결합

Concrete Creator (구체적 Creator)

public class NYPizzaStore extends PizzaStore {
    protected Pizza createPizza(String type) {
        if (type.equals("cheese"))
            return new NYStyleCheesePizza();
        else if (type.equals("veggie"))
            return new NYStyleVeggiePizza();
        return null;
    }
}

public class ChicagoPizzaStore extends PizzaStore {
    protected Pizza createPizza(String type) {
        if (type.equals("cheese"))
            return new ChicagoStyleCheesePizza();
        else if (type.equals("veggie"))
            return new ChicagoStyleVeggiePizza();
        return null;
    }
}

4. Factory Method Pattern 핵심 구조

      Creator (추상 클래스)
   - orderPizza()       ← 공통 알고리즘
   - createPizza()      ← Factory Method (abstract)
          ↑
          |
    ┌─────┴─────┐
    |           |
NYPizzaStore  ChicagoPizzaStore
(구체적 Creator들)

관계:

  • "is-a" 관계 (상속)
  • Creator가 Product 생성 방식을 몰라도 됨
  • 서브클래스가 어떤 Product를 만들지 결정

5. Factory Method 예시 코드

Step 1: Product 인터페이스

public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    
    void prepare() {
        System.out.println("Preparing " + name);
    }
    void bake() { /* ... */ }
    void cut() { /* ... */ }
    void box() { /* ... */ }
}

Step 2: Concrete Products

public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY Style Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
    }
}

public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name = "Chicago Deep Dish Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";
    }
    
    void cut() {
        System.out.println("Cutting into square slices");
    }
}

Step 3: 실행

public class PizzaTestDrive {
    public static void main(String[] args) {
        // 1. Store 생성 (이때 어떤 Factory를 쓸지 결정)
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();
        
        // 2. 주문
        Pizza pizza1 = nyStore.orderPizza("cheese");
        // → NYStyleCheesePizza 생성
        
        Pizza pizza2 = chicagoStore.orderPizza("cheese");
        // → ChicagoStyleCheesePizza 생성
    }
}

6. Abstract Factory Pattern

왜 Abstract Factory인가?

Factory Method의 한계:

  • Pizza의 재료(ingredients)도 지역마다 다름
  • 각 Pizza 클래스마다 재료 생성 코드 중복

Abstract Factory 해결책:

  • "관련된 객체들의 제품군(family)을 생성하는 인터페이스 제공"
  • 구성(composition)과 위임(delegation) 사용

Abstract Factory Interface

public interface PizzaIngredientFactory {
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
}

왜 Interface인가?

  • 다양한 지역별 Factory 구현 가능
  • 제품군을 생성하는 규격만 정의
  • Factory 객체를 주입받아 사용 (DI)

Concrete Factory

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    public Dough createDough() {
        return new ThinCrustDough();  // NY 스타일
    }
    
    public Sauce createSauce() {
        return new MarinaraSauce();   // NY 스타일
    }
    
    public Cheese createCheese() {
        return new ReggianoCheese();  // NY 스타일
    }
    
    public Veggies[] createVeggies() {
        return new Veggies[] { 
            new Garlic(), new Onion(), new Mushroom() 
        };
    }
}

7. Abstract Factory 예시 코드

Step 1: Product 클래스 수정

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Cheese cheese;
    
    abstract void prepare();  // 재료 준비는 서브클래스가
    
    void bake() { /* ... */ }
    void cut() { /* ... */ }
    void box() { /* ... */ }
}

Step 2: Concrete Product (Factory 사용)

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;
    
    public CheesePizza(PizzaIngredientFactory factory) {
        this.ingredientFactory = factory;  // Factory 주입
    }
    
    void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();    // Factory에 위임
        sauce = ingredientFactory.createSauce();    // Factory에 위임
        cheese = ingredientFactory.createCheese();  // Factory에 위임
    }
}

Step 3: Factory Method + Abstract Factory 결합

public class NYPizzaStore extends PizzaStore {
    protected Pizza createPizza(String type) {
        Pizza pizza = null;
        // 1. Ingredient Factory 생성
        PizzaIngredientFactory factory = new NYPizzaIngredientFactory();
        
        // 2. Pizza 생성하면서 Factory 주입
        if (type.equals("cheese")) {
            pizza = new CheesePizza(factory);
            pizza.setName("NY Style Cheese Pizza");
        }
        return pizza;
    }
}

Step 4: 실행 흐름

// 1. Store 생성
PizzaStore nyStore = new NYPizzaStore();

// 2. 주문
Pizza pizza = nyStore.orderPizza("cheese");

// 3. orderPizza() 내부:
//    - createPizza("cheese") 호출
//    - NYPizzaIngredientFactory 생성
//    - CheesePizza(factory) 생성
//    - pizza.prepare() → Factory로 재료 생성
//    - bake(), cut(), box()

8. Factory Method VS Abstract Factory

구분Factory MethodAbstract Factory
메커니즘상속 (inheritance)구성 + 위임 (composition)
생성 대상단일 ProductProduct 제품군 (family)
확장성새 Creator 추가 쉬움새 Product 추가 어려움
관계"is-a" (Creator 상속)"has-a" (Factory 포함)
결정 시점컴파일 타임 (서브클래스 선택)런타임 (Factory 객체 전달)

9. Dependency Inversion Principle (DIP)

원칙

"추상화에 의존하라. 구체적인 클래스에 의존하지 마라"

// ❌ 나쁜 예: 구체 클래스에 직접 의존
public class DependentPizzaStore {
    public Pizza createPizza(String style, String type) {
        if (style.equals("NY")) {
            if (type.equals("cheese"))
                return new NYStyleCheesePizza();  // 구체 클래스
        }
    }
}
// ✅ 좋은 예: 추상화에 의존
public abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);  // 추상 메서드
        // ...
    }
    
    protected abstract Pizza createPizza(String type);
}

Factory Pattern이 DIP를 따르는 방법:

  • 고수준(PizzaStore) → 저수준(구체 Pizza) 의존 X
  • 둘 다 추상화(Pizza interface)에 의존

10. 핵심 정리

Factory Method Pattern

정의: 객체 생성 인터페이스를 정의하되, 서브클래스가 어떤 클래스를 인스턴스화할지 결정하게 함

언제 사용?

  • ✅ 클래스가 생성할 객체의 타입을 미리 알 수 없을 때
  • ✅ 서브클래스가 생성할 객체를 지정하고 싶을 때
  • ✅ 객체 생성을 서브클래스에 위임하고 싶을 때

Abstract Factory Pattern

정의: 관련되거나 의존적인 객체들의 제품군을 구체 클래스 지정 없이 생성하는 인터페이스 제공

언제 사용?

  • 제품군(family)을 함께 사용해야 할 때
  • ✅ 여러 제품군 중 하나를 선택해야 할 때
  • ✅ 제품 구현을 감추고 인터페이스만 노출하고 싶을 때

장단점 비교

Factory MethodAbstract Factory
장점• 확장 쉬움 (새 Creator 추가)
• 단순함
• 제품 일관성 보장
• 구체 클래스 격리
• 제품군 교체 쉬움
단점• 클래스 증가• 새 제품 타입 추가 어려움

실전 활용

// Factory Method: 단일 객체 생성
abstract class DocumentCreator {
    abstract Document createDocument();
}

// Abstract Factory: 관련 객체 제품군 생성
interface UIFactory {
    Button createButton();
    TextField createTextField();
    ScrollBar createScrollBar();
}
profile
다들 응원합니다.

0개의 댓글