반응형

팩토리 메서드 패턴(Factory Method Pattern)

 

https://en.wikipedia.org/wiki/Factory_method_pattern

팩토리 메서드 패턴은 객체 생성을 처리하기 위한 디자인 패턴이다.

팩토리 메서드 패턴을 사용하면 Creator Product 분리함으로써, 객체 생성과 사용을 독립적으로 관리할  있다. 

 

Creator

Creator는 객체 생성을 위한 인터페이스 또는 추상 클래스를 정의한다.

Creator 클래스는 구체적인 제품(Product) 객체를 반환하는 역할을 하는 팩토리 메서드를 선언한다.

팩토리 메서드는 Creator 클래스에서 정의되지만 실제로 구현하지 않고 서브 클래스인 ConcreteCreator(Creator1)로 객체 생성에 대한 역할을 위임하여 사용한다.

 

Product

Product는 Creator에서 생성될 객체의 인터페이스 또는 추상 클래스를 정의한다.

구체적인 Product 클래스는 서브 클래스로 확장하여 사용하게 된다.

클라이언트 코드에서는 구체적인 Product 클래스에 대한 정보를 필요 없이, Creator 통해 Product 사용할 있다.

 

 

 

 


피자 가게 : 문제점

 

public class PizzaStore {

    void prepareToBoxing(Pizza pizza) {
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        prepareToBoxing(pizza);
        return pizza;
    }

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

피자 가게 운영을 위해서 다음의 코드를 작성하였다고 해보자. 
orderPizza()에서 피자 주문이 들어오면, createPizza()를 통해 NYStyle(뉴욕풍 피자)의 주문 종류 따른 피자 인스턴스를 생성하고, 포장과정을 거쳐 피자를 반환하는 기능을 하고 있다.

그런데 만약 CicagoStyle(시카고풍 피자)가 추가되어 확장을 하려고 한다면 아래와 같은 문제점이 발생한다.

 

개방-폐쇄 원칙 위반: 새로운 피자 유형을 추가하려면 PizzaStore 클래스의 코드를 수정해야 한다. 이는 "개방-폐쇄 원칙"을 위반하며, 기존 코드의 변경이 필요하다는 것을 의미한다. 이는 시간이 지남에 따라 코드가 복잡해지고 유지보수하기 어려워 질 수 있다.

단일 책임 원칙 위반: 피자 가게는 주문을 받으면 피자를 만들고, 포장하여 피자를 제공하는 역할을 하고 있다. 그런데, createPizza()를 보면 주문한 피자의 종류를 식별하여 서로 다른 객체를 생성하는 역할까지 맡고 있다. 

상기의 코드를 보면, 객체 생성을 하고 있는 createPizza()의 내용을 제외하고는 피자의 종류가 추가되더라도 변하지 않는 내용들이다. 따라서 객체 생성을 담당하는 부분을 분리하여 서브클래스로 위임하여 캡슐화하는 것에서 나온 아이디어가 팩토리 메서드 패턴이다.

 

 


팩토리 메서드 패턴  : Product 작성 

 

public abstract class Pizza {
    String name;
    String dough;

    public void prepare() {
        System.out.println("preparing : " + name);
        System.out.println("prepare dough : " + dough);
    }

    public void bake() {
        System.out.println("baking");
    }

    public void box() {
        System.out.println("boxing");
    }

    public void cut() {
        System.out.println("cutting");
    }

    public String getName() {
        return name;
    }
}
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NYStyleCheesePizza";
        dough = "NYStyleCheesePizza Dough";

    }
}

각각 Product와 Product의 서브클래스를 담당하는 NYStyleCheesePizza이다. 

서브 클래스는 지금은 1개만 작성하였지만, 총 6개의 다른 유형의 서브 클래스를 작성해 주었다.

이제 클라이언트 코드에서는 구체적인 Product 서브 클래스들에 대한 정보를  필요 없다. 단지 Pizza를 사용하면 될 뿐이다.

 

 

 


팩토리 메서드 패턴  : Creator 작성 

 

//Creator
public abstract class PizzaStore {

    void prepareToBoxing(Pizza pizza) {
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        prepareToBoxing(pizza);
        return pizza;
    }

    //구현을 서브 클래스에 위임
    abstract Pizza createPizza(String type);
}

위에서 언급하였듯이 PizzaStore을 추상 클래스로 작성하였으며, 객체 인스턴스를 생성하는 createPizza()를 서브 클래스로 위임할 것이기 때문에 추상 메서드로서 작성해주었다. 

즉 createPizza()가 다이어그램 상의 Factory Method에 해당한다.

 

 

 

//ConcreteCreator
public class NYPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String item) {
        if ("cheese".equals(item)) {
            return new NYStyleCheesePizza();
        } else if ("veggie".equals(item)) {
            return new NYStyleVeggiePizza();
        } else if ("clam".equals(item)) {
            return new NYStyleClamPizza();
        } else {
            return null;
        }
    }
}

ConcreteCreator에 해당하는 뉴욕풍 PizzaStore이다. 해당 클래스에서 위에서 위임받았던 객체 생성을 담당하는 부분을 작성하게 된다.

 

//ConcreteCreator
public class ChicagoPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String item) {
        if ("cheese".equals(item)) {
            return new ChicagoStyleCheesePizza();
        } else if ("veggie".equals(item)) {
            return new ChicagoStyleVeggiePizza();
        } else if ("clam".equals(item)) {
            return new ChicagoStyleClamPizza();
        } else {
            return null;
        }
    }
}

이제 시카고풍 피자가 추가된다면, 상기 코드와 같이 시카고 피자생성만을 담당하는 서브 클래스를 작성하여 사용할 수 있다. 이로서 더욱 각자의 역할에 충실할 수 있다.

 

 

 


팩토리 메서드 패턴  : Client 작성 

 

public class Main {
    public static void main(String[] args){
        PizzaStore nyStore = new NYPizzaStore();
        PizzaStore chicagoStore = new ChicagoPizzaStore();

        Pizza nyCheesePizza = nyStore.orderPizza("cheese"); //뉴욕 치즈피자
        Pizza nyClamPizza = nyStore.orderPizza("clam"); //뉴욕 조개피자

        Pizza chicagoCheesePizza = chicagoStore.orderPizza("cheese"); //시카고 치즈피자
        Pizza chicagoClamPizza = chicagoStore.orderPizza("clam"); //시카고 조개피자
    }
}

 

이제 클라이언트 코드에서는 피자를 주문하는 것에만 집중하면 된다. 

 

클라이언트 코드는 어떤 구체적인 피자 클래스가 사용되는지에 대한 내부적인 정보를 알 필요가 없다.

또한 새로운 피자 유형을 추가하거나 기존 유형을 변경하기 위해 클라이언트 코드를 수정할 필요가 없다.

 

 

 

 


추상 팩토리 메서드 패턴 (Abstract Factory Method Pattern)

 

한 발짝 더 나아가서 추상 팩토리 패턴이라는 것이 있다.

팩토리 메서드 패턴과 마찬가지로 객체 생성을 추상화하여 클라이언트 코드로부터 객체 생성 과정을 분리하여 클라이언트 코드는 구체적인 클래스에 직접 의존하지 않는다는 공통점이 있지만 아래와 같은 차이점이 있다.

 

팩토리 메서드 패턴

객체 생성을 서브 클래스로 미루는 것이 핵심이다. 팩토리 메서드 패턴은 한 종류의 객체, 즉 "한 개의 제품군"을 처리한다.

주로 단일 제품군을 생성하거나, 객체 생성에 대한 결정을 서브 클래스에 위임할 때 사용된다.

 

추상 팩토리 메서드 패턴

이 패턴은 관련된 객체의 집합을 생성하며, 서로 다른 객체 군을 생성하기 위한 인터페이스를 정의한다. 추상 팩토리 패턴은 "여러 제품군"을 처리한다.

여러 제품군을 생성하고, 이러한 제품군 간의 관련성을 유지하는 경우 또는  시스템에서 다양한 객체의 집합을 생성해야   사용된다.

 

AbstractFactory(추상 팩토리)와 ConcreteFactory(구체적 팩토리) 클래스를 포함한다.

ConcreteFactory에서는 여러 제품군을 생성하는 메서드가 정의되고, AbstractFactory에서 이러한 메서드를 선언한다.

 

즉 팩토리 메서드 패턴이 시카고 치즈 피자, 뉴욕 치즈 피자를 각각 객체를 만들어서 사용한다면,

추상 팩토리 메서드 패턴은 시카고 스타일, 뉴욕 스타일로 따로 피자 객체를 생성하지 않고 치즈 피자에 들어가는 구성 제품군들, 즉 어떤 야채, 소스, 도우의 종류에 대한 클래스를 정의하여 조립해서 시카고 스타일, 뉴욕 스타일로 만드는 것이 차이점이다.

 

 

 

 


추상 팩토리 패턴 : Creator 작성

 

public abstract class PizzaStore {

    void prepareToBoxing(Pizza pizza) {
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }

    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        prepareToBoxing(pizza);
        return pizza;
    }

    //구현을 서브 클래스에 위임
    abstract Pizza createPizza(String type);
}
public class NYPizzaStore extends PizzaStore {
    PizzaIngredientFactory ingredientFactory = new NyPizzaIngredientFactory();
    @Override
    Pizza createPizza(String item) {
        Pizza pizza = null;
        // 뉴욕피자 Factory 전달.

        if(item.equals("cheese")){
            pizza = new CheesePizza(ingredientFactory);
            pizza.setName("Cheese Pizza");
        }
        else if(item.equals("clam")){
            pizza = new ClamPizza(ingredientFactory);
            pizza.setName("Clam Pizza");
        }
        else if(item.equals("veggie")){
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("Veggie Pizza");
        }
        return pizza;
    }
}

팩토리 메서드 패턴에서의 Creator처럼 서브 클래스에서 다른 스타일의 피자를 생성하는 코드를 작성하였다.

다른 점은, Factory를 주입받는다는 점이며, AbstractProduct에 해당하는 CheesePizza 객체를 생성할 때, 팩토리 유형에 따라 서로 다른 스타일의 피자를 만들어 내는 역할을 하고 있다.

 

 

 


추상 팩토리 메서드 패턴 : Factory

 

public interface PizzaIngredientFactory {
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Clams createClam();
}
public class NyPizzaIngredientFactory implements PizzaIngredientFactory{
    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }

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

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

위의 코드는 Abstract Factory와 뉴욕 스타일의 피자를 생성하는 ConcreteFactory에 해당한다.

NyPizzaIngredientFactory는 뉴욕 스타일의 피자를 만들기 위한 재료들을 공급해주는 역할을 한다.

즉, Product의 클래스가 동일한 CheesePizza더라도, 팩토리의 종류에 따라서 뉴욕 스타일, 혹은 시카고 스타일 등의 피자로 탄생할 수 있는 것이다.

 

 


추상 팩토리 메서드 패턴 : Product

public abstract class Cheese {
}
public class ReggianoCheese extends Cheese{
    @Override
    public String toString() {
        return "ReggianoCheese";
    }
}
public abstract class Pizza {
    String name;
    Veggies veggies[];
    Cheese cheese;
    Clams clams;

    public abstract void prepare();

    public void bake() {
        System.out.println("baking");
    }

    public void box() {
        System.out.println("boxing");
    }

    public void cut() {
        System.out.println("cutting");
    }

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

    public String getName() {
        return name;
    }

}
public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;

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

    @Override
    public void prepare() {
        this.cheese = ingredientFactory.createCheese();
        System.out.println("Preparing : " + name);
        System.out.println("Preparing Cheese : " + cheese);
    }
}

 

Abstract Product와 Concrete Product에 해당하는 Pizza 클래스들이다.

Concrete Product에 해당하는 CheesePizza는 팩토리를 의존성을 주입받으며, 팩토리에 따라서 같은 클래스더라도 다른 유형의 치즈피자가 탄생할 수 있게 된다.

만약 Factory의 유형이 NyPizzaIngredientFactory라면, 치즈 피자에는 RaggianoCheese를 사용하게 될 것이고, 만약 다른 스타일의 피자를 만들고 싶어 CicagoPizzaIngredientFactory를 주입받는다면 다른 치즈를 사용한 CheesePizza가 탄생할 것이다.

 

 

 

 

 


 

public class Main {
    public static void main(String[] args){
        PizzaStore nyStore = new NYPizzaStore();

        Pizza nyCheesePizza = nyStore.orderPizza("cheese"); //뉴욕 치즈피자
        Pizza nyClamPizza = nyStore.orderPizza("clam"); //뉴욕 조개피자

    }
}

 

마찬가지로 클라이언트 코드에서는 피자를 주문하는 것에만 집중하면 된다.

 

클라이언트 코드는 어떤 구체적인 피자 클래스가 사용되는지에 대한 내부적인 정보를 알 필요가 없다.

또한 새로운 피자 유형을 추가하거나 기존 유형을 변경하기 위해 클라이언트 코드를 수정할 필요가 없다.

두 가지 팩토리 패턴 모두 코드 확장에 용이하며, 새로운 피자 유형을 추가되더라도, 기존 코드의 변경 대신 Factory나 Product의 서브 클래스의 추가로 유지보수를 용이하게 할 수 있을 것이다.

반응형

BELATED ARTICLES

more