관리 메뉴

SW

[개발서적] 헤드퍼스트 디자인 패턴 Ch4. 팩토리(Factory) 패턴 본문

개발서적/헤드퍼스트 디자인패턴

[개발서적] 헤드퍼스트 디자인 패턴 Ch4. 팩토리(Factory) 패턴

SWKo 2024. 7. 28. 18:47

1. 최첨단 피자 코드 만들기

피자 종류를 고르고 그에 맞는 피자를 만드는 코드를 작성해야 합니다.

 

초기 코드

public class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

 

위 코드의 문제점은 인스턴스를 만드는 구상 클래스(new ~~)를 선택하는 부분입니다.

 

  • 확장성 부족: 새로운 피자 종류를 추가하거나 기존 피자 종류를 제거하려면 PizzaStore 클래스의 orderPizza 메서드를 수정해야 합니다.
  • 유지보수 어려움: 피자 종류가 많아질수록 orderPizza 메서드의 조건문이 길어지고 복잡해집니다.

2. 객체 생성 부분 캡슐화하기

객체 생성 부분을 orderPizza() 메소드에서 뽑아내야 합니다.

새로 만들 객체를 팩토리라고 부르겠습니다.

객체 생성을 처리하는 클래스를 팩토리(Factory)라고 부릅니다.

일단 SimplePizzaFactory를 만들고 나면 orderPizza() 메소드는 새로 만든 객체의 클라이언트가 됩니다.

피자가 필요할 때마다 피자 공장에 피자 하나 만들어 달라고 부탁한다고 생각하면 됩니다.

더 이상 orderPizza() 메소드에서 어떤 피자를 만들지 고민하지 않아도 됩니다.

 

public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }
        
        return pizza;
    }
}

 

3. 클라이언트 코드 수정하기

// PizzaStore 클래스
public class PizzaStore {
    SimplePizzaFactory factory;

    // 생성자: SimplePizzaFactory 객체를 전달받아 초기화
    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    // 피자를 주문하는 메소드
    public Pizza orderPizza(String type) {
        Pizza pizza;

        // SimplePizzaFactory를 사용해 피자 객체 생성
        pizza = factory.createPizza(type);

        // 피자 준비, 굽기, 자르기, 포장 과정 수행
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

 

4.  '간단한 팩토리'의 정의

간단한 팩토리(Simple Factory)는 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝습니다.

 

 

public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

	// 피자를 만드는 코드가 PizzaStore와 직접 연결되어 있어서, 유연성이 없음(조리 방식이 일관됨)
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}
public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        }

        return pizza;
    }
}
public class CheesePizza extends Pizza {
    public CheesePizza() {
        name = "Cheese Pizza";
        dough = "Regular Crust";
        sauce = "Marinara Pizza Sauce";
    }
}
public abstract class Pizza {
    String name;
    String dough;
    String sauce;

    void prepare() {
        System.out.println("Preparing " + name);
    }

    void bake() {
        System.out.println("Baking " + name);
    }

    void cut() {
        System.out.println("Cutting " + name);
    }

    void box() {
        System.out.println("Boxing " + name);
    }

    public String getName() {
        return name;
    }
}
public class PizzaTestDrive {
    public static void main(String[] args) {
        SimplePizzaFactory factory = new SimplePizzaFactory();
        PizzaStore store = new PizzaStore(factory);

        Pizza pizza = store.orderPizza("cheese");
        System.out.println("We ordered a " + pizza.getName() + "\n");

        pizza = store.orderPizza("clam");
        System.out.println("We ordered a " + pizza.getName() + "\n");
    }
}

 

5. 다양한 팩토리 만들기

피자 가게가 잘되니, 다른 지점을 열고 싶어졌습니다.

각 지역의 특성을 반영항 다양한 스타일의 피자(뉴욕, 시카고, ..)를 만들어야 합니다.

SimplePizzaFactory 대신, NYPizzaFactory, ChicagoFactory, ... 등을 만듭니다. 

 

public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaFactory nyFactory = new NYPizzaFactory();
        PizzaStore nyStore = new PizzaStore(nyFactory);

        Pizza pizza = nyStore.orderPizza("veggie");
        System.out.println("We ordered a " + pizza.getName() + "\n");

        PizzaFactory chicagoFactory = new ChicagoPizzaFactory();
        PizzaStore chicagoStore = new PizzaStore(chicagoFactory);

        pizza = chicagoStore.orderPizza("veggie");
        System.out.println("We ordered a " + pizza.getName() + "\n");
    }
}

 

'간단한 팩토리' 방식은 지점마다 굽는 방식이나 자르는 방식이 달라지면 문제를 일으킵니다.

따라서 유연성을 유지하면서도 피자 가게와 피자 제작 과정을 일관되게 관리할 수 있는 새로운 접근법이 필요합니다.

피자 가게에서 일관된 품질의 피자를 만들기 위해 피자 제작 과정을 하나로 묶는 프레임워크가 필요합니다. 

 

6. 피자 가게 프레임워크 만들기

피자를 만드는 일 자체는 PizzaStore 클래스에 진행하면서도 지점의 스타일을 살릴 방법이 있습니다.

createPizza() 메소드를 추상 메소드로 선언하고, 지역별 스타일에 맞게 PizzaStore의 서브클래스를 만듭니다.

public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    // 팩토리 메서드
    protected abstract Pizza createPizza(String type);
}

 

이제 각 지점에 맞는 서브클래스를 만들어야 합니다.

피자의 스타일은 각 서브클래스에서 결정합니다.

 

7. 서브클래스가 결정하는 것 알아보기

pizzaStore의 orderPizza() 메소드에 이미 주문 시스템이 잘 갖춰져 있습니다.

각 지점마다 달라질 수 있는 것은 피자 스타일뿐입니다.

PizzaStore의 서브클래스에서 createPizza() 메소드를 구현하겠습니다.

 

orderPizza() 메소드는 추상 클래스 PizzaStore 클래스에 정의되어 있습니다.

orderPizza()는 실제로 어떤 구상 클래스에서 작업이 처리되고 있는지 알 수 없습니다.

PizzaStore와 Pizza는 서로 완전히 분리되어 있습니다.

 

피자의 종류는 어떤 서브클래스를 선택했느냐에 따라 결정됩니다.

 

8. 피자 스타일 서브클래스 만들기

각 지점에는 PizzaStore의 서브클래스를 만들고 지역별 특성에 맞게 createPizza() 메소드만 구현하면 PizzaStore의 기능을 그대로 사용할 수 있습니다.

public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type); // 팩토리 메소드 호출
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 팩토리 메소드
    protected abstract Pizza createPizza(String type);
}

// 객체를 생성하는 코드를 캡슐화함
public class NYPizzaStore extends PizzaStore {

    @Override
    protected Pizza createPizza(String item) {
        if (item.equals("cheese")) {
            return new NYStyleCheesePizza();
        } else if (item.equals("veggie")) {
            return new NYStyleVeggiePizza();
        } else if (item.equals("clam")) {
            return new NYStyleClamPizza();
        } else if (item.equals("pepperoni")) {
            return new NYStylePepperoniPizza();
        } else {
            return null;
        }
    }
}

public abstract class Pizza {
    String name;
    String dough;
    String sauce;

    void prepare() {
        System.out.println("Preparing " + name);
    }

    void bake() {
        System.out.println("Baking " + name);
    }

    void cut() {
        System.out.println("Cutting " + name);
    }

    void box() {
        System.out.println("Boxing " + name);
    }

    public String getName() {
        return name;
    }
}

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

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

public class NYStyleClamPizza extends Pizza {
    public NYStyleClamPizza() {
        name = "NY Style Clam Pizza";
        dough = "Thin Crust Dough";
        sauce = "White Garlic Sauce";
    }
}

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

// 메인 메소드에서 피자 주문 과정 시뮬레이션
public class PizzaTestDrive {
    public static void main(String[] args) {
        PizzaStore nyPizzaStore = new NYPizzaStore();
        Pizza pizza = nyPizzaStore.orderPizza("cheese");
        System.out.println("에단이 주문한: " + pizza.getName());
    }
}

 

9. 팩토리 메소드 패턴 살펴보기

모든 팩토리 패턴은 객체 생성을 캡슐화합니다.

팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화합니다.

 

구상 생산자(NYPizzaStore, ChicagoPizzaStore, ...)별로 수많은 제품을 만들 수 있습니다.

위 그림을 아래처럼 병렬 계층 구조로 볼 수 있습니다.

10. 팩토리 메소드 패턴의 정의

팩토리 메소드 패턴에서는 객체를 생성할 때 필요한 인터페이스(createPizza)를 만듭니다.

어떤 클래스의 인스턴스를 만들지는 서브클래스(NYPizzaStore, ChicagoPizzaStore, ...)에서 결정합니다.

팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 됩니다.

사용하는 서브클래스에 따라 생산되는 객체 인스턴스가 결정됩니다.

 

팩토리 장점은 우선 객체 생성 코드를 전부 한 객체 또는 메소드에 넣으면 코드에서 중복되는 내용을 제거할 수 있고, 나중에 관리할 때도 한 군데에만 신경을 쓰면 됩니다.

그리고 객체 인스턴스를 만들 때 인터페이스(createPizza)만 있으면 됩니다.

 

11. 객체 의존성 살펴보기

객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 합니다.

아래 코드는 심하게 의존적인 PizzaStore 코드입니다.

모든 피자 객체를 팩토리에 맡겨서 만들지 않고 PizzaStore 클래스 내에서 직접 만들었습니다.

새로운 피자 종류나 스타일을 추가할 때마다 이 클래스의 조건문을 수정해야 합니다.

public class DependentPizzaStore {

    public Pizza createPizza(String style, String type) {
        Pizza pizza = null;
        
        if (style.equals("NY")) {
            if (type.equals("cheese")) {
                pizza = new NYStyleCheesePizza();
            } else if (type.equals("veggie")) {
                pizza = new NYStyleVeggiePizza();
            } else if (type.equals("clam")) {
                pizza = new NYStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                pizza = new NYStylePepperoniPizza();
            }
        } else if (style.equals("Chicago")) {
            if (type.equals("cheese")) {
                pizza = new ChicagoStyleCheesePizza();
            } else if (type.equals("veggie")) {
                pizza = new ChicagoStyleVeggiePizza();
            } else if (type.equals("clam")) {
                pizza = new ChicagoStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                pizza = new ChicagoStylePepperoniPizza();
            }
        } else {
            System.out.println("오류: 알 수 없는 피자 종류");
            return null;
        }

        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }

        return pizza;
    }
}

12. 의존성 뒤집기 원칙

구상 클래스 의존성을 줄이면 좋습니다. 

그것이 바로 의존성 뒤집기 원칙(Dependency Inversion Principle) 입니다.

디자인 원칙
추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.

 

고수준 구성 요소(PizzaStore)가 저수준 구성 요소(Pizza 클래스)에 의존하면 안되며, 항상 추상화에 의존하게 만들어야 합니다.

DependentPizzaStore의 가장 큰 문제점은 PizzaStore가 모든 종류의 피자에 의존한다는 점입니다.

orderPizza() 메소드에서 구상 형식의 인스턴스(new ~~Pizza())를 직접 만들기 때문입니다.

인스턴스 만드는 부분을 뽑아낼 생각을 해야합니다. -> 팩토리 메소드 패턴 사용

팩토리 메소드 패턴을 적용하면 고수준 구성 요소인 PizzaStore와 저수준 구성 요소인 피자 객체 모두가 추상 클래스인 Pizza에 의존하는 걸 알 수 있습니다.

 

의존성 뒤집기에서 '뒤집기(inversion)'라는 말이 있는 이유는 객체 지향 디자인을 할 때 일반적으로 생각하는 방법과는 반대로 뒤집어서 생각해야하기 때문입니다.

이전 다이어그램에서 의존성이 위에서 아래로 내려가기만 했지만, 현재 다이어그램에서는 반대로 뒤집어져 있습니다. 고수준 모듈과 저수준 모듈이 하나의 추상 클래스에 의존하게 됩니다.

 

13. 의존성 뒤집기 원칙을 지키는 방법

  • 변수에 구상 클래스의 레퍼런스를 저장하지 맙시다. new 연산자를 사용하면 구상 클래스의 레퍼런스를 사용하게 됩니다. new 를 최대한 자제하자!
  • 구상 클래스에서 유도된 클래스를 만들지 맙시다. 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 됩니다.
public class NYStyleCheesePizza extends CheesePizza {
  // 이런 식으로 구상 클래스(CheesePizza)에서 유도된 클래스(NYStyleCheesePizza)를 만들지 맙시다.
}
  • 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 맙시다. Pizza 클래스는 prepare, bake, cut, box 메소드를 이미 구현하고 있습니다. 이 메소드들은 오버라이드하지 말아야 피자 종류에 관계없이 동일하게 동작할 수 있습니다. 

위 가이드라인을 항상 지켜야 하는 규칙은 아니고, 지향하는 바를 알려 줄 뿐입니다.

하지만 가이드라인을 습득한 상태에서 디자인한다면 원칙이 안지켜진 부분도 알 수 있고, 합리적인 이유로 불가피한 상황에서만 예외를 둘 수 있습니다.

 

14. 원재료 팩토리 만들기

PizzaStore은 어느 정도 모양새를 갖췄습니다.

그러나, 몇몇 지점에서 더 싼 재료를 사용해서 마진을 높이고 있습니다. 조치가 필요합니다.

어떻게 하면 지점에서 좋은 재료를 사용할 수 있도록 관리할 수 있을까요?

뉴욕 원재료 팩토리를 만들어보겠습니다.

// PizzaIngredientFactory은 추상 팩토리입니다.
public interface PizzaIngredientFactory {
    Dough createDough();
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
}

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies[] veggies = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FreshClams();
    }
}

 

15. Pizza 클래스 변경하기

공장도 다 만들었으니 이제 생산할 준비가 끝났습니다.

Pizza 클래스가 팩토리에서 생산한 원재료만 사용하도록 코드를 수정해보겠습니다. (prepare에 주목)

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    // 재료 준비를 위한 추상 메서드
    abstract void prepare();

    // 피자를 굽는 메서드
    void bake() {
        System.out.println("175도에서 25분 간 굽기");
    }

    // 피자를 자르는 메서드
    void cut() {
        System.out.println("피자를 사선으로 자르기");
    }

    // 피자를 상자에 담는 메서드
    void box() {
        System.out.println("상자에 피자 담기");
    }

    // 피자 이름 설정 메서드
    void setName(String name) {
        this.name = name;
    }

    // 피자 이름 반환 메서드
    String getName() {
        return name;
    }

    // 피자 이름 출력 메서드
    public String toString() {
        return "피자 이름: " + name;
    }
}

 

Pizza 추상 클래스 준비가 끝났으니, 뉴욕 스타일 피자와 시카고 스타일 피자를 만들어보겠습니다.

피자마다 클래스를 지역별로 따로 만들 필요가 없습니다.

지역별로 다른 점은 원재료 팩토리에서 처리합니다.

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

// 각 피자 클래스는 생성자로부터 팩토리를 전달받고 그 팩토리를 인스턴스 변수에 저장합니다.
    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

 

올바른 재료 공장을 사용하는 NYPizzaStore를 보겠습니다.

public abstract class PizzaStore {
    protected abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("--- Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if (item.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }
        // 다른 피자 타입도 추가 가능
        return pizza;
    }
}

 

16. 추상 팩토리 패턴의 정의 

추상 팩토리 패턴(Abstract Factory Pattern)은 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공합니다. 구상 클래스는 서브클래스에서 만듭니다.

 

추상 팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스로 일련의 제품(재료)을 공급받을 수 있습니다. 

이때, 실제로 어떤 제품이 생산되는지 전혀 알 필요 없습니다. 

따라서 클라이언트와 팩토리에서 생성되는 제품을 분리할 수 있습니다.

 

 

17. 팩토리 메소드 패턴과 추상 팩토리 패턴

 

팩토리 메소드 패턴

팩토리 메소드 패턴은 객체를 생성하기 위한 인터페이스를 정의하고, 실제 객체 생성은 서브클래스에서 담당하도록 하는 패턴입니다.

이는 객체 생성의 책임을 서브클래스로 위임함으로써 상위 클래스와 하위 클래스 간의 결합도를 낮춥니다.

 

구조

  • 상위 클래스 (Creator): 객체를 생성하는 팩토리 메소드를 정의합니다.
  • 하위 클래스 (Concrete Creator): 팩토리 메소드를 구체적으로 구현하여 특정 객체를 생성합니다.
// 상위 클래스
public abstract class PizzaStore {
    protected abstract Pizza createPizza(String type);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

// 하위 클래스
public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new NYStyleCheesePizza();
        } else if (type.equals("veggie")) {
            return new NYStyleVeggiePizza();
        }
        // 추가적인 피자 타입들
        return null;
    }
}

 

 

추상 팩토리 패턴

추상 팩토리 패턴은 관련된 객체들의 군을 생성하기 위한 인터페이스를 제공하며, 구체적인 클래스는 이 인터페이스를 구현하여 객체 군을 생성합니다. 이는 객체들이 서로 관련되어 있거나 의존적인 경우에 유용합니다.

 

구조

  • 추상 팩토리 (Abstract Factory): 객체 군을 생성하기 위한 인터페이스를 정의합니다.
  • 구체 팩토리 (Concrete Factory): 이 인터페이스를 구현하여 특정 객체 군을 생성합니다.
  • 추상 제품 (Abstract Product): 생성될 객체의 인터페이스를 정의합니다.
  • 구체 제품 (Concrete Product): 이 인터페이스를 구현하여 구체적인 객체를 정의합니다.
// 추상 팩토리
public interface PizzaIngredientFactory {
    Dough createDough();
    Sauce createSauce();
    Cheese createCheese();
    Veggies[] createVeggies();
    Pepperoni createPepperoni();
    Clams createClam();
}

// 구체 팩토리
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThinCrustDough();
    }

    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }

    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

    @Override
    public Veggies[] createVeggies() {
        Veggies[] veggies = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
        return veggies;
    }

    @Override
    public Pepperoni createPepperoni() {
        return new SlicedPepperoni();
    }

    @Override
    public Clams createClam() {
        return new FreshClams();
    }
}

// 추상 제품
public interface Dough {}
public interface Sauce {}
public interface Cheese {}
public interface Veggies {}
public interface Pepperoni {}
public interface Clams {}

// 구체 제품
public class ThinCrustDough implements Dough {}
public class MarinaraSauce implements Sauce {}
public class ReggianoCheese implements Cheese {}
public class Garlic implements Veggies {}
public class Onion implements Veggies {}
public class Mushroom implements Veggies {}
public class RedPepper implements Veggies {}
public class SlicedPepperoni implements Pepperoni {}
public class FreshClams implements Clams {}

// 피자 클래스
public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies[] veggies;
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    abstract void prepare();

    void bake() {
        System.out.println("Baking " + name);
    }

    void cut() {
        System.out.println("Cutting " + name);
    }

    void box() {
        System.out.println("Boxing " + name);
    }

    void setName(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    public String toString() {
        return "Pizza name: " + name;
    }
}

// 구체 피자 클래스
public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }

    @Override
    void prepare() {
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

// 피자 스토어 클래스
public abstract class PizzaStore {
    protected abstract Pizza createPizza(String item);

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        System.out.println("--- Making a " + pizza.getName() + " ---");
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

public class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();

        if (item.equals("cheese")) {
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza");
        }
        // 다른 피자 타입도 추가 가능
        return pizza;
    }
}

 

요약

  • 팩토리 메소드 패턴: 객체 생성의 책임을 서브클래스로 위임하여, 객체 생성 로직을 캡슐화합니다. 하나의 객체를 생성하는 데 사용됩니다.
  • 추상 팩토리 패턴: 관련된 객체들의 군을 생성하기 위한 인터페이스를 제공하여, 객체 생성의 책임을 구체 팩토리 클래스로 위임합니다. 여러 객체를 생성하는 데 사용됩니다.

두 패턴 모두 객체 생성 로직을 캡슐화하여 코드의 유연성과 확장성을 높이는 데 도움을 줍니다.

그러나 팩토리 메소드 패턴은 단일 객체 생성에, 추상 팩토리 패턴은 관련 객체들의 군을 생성하는 데 더 적합합니다.

 


 

https://www.youtube.com/watch?v=eSLrZbPHgoI

 

Comments