GoF 디자인패턴) Factory Method Pattern

첫인상

  • 많이 들어봤고 많이 알게모르게 사용해봤을 그런 패턴

의도

  • 객체를 생성하기 위해 인터페이스를 정의하지만, 어떤 클래스의 인터페이스를 생성할 지에 대한 결정은 서브클래스가 정의

다른 이름

  • Vircual Constructor

동기

  • Application abstract class와 Document Interface가 존재하고 Application은 Document Interface를 사용함.
  • Document를 사용하지만, 어떤 ConcreteDocument를 사용할지는 모르는 상태
  • Application에는 createDocument() 와 OnOperation() method가 존재한다.
  • 어떤 Document를 사용할지는 Application 의 Concrete Class에 위임하고 구현은 onOperation에 집중한다.

활용성

  • 어떤 클래스가 자신이 생성해야하는 객체의 클래스를 예측할 수 없을 때
  • 생성할 객체를 기술하는 책임을 자신의 서브클래스가 했으면 할 때
  • 어떤 Concrete객체(예제에서는 Document)가 생성될지에 대해 모르고 싶을 때.

구조

참여자

  • Product(Document)
    • 팩토리 메서드가 생성하는 객체의 인터페이스를 정의
  • ConcreteProduct (MyDocument)
    • Product 클래스에 정의 된 인터페이스 구현
  • Creator (Application)
    • Product 타입의 객체를 반환하는 팩토리 메서드를 선언함.
    • Creator class는 팩토리 메서드를 선언하는데, 이 구현에서는 ConcreteProduct 객체를 반환하고, Product 객체 생성을 위해 팩터리 메서드를 호출해야함
  • ConcreteProduct
    • 팩터리 메서드를 재정의 하여 ConcreteProduct의 instance를 반환함

협력 방법

  • Creator는 자신의 서브 클래스를 통해 실제 필요한 팩토리 메서드를 정의하여 적절한 ConcreteProduct를 반환한다.

구현

  • 이거는 책 내용이 모호하고, 블로그마다 구현이 다양해서 처음에 참 햇갈렸다.
  • 블로그 글에서 대부분은 팩토리 메서드 패턴이 아닌 팩토리 패턴을 구현하고 이걸 소개하고 있다.
  • 나도 팩토리 패턴을 팩토리 메서드 패턴이라고 잘못알고 있었던 것 같다. (팩토리 패턴은 매우 훌륭한 기법이다. 나쁘다는게 아니다)
  • 팩토리 패턴과 팩토리 메서드 패턴을 직접 구현해보면서 차이점이 뭔지 알아보자

팩토리 패턴

  1. Pizza Interface (Product)
public interface Pizza {
    void addTopping();
    void bake();
}
  1. Cheese/Peperoni Pizza (Concrete Product)
  • 모든 피자는 토핑을 더하고 굽는 과정이 존재한다고 가정한다.
public class CheesePizza implements Pizza {
    @Override
    public void addTopping() {
        System.out.println("Add Cheese Topping!");
    }

    @Override
    public void bake() {
        System.out.println("Baking cheese Pizza");
    }
}

public class PeperoniPizza implements Pizza {
    @Override
    public void addTopping() {
        System.out.println("Add Peperoni topping !");
    }

    @Override
    public void bake() {
        System.out.println("Baking peperoni Pizza");
    }
}
  1. PizzaStore/PizzaFactory (ConcreteCreator)
  • PizzaStore에서 피자를 만들 때, PizzaType별 피자를 생성하기 위해 사용 할 지저분한 if/else 을 별도로 분리 시키기 위해 PizzaFactory를 만들어주었다.
  • PizzaStore에 orderPizza()를 호출하면, 해당 메소드에서는 PizzaFactory에서 피자 종류를 생성해 주고, 공통과정인 addTopping()과 bake() 처리를 해준다.
public class PizzaStore {
    private SimplePizzaFactory simplePizzaFactory;

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

    public void orderPizza(PizzaType pizzaType) {
        Pizza pizza = simplePizzaFactory.createPizza(pizzaType);
        pizza.addTopping();
        pizza.bake();
    }
}

public class PizzaFactory {
    public Pizza createPizza(PizzaType pizzaType) {
        if (pizzaType == PizzaType.CHEESE) {
            return new CheesePizza();
        } else if (pizzaType == PizzaType.PEPERONI) {
            return new PeperoniPizza();
        } else {
            System.out.println("Pizza type not existed");
            throw new RuntimeException();
        }
    }

}
  1. Client
public class Client {
    public static void main(String[] args) {
        PizzaStore pizzaStore = new PizzaStore(new SimplePizzaFactory());
        pizzaStore.orderPizza(PizzaType.PEPERONI);
        pizzaStore.orderPizza(PizzaType.CHEESE);
    }
}

Add Peperoni topping !
Baking peperoni Pizza
Add Cheese Topping!
Baking cheese Pizza

  • 이정도만해도 PizzaStore Class에서 if/else 문을 없애줄수 있다는 점에서 충분히 훌륭한 코딩 방법이기는 하다.
  • 하지만 이건 책에서 이야기하는 팩토리 메서드 패턴이 아니다. 패턴이 아닌 좋은 코드 습관 정도에 불과하다.
  • 팩토리 매서드 패턴 이라는 제목에 주목하자.
  • 만약 PizzaStore가 아닌 SeoulPizzaStore, PusanPizzaStore 분점을 내서 피자를 생산해야한다 가정해보자.
  • SeoulPizzaStore에서는 먼저 굽고 토핑을 올리고, PusanPizzaStore에서는 토핑을 올리고 굽는 본점 방식을 그대로 사용한다면? 문제가 될 것이다.
  • 이런 공통의 기능은 따로 묶고 각 분점마다의 고유한 기능을 Concrete 클래스에 위임하고 싶을 때 사용하는 것이 바로 팩토리 메소드 패턴이다.

팩토리 메서드 패턴

  • 자 이제, 제대로 된 팩토리 메서드 패턴을 만들어보자.

  • 팩토리 메서드 패턴은 Template Method Pattern의 Creator 버전이라고 생각하면 좋다고 위키에 나와있다. (이거보고 사실 좀 이해가 된 거였음)

  • 위에서 작성했던 PizzaStore는 concretFactory이다. 책에서 나온대로 abstract class로 바꿔보자


public abstract class PizzaStore {

    public void orderPizza(PizzaType pizzaType) {
        Pizza pizza = createPizza(pizzaType);
        pizza.addTopping();
        pizza.bake();
    }

    abstract Pizza createPizza(PizzaType pizzaType);
    
    
}
  • 바꿔보니 어떤가. 템플릿 메소드 패턴과 유사해지지 않는가? 책에서 말한대로 생성하는 부분을 Concrete 에 위임하는 모양새가 되었다.
  • 생성을 concrete Class에 위임해보자.

public class SeoulPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(PizzaType pizzaType) {
        if (pizzaType == PizzaType.PEPERONI) {
            return new SeoulPeperoniPizza();
        } else if (pizzaType == PizzaType.CHEESE) {
            return new SeoulCheesePizza();
        }
    }
}
... NewYorkPizzaStore는 생략.. 
  • 마지막으로 Client를 구현
public class Client {
    public static void main(String[] args) {
        PizzaStore seoulPizzaStore = new SeoulPizzaStore();
        PizzaStore newYorkPizzaStore = new NewYorkPizzaStore();
        
        seoulPizzaStore.orderPizza(PizzaType.PEPERONI);
        newYorkPizzaStore.orderPizza(PizzaType.PEPERONI);
        seoulPizzaStore.orderPizza(PizzaType.CHEESE);
        newYorkPizzaStore.orderPizza(PizzaType.CHEESE);
    }
}

소감

  • 간단한 팩토리는 디자인 패턴이 아니다. 관용구에 가깝다. 나는 관용구에 가까운 팩토리 메소드 패턴만 이해하고 있었다고 보면 된다. 물론 관용구를 아는 것도 중요하다. 지저분한 if else문을 분리했다는 것만 해도 코드의 가독성 향상에 큰 역할을 했다는 것
  • 템플릿 메소드 패턴의 create 패턴 버전!

좋은 웹페이지 즐겨찾기