suyeonme

[디자인 패턴] 팩토리 패턴(Factory Pattern) 본문

프로그래밍👩🏻‍💻/디자인 패턴

[디자인 패턴] 팩토리 패턴(Factory Pattern)

suyeonme 2022. 6. 26. 22:23
헤드 퍼스트 디자인 패턴을 읽고 정리한 내용입니다.

1. 간단한 팩토리(Simple Factory)

  • 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구이다.
  • 객체 생성 작업을 팩토리 클래스로 캡슐화해놓으면 구현을 변경할 때 팩토리 클래스 하나만 고치면 된다. 즉 코드에서 중복되는 내용을 제거할 수 있고, 관리할 때도 한군데만 관리하면 된다.
  • 객체 인스턴스를 만들 때 인터페이스만 있으면 된다. 인터페이스를 바탕으로 하여 유연성과 확장성이 뛰어난 코드를 작성할 수 있다. 
  • 팩토리(factory): 객체 생성을 처리하는 클래스

1.1 문제 상황

당신은 피자가게를 운영한다. 따라서 아래의 코드를 작성하였다.  하지만 코드는 몇 가지 문제점이 있다.

  • 피자 종류가 추가, 제거, 변경등이 일어날 때마다 코드를 계속 고쳐야한다.
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;
}

1.2 객체 생성 팩토리로 교체

// 간단한 팩토리(Simple Factory)
public class SimplePizzaFactory {
  public pizza createPizza(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();
     }
  }
}

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

2. 팩토리 메서드 패턴(Factory Method Pattern)

  • 객체를 생성할 때 필요한 인터페이스를 만든다.
  • 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정한다. 팩토리 메소드 패턴을 사용하면 클래스 인스턴스 만드는 일을 서브 클래스에게 맡기게 된다. 즉, 사용하는 서브 클래스에 따라 생산되는 객체 인스턴스가 결정된다.
  • 상속으로 객체를 생성한다. 따라서 클래스를 확장하고 팩토리 메소드를 오버라이드한다. 즉 클라이언트와 구상 형식을 분리한다.

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

  • 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다. 고수준 구성 요소가 저수준 구성요소에 의존하면 안되며, 항상 추상화에 의존하게 만들어야한다. (PizzaStore는 고수준 구성 요소, Pizza 클래스는 저수준 구성 요소)
  • 변수에 구상 클래스의 레퍼런스를 저장하지 않는다.
  • 구상 클래스에서 유도된 클래스를 만들지 않는다.
  • 베이스 클래스에 이미 구현되어있는 메소드를 오버라이드하지 않는다.

2.2 피자 가게 프레임워크 만들기

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); // 팩토리 메서드
}

2.3 서브 클래스 만들기

서브 클래스에서 구상 클래스 인스턴스를 생성한다.

public class MYPizzaStore extends PizzaStore {
  Pizza createPizza(String item) {
    if(type.equals("cheese")) {
      pizza = new NYCheesePizza();
    } else if(type.equals("greek")) {
      pizza = new NYGreekPizza();
    } else if(type.equals("pepperoni")) {
      pizza = new NYPepperoniPizza();
    } else {
      pizza = null;
    }
  }
}

2.4 Pizza 클래스 만들기

public abstract class Pizza {
  String name;
  String dough;
  String sauce;
  List<String> toppings = new ArrayList<String>();
  
  void prepare() {
    System.out.pringln("준비중: " + name);
    System.out.pringln("도우를 돌리는 중");
    System.out.pringln("소스를 뿌리는 중");
    System.out.pringln("토핑을 올리는 중");

	for(String topping : toppings) {
      System.out.println(" " + topping);
    }
  }
  
  void bake()  {
    System.out.pringln("175도에서 25분간 굽기");
  }
    
  void cut()  {
    System.out.pringln("피자를 자르기");
  }
    
  void box()  {
    System.out.pringln("피자를 포장하기");
  }
}

// 구상 서브 클래스
public class NYCheesePizza extends Pizza {
  public NYCheesePizza() {
    name = "뉴욕 스타일 소스와 치즈 피자";
    dough = "씬 크러스트 도우";
    sauce = "마리나라 소스";
    toppings.add("잘게 썬 레지아노 치즈");
  }
}

3. 추상 팩토리 패턴(Abstract Factory Pattern)

  • 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공한다.
  • 구상 클래스는 서브 클래스에서 만든다.
  • 추상 팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스로 일련의 제품을 공급받을 수 있다. 따라서 클라이언트와 팩토리에서 생산되는 제품을 분리할 수 있다. 
  • 객체 구성(composition)으로 객체를 생성하며 제품군을 만드는 추상 형식을 제공한다. 제품이 생산되는 방법은 이 형식의 서브클래스에서 정의한다. 

3.1 원재료를 생산하는 팩토리용 인터페이스 생성

public interface PizzaIngredientFactory {
  public Dough createDough;
  public Sauce createSauce;
  public Cheese createCheese;
  public Veggies[] createVeggies;
  public Pepperoni createPepperoni;
  public Clams createClams;
}

3.2 원재료(Ingredients) 클래스 생성 

// 뉴욕 원재료 팩토리
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() };
     return veggies;
  }
  
  public Pepperoni createPepperoni() {
    return new SlicedPepperoni();
  }
  
  public Clams createClams() {
    return new FreshClams();
  }
}

3.3 Pizza 클래스 변경

public abstract class Pizza {
  String name;
  Dough dough;
  Sauce sauce;
  Veggies[] veggies;
  Cheese cheese;
  Pepperoni pepperoni;
  Clams clams;
  
  abstract void prepare();
  
  void bake()  {
    System.out.pringln("175도에서 25분간 굽기");
  }
    
  void cut()  {
    System.out.pringln("피자를 자르기");
  }
    
  void box()  {
    System.out.pringln("피자를 포장하기");
  }
}

// 구상 서브 클래스
public class CheesePizza extends Pizza {
  PizzaIngredientFactory  ingredientFactory;
  
  public CheesePizza(PizzaIngredientFactory ingredientFactory) {
    this.ingredientFactory = ingredientFactory;
  }
  
  void prepare() {
    System.out.println("준비중: " + name);
    dough = ingredientFactory.createDough();
    sauce = ingredientFactory.createDough();
    cheese = ingredientFactory.createCheese();
  }
}

3.4 PizzaStore 구현 클래스 생성

public class NYPizzaStore extends PizzaStore {
  protected Pizza createPizza(String item) {
    Pizza pizza = null;
    PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
    
    if(item.equals("cheese")) {
      pizza = new CheesePizza(ingredientFactory);
    } else if(item.equals("veggie")) {
      pizza = new VeggiePizza(ingredientFactory);
    } 
    // ... 
    return pizza;
  }
}
Comments