코딩하는 털보

디자인 패턴 4. 팩토리 패턴 1/2 본문

Book/헤드 퍼스트 디자인 패턴

디자인 패턴 4. 팩토리 패턴 1/2

이정인 2021. 8. 31. 18:58

디자인 패턴 4. 팩토리 패턴

이번 장의 예시는 PizzaStore였다. 여러 종류의 피자 중에서 한 가지 타입의 피자 객체를 생성하고 가공하여 반환하는 메서드 orderPizza. (재미있는건 클린 코드에서도 피자를 예로 들었던 빌더 패턴이 있었다.)
String 아규먼트 type으로 피자 종류를 선택하고 인스턴스를 생성한다.

public class PizzaStore {

    Pizza orderPizza(String type) {
        Pizza pizza;

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

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

피자 객체가 만들어 진 후에 동작하는 준비 과정은 당연히 해야하는 일이기 때문에 바뀌지 않는다. 다형성에 의해 어떤 피자 클래스의 메서드가 호출될지 알아서 정해진다.
문제는 피자 종류가 선택되고 객체를 생성하는 if문 부분이다. 이 코드는 피자 종류가 더 많아지거나 또는 제거되는 등 변경에 의해 바뀔 수 있는 부분이다. 즉, OCP의 "변경에 대해 닫혀있어야 한다"를 위반한다.
디자인 원칙에 따라 달라지는 부분을 달라지지 않는 부분으로부터 분리시켜본다.
객체를 생성하는 부분을 처리할 새로운 클래스를 SimplePizzaFactory라고 만들었다. 객체 생성만을 처리하는 클래스를 팩토리라고 부른다고 한다.

public class SimplePizzaFactory {

    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 {
            throw new NoSuchElementException();
        }
        return pizza;
    }
}

PizzaStore에서 SimplePizzaFactory의 피자 객체 생성 기능을 사용할 수 있도록 컴포지션을 활용했다.
컴포지션을 통해서 SimplePizzaFactory에게 피자 객체를 선택하고 생성하는 작업을 인가했다.

public class PizzaStore {
    SimplePizzaFactory factory;

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

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

    //...
}

createPizza 메서드를 static으로 바꿔서(정적 팩토리) 팩토리 인스턴스 생성을 생략해도 된다. 하지만 이러면 팩토리 클래스의 서브클래스를 만들어서 객체 생성 메서드의 행동을 변경시킬 수 없다는 단점이 있다.

원래 컴포지션이 그러하듯 여기서도 다른 종류의 피자를 생성하는 다른 factory로 동적으로 변경할 수 있을것이다.
다른 factory는 여기서 예를들면 지역별 특색있는 피자스타일에 따른 factory와 같다.

        NYPizzaFactory nyFactory = new NYPizzaFactory();
        PizzaStore nyStore = new PizzaStore(nyFactory);
        nyStore.orderPizza("pepperoni");

여기까지는 심플 팩토리에 대한 이야기였다. 심플 팩토리는 디자인 패턴은 아니라고 한다.


다만 위의 예시는 지역별로 동일한 준비 동작을 가정한다. 만약, 지역별로 다른 피자 모양을 갖거나 다른 커팅 방법 또는 굽기 방법을 가진다면 어떻게 해야 할까?
해결책은 PizzaStore를 추상화하고 피자 객체 생성작업을 팩토리로부터 다시 가져오는 것이다. 각각의 지역 Store에서는 PizzaStore를 상속받으며 피자 객체 생성 작업을 각자 구현한다.

public abstract class PizzaStore {

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

        return pizza;
    }

    abstract Pizza createPizza(String type);
}

하위 클래스가 어떤것인지에 따라 서로 다른 Pizza객체를 생성한다. 즉, 만들어지는 피자의 종류를 해당 서브클래스에서 결정한다고 볼 수 있다.

public class NYPizzaStore extends PizzaStore{
    @Override
    Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            pizza = new NYStyleCheesePizza();
        } else if ( type.equals("pepperoni")) {
            pizza = new NYStylePepperoniPizza();
        } else if ( type.equals("clam")) {
            pizza = new NYStyleClamPizza();
        } else {
            throw new NoSuchElementException();
        }
        return pizza;
    }
}

하위 클래스에서 피자 객체 생성을 구현하는 것을 보면 마치 팩토리와 유사하게 보인다. createPizza 메서드는 이제 팩토리 메서드가 되었다.


팩토리 메서드 패턴
모든 팩토리 패턴에서는 객체 생성을 캡슐화한다. 팩토리 메서드 패턴에서는 서브클래스에서 어떤 클래스를 만들지를 결정하게 함으로써 객체 생성을 캡슐화 한다.

추상 생산자 : PizzaStore, 추상 생산자에서는 절대로 어떤 구상 제품 클래스가 만들어질지 미리 알 수 없다.
구상 생산자 : NYPizzaStore, ChicagoPizzaStore, 구상 생산자에서는 팩터리 메서드를 구현하고 제품을 선택하고 만들 수 있다. 사용하는 콘크리트 생산자에 따라서 생산되는 제품 객체 인스턴스가 결정된다.
팩터리 메서드 : createPizza(), 제품을 생산한다.
제품 : Pizza, 팩터리 메서드로부터 생산되는 클래스. 추상화 되어있어야 제품을 사용할 생산자 클래스에서 인터페이스에 대한 레퍼런스를 써서 객체를 참조할 수 있다.

책에서는 제품에 관한 지식을 생산자 안에 캡슐화하는 것에 팩토리 메서드가 핵심적인 역할을 맡고 있다고 말하고있다.

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

Comments