4. Abstract Factory Method

2021. 9. 15. 13:08Computer Sciences/Design Patterns

문제

프랜차이즈를 운영하는 피자 가게가 있다. 그동안 본점에서는 분점에 지침을 내려주지 않았다. 그래서 각 분점에서는 마음대로 더 싼 원재료를 사용해서 품질이 떨어지는 피자를 만들어서 팔았다. 이는 브랜드에 타격이 올 수도 있기 때문에 조치를 취해야 한다.

원재료 품질 관리 방법

서울 피자 메뉴

  • 치즈 피자 - 토마토 소스, 모짜렐라 치즈, 파마산 치즈
  • 야채 피자 - 토마토 소스, 모짜렐라 치즈, 파마산 치즈, 시금치, 올리브
  • 조개 피자 - 토마토 소스, 모짜렐라 치즈, 파마산 치즈, 조개
  • 페퍼로니 피자 - 토마토 소스, 모짜렐라 치즈, 파마산 치즈, 시금치, 올리브, 페퍼로니

부산 피자 메뉴

  • 치즈 피자 - 마리나라 소스, 레지아노 치즈, 마늘
  • 야채 피자 - 미라나라 소스, 레지아노 치즈, 버섯, 양파, 고추
  • 조개 피자 - 마리나라 소스, 레지아노 치즈, 조개
  • 페퍼로니 피자 - 마리나라 소스, 레지아노 치즈, 버섯, 양파, 고추, 페퍼로니

서로 다른 종류의 재료를 제공하기 위한 원재료의 처리 방법을 고려해야 한다.

해결 - 추상 팩토리 패턴 적용

public interface PizzaIngredientFactory {

    public Dough makeDough();
    public Sauce makeSauce();
    public Cheese makeCheese();
    public Veggies[] makeVeggies();
    public Pepperoni makePepperoni();
    public Clams makeClam();

}

먼저 피자 재료를 만드는 인터페이스를 작성한다. 피자에는 도우, 소스, 치즈, 야채, 페퍼로니, 조개가 들어간다.

// 서울 원재료 공장
public class SeoulPizzaIngredientFactory implements PizzaIngredientFactory {

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

    @Override
    public Sauce makeSauce() {
        return new TomatoSauce();
    }

    @Override
    public Cheese makeCheese() {
        return new MozzarellaCheese();
    }

    @Override
    public Veggies[] makeVeggies() {
        Veggies[] veggies = { new Olive(), new Spinach() };
        return veggies;
    }

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

    @Override
    public Clams makeClams() {
        return new FrozenClams();
    }
}
// 부산 원재료 공장
public class BusanPizzaIngredientFactory implements PizzaIngredientFactory {

    @Override
    public Dough makeDough() {
        return new CheeseCrustDough();
    }

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

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

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

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

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

서울과 부산의 원재료 공장은 각 공장의 지침대로 재료를 생산해서 공급한다.

  • 서울
    • 도우 - 씬크러스트
    • 소스 - 토마토
    • 치즈 - 모짜렐라
    • 야채 - 올리브, 시금치
    • 페퍼로니 - 잘린 페퍼로니
    • 조개 - 냉동조개
  • 부산
    • 도우 - 치즈크러스트
    • 소스 - 마리나라
    • 치즈 - 레지아노
    • 야채 - 마늘, 양파, 버섯
    • 페퍼로니 - 잘린 페퍼로니
    • 조개 - 신선한 조개
// 피자 클래스
public abstract class Pizza {

    private String name;
    private Dough dough;
    private Sauce sauce;
    private Veggies[] veggies;
    private Cheese cheese;
    private Pepperoni pepperoni;
    private Clams clam;

    /**
      * SeoulPizzaFactory 또는 BusanPizzaFactory에서 생성된 재료들을 사용하도록
      * 추상 클래스 Pizza 안에 정의된 prepare() 추상 메서드를 이용하여
      * 각 피자마다 재료를 다르게 정의하도록 함
      */
    abstract void prepare();

    void bake() {
        /* 피자 굽기 */
    }

    void cut() {
        /* 구워진 피자 자르기 */
    }

    // Getters and Setters...
}
public class CheesePizza extends Pizza {

    private PizzaIngredientFactory ingredientFactory;

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

    void prepare() {
        dough = ingredientFactory.makeDough();
        sauce = ingredientFactory.makeSauce();
        cheese = ingredientFactory.makeCheese();
    }
}

피자는 서울 지점이냐 부산 지점이냐에 따라 재료가 달라지기 때문에 추상 클래스로 만들고 prepare()라는 추상 메서드를 두어 지역에 따라 다른 피자 재료를 준비하도록 한다.

위의 CheesePizza를 예로 들면 CheesePizza 인스턴스를 생성할 때 원재료 공장을 합께 주입받는다. 그리고 prepare() 추상 메서드를 위와 같이 구현하면 주입받은 원재료 공장에 따라 다른 재료를 가지고 도우, 소스, 치즈를 만들어낼 수 있는 것이다.

// 서울의 한 피자 가게 지점
public class SeoulStationPizzaStore extends PizzaStore {

    protected Pizza makePizza(String item) {
        Pizza pizza = null;

        PizzaIngredientFactory ingredientFactory = new SeoulPizzaIngredientFactory();

        if(item.equals("cheese")) {
            // SeoulStationPizzaStore 클래스에서 생성한 팩토리를 CheesePizza에 주입시킨다.
            // 따라서 여기서 생성하는 CheesePizza는 서울 원재료 공장에서 만든 재료를 사용한다.
            pizza = new SeoulCheesePizza(ingredientFactory);
            pizza.prepare();
            pizza.setName("서울역치즈피자");
            // ...
        }
    }
}
// 부산의 한 피자 가게 지점
public class BusanStationPizzaStore extends PizzaStore {

    protected Pizza makePizza(String item) {
        Pizza pizza = null;

        PizzaIngredientFactory ingredientFactory = new BusanPizzaIngredientFactory();

        if(item.equals("cheese")) {
            // BusanStationPizzaStore 클래스에서 생성한 팩토리를 CheesePizza에 주입시킨다.
            // 따라서 여기서 생성하는 CheesePizza는 부산 원재료 공장에서 만든 재료를 사용한다.
            pizza = new BusanCheesePizza(ingredientFactory);
            pizza.prepare();
            pizza.setName("부산역치즈피자");
            // ...
        }
    }
}

이제 마지막으로 지역별 각 분점에 이를 구현해주어야 한다.

서울의 서울역피자가게에서는 피자를 만들 때 서울 원재료 공장에서 재료를 조달받는다. 해당하는 코드는 7번째 줄이다. 그리고 들어온 주문이 치즈라면 CheesePizza를 만드는데 이때 원재료 공장을 주입해준다. 이제 prepare() 메서드를 실행하면 서울의 분점들은 서울 원재료 공장에서 조달받은 재료를 가지고 피자를 만들 준비를 하게 된다. 부산도 마찬가지이다.

이렇게 각자 맘대로 만들던 분점들에게 재료를 받을 원재료 공장을 지정해주었다. 이제 분점들은 해당 지역의 원재료 공장에서 조달받은 재료를 통해 피자를 만들어 팔 것이다.

추상 팩토리 패턴

인터페이스를 이용하여 서로 연관되거나 의존하는 객체를 구현 클래스를 지정하지 않고 생성하는 패턴이다. 위에서 본 것처럼 만들어지는 피자들은 재료들이 무엇인지 알지 못한다. 피자가게에서 원재료 공장을 주입해주면 그 재료들을 가지고 만들어질 뿐이다. 이는 의존관계 주입(Dependency Injection), 제어의 역전(Inversion of Control, IoC)이기도 하다. 피자는 만들어질 때 PizzaIngredientFactory를 구현한 인스턴스의 재료를 사용하기만 한다. 그리고 그 인스턴스는 피자를 만드는 PizzaStore에서 주입한다. 분점들은 피자를 만들기 위해 PizzaIngredientFactory를 반드시 필요로 하게 된다. 왜냐하면 피자를 만들기 위해 준비하는 prepare() 메서드에서 PizzaIngredientFactory 인스턴스의 재료들을 사용하기 때문이다. 이렇게 각 객체들은 설계 의도대로 동작하며, 여기에서 다형성, 의존관계 주입, 제어의 역전 기법이 적용됐다.

실무 적용

다른 OS, 다른 버전, 다른 성능 지침 등과 같은 상황에서 이러한 추상 팩토리 패턴을 적용하면 문제를 효과적으로 해결할 수 있다.

'Computer Sciences > Design Patterns' 카테고리의 다른 글

6. Command Pattern  (0) 2021.10.14
5. Singleton Pattern  (0) 2021.09.15
3. Factory & Factory Method Pattern  (0) 2021.09.15
2. Decorator Pattern  (0) 2021.09.06
1. Observer Pattern  (0) 2021.09.06