3. Factory & Factory Method Pattern

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

문제

우리 동네에는 피자집이 하나 있다. 이 피자집에서는 예전부터 한 가지 피자만 만들어서 팔았다. 그러나 손님들이 다른 메뉴도 만들어달라는 요청이 들어왔고 이에 따라 신메뉴를 만들어서 제공했다.

// 기존 방식
public class PizzaStore {

    Pizza orderPizza() {

        Pizza pizza = new Pizza();

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

        return pizza;
    }
}
// 새로운 방식
public class PizzaStore {

    Pizza orderPizza(String type) {

        Pizza pizza;

        // 피자 타입에 따라 인스턴스 생성
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        // 공통 코드
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

그런데 이렇게 만들어놓고 보니 메뉴가 추가되거나 삭제될 때마다 orderPizza()의 코드를 변경해야 한다. orderPizza()는 주문된 피자를 만들기만 하면 되는 메서드인데 메뉴의 추가/삭제 마다 변경되는 것이 문제이다. 이를 팩토리 패턴으로 해결할 수 있다.

// 팩토리 클래스
// 인스턴스를 생성하는 역할을 담당한다.
public class SimplePizzaFactory {

    public Pizza makePizza(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();
        }

        return pizza;
    }
}
// 팩토리 패턴 적용
public class PizzaStore {

    SimplePizzaFactory simplePizzaFactory;

    // 생성자를 통해 팩토리 인스턴스를 받는다.
    public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
        this.simplePizzaFactory = simplePizzaFactory;
    }

    Pizza orderPizza(String type) {

        Pizza pizza;

        // 팩토리 패턴을 통해 이제 메뉴의 추가/삭제는 팩토리 클래스에서 담당한다.
        simplePizzaFactory.createPizza(type);

        // 공통 코드
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

팩토리 패턴

  • 객체 생성 부분을 캡슐화한다.
  • 코드의 결합도가 낮아진다.
  • 변화에 유연하다.
  • 프로그래밍을 하는데 있어서 자주쓰이는 관용구에 가깝다.

다시 문제

메뉴까지 늘린 피자집은 매출이 매우 올라갔다. 그래서 프랜차이즈 사업까지 하려고 한다. 먼저 서울과 부산에 지점을 내기로 마음먹었다. 그리고 각 분점들은 각 지역의 특색에 맞는 팩토리를 사용하도록 하게 한다. 이는 팩토리 패턴 또는 팩토리 메서드 패턴을 적용하여 해결할 수 있다.

SeoulPizzaFactory ssFactory = new SeoulPizzaFactory();
PizzaStore ssStore = new PizzaStore(ssFactory);
ssStore.orderPizza("Cheese");
BusanPizzaFactory bsFactory = new BusanPizzaFactory();
PizzaStore bsStore = new PizzaStore(bsFactory);
bsStore.orderPizza("Cheese");

위와 같은 방법은 팩토리 패턴을 적용한 방법이다. 이번엔 팩토리 메서드 패턴을 적용해보자.

public abstract class PizzaStore {

    public Pizza orderPizza(String type) {

        Pizza pizza;

        // 팩토리 객체가 아닌 PizzaStore에 있는 createPizza()를 호출한다.
        pizza = createPizza(type);

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

        return pizza;
    }

    // 팩토리 객체를 받는 것이 아닌 PizzaStore의 추상 메서드로 변경됐다.
    // 이 클래스를 상속받는 서브 클래스들이 이를 구현하여 사용한다.
    abstract Pizza makePizza(String type);
}
// 서울역 피자집
public class SeoulStationPizzaStore extends PizzaStore {

    Pizza makePizza(String type) {
        if(type.equals("cheese")) {
            return new SeoulStationCheesePizza();
        } else if (type.equals("pepperoni")) {
            return new SeoulStationPepperoniPizza();
        } else if (type.eqeuals("clam")) {
            return new SeoulStationClamPizza();
        } else {
            return null;
        }
    }
}
// 부산역 피자집
public class BusanStationPizzaStore extends PizzaStore {

    Pizza makePizza(String type) {
        if(type.equals("cheese")) {
            return new BusanStationCheesePizza();
        } else if (type.equals("pepperoni")) {
            return new BusanStationPepperoniPizza();
        } else if (type.eqeuals("clam")) {
            return new BusanStationClamPizza();
        } else {
            return null;
        }
    }
}

이런 식으로 만들게 되면 서울역 피자집과 부산역 피자집 분점을 낼 수 있게 된다. 피자 클래스도 만들어보자.

public abstract class Pizza {

    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

    void prepare() {
        System.out.println(name + " 준비 중...");
        System.out.println(dough + " 굽는 중...");
        System.out.println(sauce + " 뿌리는 중...");

        toppings.stream().forEach(t -> System.out.println(t + " 추가 중..."));
    }

    void bake() {
        // 피자 오븐에서 굽기
    }

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

    void box() {
        // 피자 박싱
    }

    // Getter and Setter...
}
public class SeoulCheesePizza extends Pizza {

    public SeoulCheesePizza() {
        name = "서울 스타일 소스와 치즈 피자";
        dough = "씬 크러스트 도우";
        sauce = "마리나라 소스":
        toppings.add("레지아노 치즈");
    }
}
public class BusanCheesePizza extends Pizza {

    public BusanCheesePizza() {
        name = "부산 스타일 소스와 치즈 피자";
        dough = "치즈 크러스트 도우";
        sauce = "토마토 소스":
        toppings.add("모짜렐라 치즈");
    }
}

위와 같이 작성함으로써 분점 지역의 특색에 맞게 피자를 만들어낼 수 있게 됐다.

팩토리 메서드 패턴

  • 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 하는 패턴이다.
  • 팩토리 메서드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브 클래스에게 맡기는 것이다.
  • 새로운 분점이나 피자 종류를 추가해도 소스의 수정 없이 추상 클래스를 상속받아 구현하기만 하면 된다.

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

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