[Design Pattern] Factory Pattern

younghyun·2022년 10월 25일
0

Design Pattern

목록 보기
5/14
post-thumbnail

Factory Pattern

  • 등장 이유: 실제로 구현되는 클래스의 객체를 생성할 때 객체의 종류가 달라지면 클라이언트 코드를 수정해야 하는 것이 너무 많음
  • 해결 방안: 생성을 분리해서 캡슐화 시킴
  • 결과: 사용할 객체가 많거나 객체를 생성하는 방법이 변경되어도 연쇄적인 수정이 적어짐
  • 핵심: 클래스의 인스턴스를 만드는 것을 서브클래스에서 결정하도록 한다

Factory Method Pattern 이란

객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 한다. 즉, new 키워드를 사용하는 부분을 서브클래스에 위임함으로서 객체 생성을 캡슐화하고 구체적인 클래스에 대한 의존성을 줄인다.

  • 등장 이유: 객체를 생성하는 'new'의 문제 (생성할 구현 클래스가 늘어나면 클래스의 변경에 따른 클라이언트 코드 변경이 많아짐)
  • 해결 방안: 수퍼클래스에 있는 클라이언트 코드와 서브클래스에 있는 객체 생성 코드를 분리시킴
    abstract Product factoryMethod(String type)
  • 결과: 사용할 객체가 많거나 객체를 생성하는 방법이 변경되어도 연쇄적인 수정이 적어짐
  • 특징
    • 팩토리 메소드는 특정 객체를 반환
    • 팩토리 메소드는 클라이언트(수퍼클래스의orderPizza())에서 실제로 생성되는 실제 객체가 무엇인지 알 수 없게 만드는 역할

Abstract Factory Pattern 이란

인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구체적인 클래스를 지정하지 않고도 생성할 수 있는 디자인 패턴이다.

  • 추상 팩토리를 통해서 제품군을 생성하기 위한 인터페이스를 제공할 수 있음
  • 인터페이스를 이용하는 코드를 만들면 코드를 제품을 생산하는 실제 팩토리와 분리시킬 수 있음
  • 코드가 실제 제품과 분리되어 있으므로 다른 공장을 사용하면 다른 결과를 얻을 수 있음

⭐ 디자인 원칙1: 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.

⭐ 디자인 원칙2: 추상된 것에 의존하도록 만들어야한다. 구상 클래스에 의존하지 않도록 만든다.

예시 (Simple Factory 메소드)

피자 주문 어플리케이션

  • 메뉴 => 변경이 잦음
    • 기존 메뉴: CheesePizza , GreekPizza, PepperoniPizza
    • 리뉴얼 후: CheesePizza , PepperoniPizza, ClamPizza, Veggie Pizza
  • 기능: order, prepareToBoxing(prepare, bake, cut, box) => 변경이 없음

Bad Case

Pizza orderPizza(){
    Pizza pizza = new Pizza();
    
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    pizza.pizza();
}
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();
    } else if(type.equals("clam")){    // 메뉴 추가
        pizza = new ClamPizza();
    } else if(type.equals("veggie")){   // 메뉴 추가
        pizza = new VeggiePizza();
    }
    
    // 바뀌지 않는 부분
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    pizza.pizza();
}

문제점: 피자 메뉴가 수정될 때마다 orderPizza 메소드는 해당 부분의 소스코드를 수정해야 한다.
=> 소스코드 변경에 대해서 닫혀있지 않다

바뀌는 부분과 바뀌지 않는 부분의 분리가 필요하다.
=> 메뉴 코드기능 코드의 분리
=> (메뉴 코드 담당 = 객체 생성 처리)하는 Factory 클래스 생성

Good Case

SimplePizzaFactory (객체 생성을 처리하는 클래스)

public class SimplePizzaFactory{
    
    // 피자 객체 생성을 위한 메소드
    public Pizza createPizza(String type){
        Pizza pizza = null;
 
        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;
    }
}

수정된 orderPizza

public class PizzaStore{
    SimplePizzaFactory factory;
    
    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }
 
    public Pizza orderPizza(String type){
        Pizza pizza;
 
        // 객체생성을 factory에 맡긴다.
        pizza = factory.createPizza(type);
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        pizza.pizza();
 
    }
}

예시 확장 (팩토리 메소드 패턴 구현)

프렌차이즈 피자집 주문 어플리케이션

  • 메뉴: 같은 메뉴여도 지역별 분점마다 다른 스타일을 만들어야 함 (ex. 뉴욕은 치즈 적게, 시카고는 치즈 많이)
  • 기능: 모든 분점이 같음
// 뉴욕 지역을 위한 피자 인스턴스를 관리하는 클래스 NYPizzaFactory
NYPizzaFactory nyFactory = new NYPizzaFactory();  
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.order("Veggie");
// 시카고 지역을 위한 피자 인스턴스를 관리하는 클래스 NYPizzaFactory
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.order("Veggie");
  • 문제점

    • PizzaStore가 피자 생성 과정과 분리되어 있어, 유연성은 보장되나, 일괄적인 처리가 어려울 수 있음
    • 피자 스토어마다 다른 처리 과정이 나타날 수 있음
  • 해결방법
    : PizzaStore와 피자 제작 과정 전체를 묶어주면서 유연성은 지키는 방법

    • PizzaStore를 추상클래스로 변경 (PizzaStore 클래스를 인스턴스화 할 수 없음)
    • createPizza()를 PizzaStore에 넣고, 추상 메소드로 정의 (서브클래스에서 피자 인스턴스 생성 가능)
    • orderPizza()를 추상 클래스에서 구현 (서브 클래스에서 수정하지 않는다면 orderPizza() 에서 정의한 동작을 따라야 함)

Good Case

  • Factory 메소드 (객체를 생성하는 메소드): createPizza
  • 서브 클래스 (어떤 클래스의 인스턴스를 만들것인지 결정하고 객체를 생성): NYPizzaStore, ChicagoPizzaStore

PizzaStore

public abstract class PizzaStore {
    void prepareToBoxing(Pizza pizza) {
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }
    
    public Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);   // pizza 객체 생성을 외부에 위임
        prepareToBoxing(pizza);
        return pizza;    // 객체 반환
    }
    
    // factory 메소드
    abstract Pizza createPizza(String type);
}
  • 장점: PizzaStore에서는 어떤 종류의 피자가 만들어 질것인지 전혀 알지 못한다. 단지 주문으로 들어온 피자를 준비하고 굽고 자르고 포장할 뿐이다.

느슨한 결합 !!! Pizza 객체 생성과 pizza의 주문처리가 클래스로 완전히 분리됨

뉴욕 PizzaStore

public class NYPizzaStore extends PizzaStore{
    
    Pizza createPizza(String type){
        if(type.equals("cheese")){
            return new CheesePizza();    
        } else if(type.equals("pepperoni")){
            return new PepperoniPizza();
        } else if(type.equals("clam")){
            return new ClamPizza();
        } else if(type.equals("veggie")){
            return new VeggiePizza();
        } else return null;
        
    }
}

Pizza (기능 구현)

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 minutes 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;
    }
}

뉴욕 스타일 CheezePizza

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");
    }
}

시카고 스타일 CheezePizza

public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza () {
    
        name = "Chicago Style Deep Dish 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");   // 사각형 형태로 자르기 위해 cut 메소드 오버라이드하여 새로 구현
    }
}

Main 함수

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 = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }
}

예시 확장 (추상 팩토리 메소드 구현)

분점마다 원재료 생산 공장을 가진 프랜차이즈 피자집 주문 어플리케이션

  • 메뉴: 같은 메뉴여도 지역별 분점마다 사용하는 재료가 다름 (ex.신선한 조개/냉동 조개)
    • 지역별 분점마다 재료가 다름 => 해당 지역 원재료 공장에서 만들기
    • 준비과정은 같음 => 지역별로 따로 만들 필요 X
  • 기능: 모든 분점이 같음

원재료를 생산할 팩토리 인터페이스
: 관련되어있는 팩토리 메소드 함수들을 여러개 묶어놓은 클래스를 만들어서 제공

public interface PizzaIngredientFactory {
    public Dough createDough();           // 팩토리 메소드
    public Sauce createSauce();           // 팩토리 메소드
    public Cheese createCheese();         // 팩토리 메소드
    public Veggies[] createVeggies();     // 팩토리 메소드
    public Pepperoni createPepperoni();   // 팩토리 메소드
    public Clams createClam();            // 팩토리 메소드
}

뉴욕 원재료 공장

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

Pizza 클래스

public abstract class Pizza {

    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;
    abstract void prepare();
    
    void bake() {
        System.out.println("Bake for 25 minutes at 350");
    }
    void cut() {
        System.out.println 피자 클래스("Cutting the pizza into diagonal slices");
    }
    void box() {
        System.out.println("Place pizza in official PizzaStore box");
    }
    void setName(String name) {
        this.name = name;
    }
    String getName() {
        return name; 
    }
    public String toString() {
        // 피자 이름 출력
    }
}

NYCheesePizza와 ChicagoCheesePizza 클래스

: 코드가 같음 (재료만 다를 뿐 결국 준비 단계는 같기 때문에 지역별로 따로 만들 필요가 없음)

CheezePizza

public class CheesePizza extends Pizza {

    PizzaIngredientFactory ingredientFactory;  // 지역별로 다른 재료 원재료 공장에서 가져오기
    
    public CheesePizza(PizzaIngredientFactory ingredientFactory) {
        this.ingredientFactory = ingredientFactory;
    }
    
    void prepare() {      // 재료 원재료 공장에서 가져온 것 쓰기
        System.out.println("Preparing " + name);
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

뉴욕 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);
            pizza.setName("New York Style Cheese Pizza");
        } else if (item.equals("veggie")) {
            pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza");
        } else if (item.equals("clam")) {}
        return pizza;
    }
}
profile
🌱 주니어 백엔드 개발자입니당

0개의 댓글