2010년 1월 26일 화요일

디자인 패턴의 네번째! 팩토리 패턴(Factory Pattern) - 1

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

  팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤

클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듦. 팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브 클래스에게 맡김

  - 서브클래스에서 결정한다는 것은 이 패턴을 사용할 때 서브클래스에서 실행중에 어떤 클래스의 인스턴스를 만들지 결정하기 때문이 아니라 생산자 클래스 자체가 실제 생산될 제품에 대한 사전지식이 전혀 없이 만들어 지기 때문(팩토리 메소드를 사용하는 이유는 이전 블로깅의 new를 사용할 때의 단점을 보완하여 유연성 있도록 하기 위함)

 

팩토리 메소드 패턴의 사용 예(이전 블로깅에서 만들었던 내용에 이어서)

 

  이전에 만들었던 createPizza() 메소드를 PizzaStore에 다시 넣으면서 추상 메소드로 선언하고 PizzaStore의 서브클래스를 만듦

 

public abstract class PizzaStore{

    public Pizza orderPizza(String type){

        Pizza pizza;

        pizza = createPizza(type);

        // 팩토리 객체가 아닌 PizzaStore에 있는 createPizza를 호출하게 됨

        pizza.prepare();

        pizza.bake();

        pizza.cut();

        pizza.box();

        return pizza;

    }

    protected abstract Pizza createPizza(String type);

    //기타 메소드

    //팩토리 메소드가 PizzaStore의 추상메소드로 바뀜

    //Pizza 인스턴스를 만드는 일은 이제 팩토리 역할을 하는 메소드에서 맡아서 처리

}

 

  팩토리 메소드는 객체 생성을 처리하며, 팩토리 메소드를 이용하면 객체를 생성하는 작업을 서브클래스에 캡슐화 시킬 수 있음. 이렇게 하면 수퍼클래스에 있는 클라이언트 코드와 서브클래스에 있는 객체 생성 코드를 분리시키게 됨.

 

 

서브클래스에서 결정되는 것

  PizzaStoreorderPizza() 메소드에 이미 주문 시스템이 갖춰져 있으므로(서브 클래스

에서 변경하지 못하도록 final로 선언 가능) 각 서브클래스에서는 달라지는 피자의 스타일을 createPizza() 메소드로 구현

 

 

<서브 클래스의 코드>

public class NYPizzaStore extends PizzaStore{

    Pizza createPizza(String item){

        if(item.equals("cheese")){

            return new NYStyleCheesePizza();

        } else if (item.equals("veggie)){

            return new NYStyleVeggiePizza();

        } else return null;

    }

}

 

  수퍼클래스에 있는 orderPizza()메소드에서는 어떤 피자가 만들어지는지 전혀 알 수 없음. 그 메소드에서는 피자를 준비하고 굽고 자르고 포장하는 작업만 처리함.

 

팩토리 메소드를 이용한 피자 주문(뉴욕풍 피자 주문)

  1. PizzaStore 인스턴스를 확보(NYPizzaStore의 인스턴스를 만듦)

  2. PizzaStore가 만들어지고 나면 orderPizza()를 호출

      (인자를 사용해서 cheese, veggie 등을 알려줌)

  3. 피자를 만들 때는 createPizza() 메소드가 호출되는데, 이 메소드는 PizzaStore의 서브

     클래스인 NYPizzaStore에 정의되어 있으므로 NYPizzaStore에서는 뉴욕풍의 피자 인스

     턴스를 만들고 어떤 서브클래스를 쓰든지 Pizza 객체가 orderPizza() 메소드로 리턴됨.

  4. orderPizza() 메소드에서는 어떤 종류의 피자가 만들어졌는지 전혀 알지 못하지만 피자

     라는 것을 알고있고 그 피자를 준비하고, 굽고, 자르고, 포장하는 작업을 완료함.

 

<만들어지는 과정>

1. 뉴욕풍 피자가게 확보

  PizzaStore nyPizzaStore = new NYPizzaStore();

  // NYPizzaStore 인스턴스 생성

 

2. 피자 가게가 확보되었으니 주문을 받음

  nyPizzaStore.orderPizza(“cheese”);

  // nyPizzaStore 인스턴스의 orderPizza() 메소드가 호출(PizzaStore에 정의된 메소드 호출)

 

3. orderPizza() 메소드에서 createPizza() 메소드를 호출

  Pizza pizza = createPizza(“cheese”)

  // 팩토리 메소드인 createPizza() 메소드는 서브 클래스에서 구현. 이 경우 뉴욕풍 피자 리턴

 

4. 피자를 받아서 마무리 작업

  pizza.prepare();

  pizza.bake()

  pizza.cut();

  pizza.box();

  // aphememf은 모두 createPizza()

 

<팩토리 메소드 패턴의 다이어그램>

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

 

<생산자(Creator) 클래스>

 

<제품(Product) 클래스>

 

객체 의존성

  만약 팩토리 메소드 패턴을 사용하지 않고 객체 인스턴스를 직접 만들면 구상 클래스에 의존해야 함. 모든 피자 객체를 팩토리에 맡겨서 만들지 않고 PizzaStore 클래스 내에서 직접 만들시 모든 피자 객체들에게 직접적으로 의존하게 되고 피자 클래스들의 구현이 변경되면 PizzaStore 클래스까지 고쳐야 됨

 

의존성 뒤집기 원칙(Dependency Inversion Principle)

  구상 클래스처럼 구체적인 것이 아닌 추상 클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드를 만들어야 한다는 것으로 이전에 나왔던 특정 구현이 아닌 인터페이스에 맞춰서 프로그래밍 한다는 원칙과 비슷하지만 의존성 뒤집기 원칙에서는 추상화를 더 많이 강조. 이 원칙에는 고수준 구성요소가 저수준 구성요소에 의존하면 안된다는 것이 내포되어 있고 항상 추상화에 의존하도록 만들어야 한다는 것(PizzaSotre를 고수준 구성요소라고 할수 있고 서브 피자 클래스들은 저수준 요소라고 할수 있음)

 

원칙 적용

  팩토리 메소드 패턴을 적용하고 나면 고수준 구성요소인 PizzaStore와 저수준 구성요소인 피자 객체들이 모두 추상 클래스인 Pizza에 의존하게 됨. 팩토리 메소드 패턴이 의존성 뒤집기 원칙을 준수하기 위해 쓸 수 있는 유일한 기법은 아니지만 가장 접합한 방법 가운데 하나

 

원칙을 지키는 데 도움이 될만한 가이드라인

  어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 말 것

     - new 연산자를 사용하면 구상 클래스에 대한 레퍼런스를 사용하게 되는 것이고

       팩토리를 써서 구상 클래스에 대한 레퍼런스를 변수에 저장하는 일을 미리 방지

  구상 클래스에서 유도된 클래스를 만들지 말 것

     - 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 됨.

        인터페이스나 추상 클래스처럼 추상화된 것으로부터 클래스를 만들어아 함

  베이스 클래스에 이미 구현된 메소드를 오버라이드 하지 말 것

     - 이미 구현되어 있는 메소드를 오버라이드 한다는 것은 애초부터 베이스 클래스가 제대로

       추상화 된 것이 아니라고 볼 수 있음. 베이스 클래스에서 메소드를 정의할 때는 모든

       서브클래스에서 공유할 수 있는 것만 정의해야 함

 

이 가이드라인은 항상 지켜야 하는 규칙이 아니라 우리가 지향해야 할 바를 밝히고 있을 뿐

    이고 자바 프로그램 가운데 이 가이드라인을 완벽하게 따르는 것은 하나도 없음. 그러나 이

    가이드라인을 완전히 습득한 상태에서 디자인을 할 때 항상 이 가이드라인을 염두해

    둔다면, 불가피한 상황에서만 합리적인 이유를 바탕으로 그렇게 하게 될 것이다.

 

출처 : Head First Design Patterns

댓글 없음:

댓글 쓰기