ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 팩토리 패턴
    Programming/Design Patterns 2010. 5. 3. 08:12

     

    4. 팩토리 패턴

    1. 간단한 팩토리

    1.1 간단한 피자 팩토리를 만들기.

    • 팩토리(Factory) : 객체 생성을 처리하는 클래스.

     

    피자 객체 생성 간단한 피자 팩토리

    //==============================================
    //피자 가게 운영을 위한 객체생성
    //==============================================
    Pizza orderPizza(){

      //추상 클래스, 인터페이스로는 직접 인스턴스를 만들 수 없다.
      Pizza pizza = new Pizza();
      Pizza.prepare();
      Pizza.bake();
      Pizza.cut();
      Pizza.box();
      return pizza;
    }

     

    //==============================================
    //여러 종류 피자를 만들기 위한 수정
    //==============================================
    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 if(type.equals("veggie")){
        pizza = new VeggiePizza();
      }
      //피자 종류에 상관없이 바뀌지 않는 부분.
      Pizza.prepare();
      Pizza.bake();
      Pizza.cut();
      Pizza.box();
      return pizza;
    }

     //==============================================
    //피자 가게 객체 생성에 캡슐화 적용
    //==============================================
    Pizza orderPizza(String type){
    Pizza pizza;
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
    return pizza;
    }
    //==============================================
    //피자를 만들기 위한 객체생성
    //==============================================
    public class SimplePizzaFactory{
     //클라이언트에서 새로운 객체의 인스턴스를 만들때 호출 
    public Pizza createPizza (String type){
    Pizza pizza = null;
     //orderPizza() 메소드에서 뽑아낸 코드(피자의 종류를 결정) 
    if(type.equals("cheese")){
    pizza = new CheesePizza();
    } else if(type.equals("pepperoni")){
    pizza = new PepperoniPizza();
    } else if(type.equals("clam")){
    pizza = new ClamPizza();
    } else if(type.equals("veggie")){
    pizza = new VeggiePizza();
    }
    return pizza;
    }
    }

     


    • 간단한 팩토리의 장점
      Factory를 사용하는 클라이언트가 많을 경우, 여러 클래스에서 사용할 수 있다.
      (예를들어, 피자를 생성하는 작업을 한 클래스에 캡슐화 시켜 놓으면 구현을 변경해야하는 경우, 해당 팩토리 클래스 하나만 수정하면 된다.)
    • 정적 팩토리(static factory)
      장점 : 객체 생성을 위한 메소드 실행을 위해 객체 인스턴스를 만들지 않아도 된다.
      단점 : 서브 클래스를 만들어 객체 생성 메소드의 행동을 변경 시킬 수 없다.
     //============================================== 
    //팩토리를 이용한 피자 객체를 생성
    //==============================================
     public class PizzaStore{ 
    //Simplepizzafactory에 대한 레퍼런스를 저장

    SimplePizzaFactory factory;

    //PizzaStore의 생성자에 팩토리 객체 전달

    public Pizza createPizza (SimplePizzaFactory factory){
    this.factory = factory;
    }

    public Pizza orderPizza (String type){
    Pizza pizza;

    /*orderPizza() 매소드에서는 팩토리를 써서 피자 객체 생성.
    new 연산자 대신 팩토리 객체에 있는 create 메소드를 사용.
    구상 클래스의 인스턴스를 만들 필요가 없다.*/

    pizza = faactory.createPizza(type);
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();
    return pizza;
    }
    }

    1.2 간단한 팩토리 정의

    • 간단한 팩토리(Simple Factory) : 디자인 패턴이라 할 수 없고, 자주 쓰이는 관용구에 가깝다고 할 수 있다.

    <피자가게의 클래스 다이어그램>

    디자인 패턴에서 '인터페이스를 구현한다' 는 표현이
    항상 클래스 선언 부분의 implements 키워드를 써서 인터페이스 구현하는 클래스를 만든다는 것이 아니다.
    일반적으로 상위 형식(클래스 or 인터페이스)에 있는 메소드를 구현하는 구상 클래스는 그 상위 형식의 '인터페이스를 구현하는' 클래스라고 생각하면 된다.

    1.3 피자가게 프레임워크

    *Point : 피자는 PizzaStore 클래스에서 만들고, 분점마다 고유 스타일을 살리자.


     
     //=========================================
    //뉴욕 스타일 피자를 만들기 위한 팩토리 생성
    //==============================================
     NYPizzaFactory nyFactory = new NYPizzaFactory(); 
    PizzaStore nyStore = new PizzaStore(nyFactory);
    nyStore.order("Veggie");
     //ChicagoPizzaFactory 객체가 포함되어 있는 PizzaStore 객체를 만듬. 
     ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory(); 
    PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
    chicagoStore.order("Veggie");
     //=========================================
    //추상클래스 (PizzaStore) 생성
    //==============================================
     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;
    }

    //팩토리 객체대신 사용
    abstract createPizza(String type);

    }


    1.4 팩토리 메소드 선언

    public abstract class PizzaStore{
    public Pizza orderPizza(String type){
    Pizza pizza;
    pizza = createPizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
    }
    protected abstract Pizza createPizza (String type);
    }

    팩토리 메소드

    • 객체 생성을 처리.
    • 객체 생성작업을 서브클래스에 갭슐화.
    • 슈퍼클래스의 클라이언트 코드와 서브클래스의 객체생성 코드 분리.
     -코드탐구- abstract Product factoryMethod(String type)

    1.4.1 피자 팩토리 메소드를 이용한 피자 주문

    • 1. PizzaStore 인스턴스 확보.
    • 2. orderPizza() 호출
    • 3. createPizza() 호출.(Pizza 객체가 orderPizza() 메소드로 리턴됨)
    • 4. orderPizza() 피자를 준비하고, 굽고, 자르고, 포장하는 작업 완료함.

    1.4.2 피자 만드는 과정 살펴보자

     public abstract class Pizza { 
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

    void prepare() {
    System.out.println("Preparing " + name);
    System.out.println("Tossing dough..");
    System.out.println("Adding sauce... ");
    System.out.println("Adding toppings: ");
    for(int i=0; i< toppings.size(); i++) {
    System.out.println(" "+ toppings.get(i));
    }
    }
      void bake() { 
    System.out.println("Bake for 25 miutes at 350");
    }

    void cut() {
    System.out.println("Cutting the pizza into diagonal slices");
    }
      void box() { 
    System.out.println("Place pizza in official PizzaStore box");
    }

    public String getName() {
    return name;
    }
    }

    public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
    name = "NY Style Sauce and Cheese Pizza";
    dough = "Thin Crust Dough";
    sauce = "Marinara Sauce";

    toppings.add("Grated Reggiano Cheese");
    }
    }

    public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
    name = "Chicago Style Sauce and Cheese Pizza";
    dough = "Extra Thick Crust Dough";
    sauce = "Plum Tomato Sauce";

    toppings.add("Shredded Mozzarella Cheese");
    }

    void cut() {
    System.out.println("Cutting the pizza into square slices");
    }

    }

    public class PizzaTestDrive {
    public static void main(String[] args) {
    PizzaStore nyStore = new NYPizzaStore();
    PizzaStore chicagoStore = new ChicagoPizzaStore();

    Pizza pizza = nyStore.orderPizza("cheese");
    System.out.println("Ethan ordered a " + pizza.getName() + "\n");

    Pizza pizza = chicagoStore.orderPizza("cheese");
    System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }

    }

    2. 팩토리 메소트 패턴

    • 모든 팩토리 패턴에서는 객체 생성을 캡슐화 한다.

    2.1 병렬 클래스 계층구조

    • 지금까지의 학습내용
      팩토리 메소드 패턴 : 팩토리 메소드 + orderPizza() 메소드를 제공하는 형태의 프레임워크.

    <다른 형태의 프레임 워크로 바라보기(두 클래스의 연관성 찾기)>

    제품 클래스 생산자 클래스

    • 서로 병렬적으로 구성되어 있다.
    • 추상 클래스를 시작한다.
    • 구상 클래스를 가지고 있다.

    2.2 팩토리 메소트 패턴 정의

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

    .Creator 추상 클래스에서 팩토리 메소드를 위한 인터페이스를 제공한다
    .서브클래스에서 실제 팩토리 메소드를 구현하고 제품(객체 인스턴스)을 만들어내는 일을 수행한다
    .사용하는 서브클래스에 따라 생산되는 객체 인스턴스가 결정된다.

    사부와 제자

    사부 : '팩토리'를 썼을 때 어떤 장점이 있느냐~?

    제자 :
    1. 객체 생성코드를 전부 한 객체 또는 메소드에 집어 넣으면 코드에서 중복되는 내용을 제거할 수 있습니다.
    2. 클라이언트 입장에서는 객체 인스턴스를 만들 때 필요한 구상 클래스가 아닌 인터페이스만 필요하게 됩니다.
    3. 인터페이스를 바탕으로 프로그래밍을 할수 있어, 유연성과 확장성이 뛰어난 코드를 만들 수 있습니다.

    2.3 객체 의존성 살피기

     public class DependentPizzaStore { 
    public Pizza createPizza(String style, String type) {
    Pizza pizza = null; //뉴욕풍 피자를 처리하는 부분
    if (style.equals("NY")) {
    if (type.equals("cheese")) {
    pizza = new NYStyleCheesePizza();
    } else if (type.equals("veggie")) {
    pizza = new NYStyleVeggiePizza();
    } else if (type.equals("clam")) {
    pizza = new NYStyleClamPizza();
    } else if (type.equals("pepperoni")) {
    pizza = new NYStylePepperoniPizza();
    }

    //시카고풍 피자를 처리하는 부분
    } else if (style.equals("Chicago")) {
    if (type.equals("cheese")) {
    pizza = new ChicagoStyleCheesePizza();
    } else if (type.equals("veggie")) {
    pizza = new ChicagoStyleVeggiePizza();
    } else if (type.equals("clam")) {
    pizza = new ChicagoStyleClamPizza();
    } else if (type.equals("pepperoni")) {
    pizza = new ChicagoStylePepperoniPizza();
    }
    } else {
    System.out.println("Error: invalid type of pizza");
    return null;
    }
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
    }
    }

    • 객체 인스턴스를 직접 만들면 구상 클래스에 의존해야한다.


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

    • 추상화된 것에 의존하도록 만들어라.
    • 구상 클래스에 의존하도록 만들지 않도록 한다.

    -고수준 구성요소가 저수준 구성요소에 의존하면 안된다.(고수준, 저수준 모듈이 둘 다 하나의 추상 클래스에 의존하는 것)
    -의존성 뒤집기 원칙에 의하면, 구상 클래스처럼 구체적인 것이 아닌 '추상 클래스나 인터페이스와 같이 추상적인 것에 의존하는 코드'를 만들어야 한다.

    2.4.1 원칙 적용하기

    -심하게 의존적인 PizzaStore()의 가장 큰 문제점은 모든 종류의 피자에 의존한다는 점이다.

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

    2.5 원칙적용과 지키기

    1. 어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 맙시다.
    2. 구상 클래스에서 유도된 클래스를 만들지 맙시다.
    3. 베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드 하지 맙시다.

    위 가이드 라인은 항상 지켜야 하는 규칙이 아니라, 개발자가 지향해야 하는 규칙이다.

    2.6 원재료 공장 만들기

    -원재료를 생산할 팩토리를 위한 인터페이스 정의

     public interface PizzaIngredientFactory { //각 재료별로 생성 메소드 정의. 
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClam();
    }
     -모든 팩토리 인스턴스에서 공통적으로 사용하는 부분이 있다면 인터페이스가 아닌 추상클래스로 만들어도 된다.

    -이렇게 해봅시다.
    1. 지역별로 팩토리를 만든다. 각 생성 메소드를 구현하는 PizzaIngredientFactory 클래스를 만들어야 한다.
    2. Reggianocheese, RedPeppers, ThickCrustDough와 같이 팩토리에서 사용할 원재료 클래스들을 구현한다.

    상황에 따라 서로 다른 지역에서 같은 재료 클래스를 쓸 수도 있다.
    3. 새로 만든 원재료 공장을 PizzaStore 코드에서 사용하도록 함으로써 모든 것을 하나로 묶어줘야 된다.

    //========================================================================
    //모든 재료 공장에서 구현해야 하는 인터페이스를 구현
    //========================================================================
    public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    public Dough createDough() {
    return new ThinCrustDough();
    }
    public Sauce createSauce() {
    return new MarinaraSauce();
    }
    public Cheese createCheese() {
    return new ReggianoCheese();
    }
    public Veggies[] createVeggies() {
    Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
    return veggies;
    }
    public Pepperoni createPepperoni() {
    return new SlicedPepperoni();
    }
    public Clams createClam() {
    return new FreshClams();
    }
    }

    3. 추상 팩토리 패턴

    3.1 클래스 변경

    -코드탐구
    sauce = ingredientFactory.createSauce();

    3.1.1 지금까지의 과정

    -추상 팩토리(Abstract Factory)라고 부르는 새로운 형식의 팩토리 도입한다.
    -추상 팩토리를 통해 제품군을 생성하기 위한 인터페이스를 제공한다
    -다양한 팩토리를 구현할 수 있다.
    -클라이언트 코드의 전혀 바뀔 필요 없이, 제품을 여러가지 서로 다른 방식으로 구현 할 수 있다.

    3.2 추상 팩토리 패턴 정의

    추상 팩토리 패턴 :
    -인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다.
    -클라이언트에서 추상 인터페이스를 통해서 일련의 제품들을 공급받을 수 있다.

    -피자주문하기
    1. 뉴욕 피자가게(NYPizzaStore)가 필요하다.

    PizzaStore nyPizzaStore = new NYPizzaStore();

    2.주문시작

    nyPizzaStore.orderPizza("cheese");

    3.orderPizza() 메소드에서 우선 createPizza() 메소드를 호출한다.

    Pizza pizza = createPizza("cheese");

    4.createPizza() 메소드가 호출되면 원재료 공장이 돌아가기 시작한다.

    Pizza pizza = new CheesePizza(nyIngredientFactory);
    5. prepare() 메소드가 호출되면 팩토리에 원재료 주문이 들어간다.

    void prepare() {
    dough = factory.createDough();
    sauce = factory.createSauce();
    cheese = factory.createCheese();
    }

    6. 피자준비 끝~! orderPizza() 메소드에서 피자를 굽고, 자르고, 포장한다.

    패턴 집중 인터뷰('팩토리 메소드 패턴과 추상 팩토리 패턴')

    -공통점

    • 클라이언트와 구상 형식을 분리 시켜주는 역할을 한다.
    • 일련의 연관된 제품을 하나로 묶을 수 있는 장점이 있다.
    • 객체 생성을 캡슐화해서 애플리케이션의 결합을 느슨하게 만들고, 특정구현에 덜 의존하게 만든다.

    -특징
    팩토리 메소드 패턴:

    • 상속을 통해서 객체를 만든다.
    • 클래스를 확장하고 팩토리 메소드를 오버라이드 한다.
    • 서브클래스에서 만드는 구상 형식을 활용하는 추상 생산자에서 코드를 구현한다.
    • 클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리시켜야 할 때 유용하다.
    • 어떤 구상 클래스를 필요로 하게 될지 알 수 없는 경우에도 유용하다.

    추상 팩토리 패턴:

    • 객체구성(composition)을 통해서 객체를 만든다.
    • 제품군을 만들기 위한 추상 형식을 제공한다.
    • 새로운 제품을 추가하려면 인터페이스를 바꿔야 한다.
    • 구상 팩토리를 구현할 때 팩토리 메소드를 써서 제품을 생산하는 경우도 있다.
    • 클라이언트에서 서로 연관된 일련의 제품들을 만들어야 할 때 유용하다.

    5. 핵심정리

    • 팩토리를 쓰면 객체 생성을 캡슐화 할 수 있다.
    • 간단한 팩토리는 엄밀하게 말해 디자인 패턴은 아니지만, 클라이언트와 구상 클래스를 분리시키기 위한 간단한 기법으로 활용할 수 있다.
    • 팩토리 메소드 패턴에서는 상속을 활용한다. 객체 생성이 서브클래스에게 위임된다. 서브클래스에서는 팩토리 메소드를 구현하여 객체를 생산한다.
    • 추상 팩토리 패턴에서는 객체 구성을 활용한다. 객체 생성이 팩토리 인터페이스에서 선언한 메소드들에서 구현된다.
    • 모든 팩토리 패턴에서는 애플리케이션의 구상 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합을 도와준다.
    • 팩토리 메소드 패턴에서는 어떤 클래스에서 인스턴스를 만드는 일을 서브클래스한테 넘긴다.
    • 추상 팩토리 패턴은 구상 클래스에 직접 의존하지 않고도 서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰인다.
    • 의존성 뒤집기 원칙을 따르면 구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있다.
    • 팩토리는 구상 클래스가 아닌 추상 클래스/인터페이스에 맞춰서 코딩할 수 있게 해 주는 강력한 기법이다.

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

    디자인 패턴 요약 정리  (0) 2010.05.03
    옵저버 패턴  (0) 2010.05.03
    스트래티지 패턴  (0) 2010.05.03
    데코레이터 패턴  (0) 2010.05.03
Designed by Tistory.