Tech & Programming/Pattern & Design

팩토리 패턴(Factory Pattern) 이란?

소스코드 요리사 2019. 1. 16. 12:11

이번 시간에는 팩토리 패턴(Factory Pattern)에 대해서 알아보도록 하겠습니다.


원칙 : 바뀔 수 있는 부분을 찾아내서 바뀌지 않는 부분하고 분리시켜야 한다.


new를 사용하는 것은 구상클래스의 인스턴스를 만드는 것입니다. 구상클래스를 바탕으로 코딩을 하면 나중에 코드를 수정해야할 가능성이 높아지고, 유연성이 떨어지게 됩니다. 그 이유는 바로 변화에 약하기 때문입니다. 인터페이스에 맞춰서 코딩을 하면 시스템에서 일어날 수 있는 여러가지 변화를 이겨낼 수 있습니다.


이렇게 구상클래스의 인스턴스를 만드는 부분을 찾아서 분리/캡슐화 하기 위해서 바로 팩토리 패턴을 씁니다.

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다. 팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에 맡기는 겁니다.





위 팩토리 패턴의 기본 클래스 다이어그램과 아래  피자 예제코드를 통해 보시면 더 쉽게 이해 되실 것 같습니다.


아래 PizzaStore 클래스가 Creator에 해당하고, ChicagoPizzaStore 클래스가 ConcreateCreator 에 해당합니다.

서브클래스인 ChicagoPizzaStore, NyPizzaStore 에서 createPizza() 추상메소드를 구현합니다.

그리고, 어떤 제품의 서브클래스로 객체를 생성하느냐에 따라 생산되는 객체 인스턴스(Pizza 종류) 가 결정됩니다.

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

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

        return pizza;
    }

    abstract protected Pizza createPizza(String type);
}

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

public class Main {
    public static void main(String[] args) {
        PizzaStore nyPizzaStore = new NyPizzaStore();
        PizzaStore chicagoPizzaStore = new ChicagoPizzaStore();

        Pizza pizza = nyPizzaStore.orderPizza("cheese");
        System.out.println(pizza.getName() + "피자를 주문 했습니다.");
        System.out.println("===================");
        pizza = chicagoPizzaStore.orderPizza("pepperoni");
        System.out.println(pizza.getName() + "피자를 주문 했습니다.");
    }
}

위 예제 소스코드의 클래스 다이어그램입니다.


※ 위 클래스 다이어그램은 구루비의 위키(http://wiki.gurubee.net/pages/viewpage.action?pageId=1507401) 에서 발췌했습니다.


두번째 예제 입니다. 앞선 코드를 추상팩토리 패턴을 적용해 수정해볼 것 입니다.


의존성 뒤집기 윈칙 (Dependency Inversion Principle) : 추상화 된 것에 의존하도록 만들어라. 

구상클래스에 의존하도록 만들지 말고, 구상 클래스처럼 구체적인 것이 아닌 추상 클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드를 만들어야 합니다.


이 원칙을 지키는 데 도움이 될만한 가이드라인

  • 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 않습니다.
  • 구상클래스에서 유도된 클래스를 만들지 않습니다.
  • 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 않습니다.


추상팩토리를 도입하여 원재료 공장의 추가

추상팩토리 패턴은 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상클래스를 지정하지 않고도 생성할 수 있도록 합니다.

클라이언트에서는 인터페이스를 통해 제품을 공급 받을 수 있습니다.


이제 예제를 한번 볼까요?

각 지역마다 원재료 공장을 두고, 원재료들을 제공받아 피자를 만든다고 생각해봅시다.

아래 그림은 추상 팩토리의 일반적인 클래스다이어그램 입니다.


※ 위 클래스 다이어그램은 구루비의 위키(http://wiki.gurubee.net/pages/viewpage.action?pageId=1507401) 에서 발췌했습니다.



추상팩토리는 인터페이스를 이용하여 서로 연관된, 또는 의존한는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있습니다.


AbstractFactory 인터페이스에 해당하는 PizzaIngredientFactory 인터페이스입니다.

각 원재료 공장에 맞는 원재료를 생성하기 위한 메소가 정의되어 있습니다.


public interface PizzaIngredientFactory {
    public Dough createDough();
    public Souce createSouce();
    public Cheese createCheese();
}

ChicagoIngredientFactory 클래스는 ConcreateFactory 로 각 공장별로 어떤 원재료가 제공되는 지 구현되어 있습니다.

public class ChicagoIngredientFactory implements PizzaIngredientFactory {
    @Override
    public Dough createDough() {
        return new ThickCrustDough();
    }

    @Override
    public Souce createSouce() {
        return new HotSouce();
    }

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

이제 치즈 피자를 만들기 위해서는 원재료 공장 인터페이스를 넘겨주면 prepare() 메소드 쪽에 pizzaIngredientFactory 생성된 객체에 따라 Dough, Souce, Cheese 각각 준비 됩니다.
public class CheesePizza extends Pizza {
PizzaIngredientFactory pizzaIngredientFactory; public CheesePizza(PizzaIngredientFactory pizzaIngredientFactory) { this.pizzaIngredientFactory = pizzaIngredientFactory; } @Override void prepare() { dought = pizzaIngredientFactory.createDough(); sauce = pizzaIngredientFactory.createSouce(); cheese = pizzaIngredientFactory.createCheese(); toppings.add("불고기"); toppings.add("체다치즈"); System.out.println(dought.getName()); System.out.println(sauce.getName()); System.out.println(cheese.getName()); } }

아래 그림은 위 예제코드 클래스 다이어그램 으로 추상팩토리를 적용한 모습입니다.




팩토리 패턴은  객체생성을 캡슐화하고, 클라이언트와 구상 형식을 분리 시켜주는 역할을 합니다.
일련의 연관된 제품을 하나로 묶을 수 있으며,  객체 생성을 캡슐화해서 애플리케이션의 결합을 느슨하게 만들고, 특정구현에 덜 의존하게 만듭니다.


팩토리 메소드 패턴 VS 추상팩토리 패턴

팩토리 메소드 패턴:
상속을 활용한다. 객체 생성이 서브클래스에게 위임된다. 서브클래스에서는 팩토리 메소드를 구현하여 객체를 생산합니다.
클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야 할 때 유용하며, 어떤 구상 클래스를 필요로 하게 될지 알 수 없는 경우에도 유용합니다.

추상 팩토리 패턴:
추상 팩토리 패턴에서는 객체 구성을 활용하고, 객체 생성이 팩토리 인터페이스에서 선언한 메소드들에서 구현됩니다.
새로운 제품을 추가하려면 인터페이스를 바꿔야 합니다.
클라이언트에서 서로 연관된 일련의 제품들을 만들어야 할 때 유용합니다.


참조할 만한 사이트 : 구루비 헤드퍼스트 책자 정리 위키(http://wiki.gurubee.net/pages/viewpage.action?pageId=1507401)