[디자인패턴] 팩토리 메서드 패턴

seheo·2022년 9월 18일
0

Software Engineering

목록 보기
4/5

1.팩토리 메소드 패턴

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만든다.
팩토리 메소드 패턴을 통해 클래스의 인스턴스를 만드는 일을 서브클래스에 맡길 수 있다.

클래스 다이어그램

생산자(Creator) 클래스

  • Product 타입의 객체를 반환하는 팩토리 메서드(추상 메소드)를 선언하는 클래스
  • 팩토리 메서드를 기본적으로 구현, ConcreteProduct 객체를 반환한다.
  • Product 객체의 생성을 위해 팩토리 메서드를 호출한다.

ConcreteCreator 클래스

  • 실제로 제품을 생산하는 팩토리 메서드를 구현

제품(Product) 클래스

  • 팩토리 메서드가 생성하는 객체(추상 클래스)의 인터페이스 정의한다.

ConCreteProduct 클래스

  • Product 클래스에 정의된 인터페이스를 실제로 구현하는 클래스

팩토리 메서드 패턴를 통한 피자가게 구현

병렬 클래스 계층구조

심하게 의존적인 PizzaStore

객체지향의 팩토리 개념이 없이 피자가게를 만든다고 생각해보자

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

객체 의존성 살펴보기


위 소스의 객체 의존성의 다이어그램으로 그려보면 다음과 같습니다

  • PizzaStore는 모든 피자 객체들을 직접 생성해야 하므로, 모든 피자 객체들에게 직접적으로 의존하게 된다.
  • 그렇기 떄문에 피자 클래스들의 구현이 변경되면 PizzaStore까지 고쳐야 할 수도 있다.
  • 나중에 지역이 많아지면 많아질수록 더욱 힘들어질것이다. 왜냐하면, PizzaStore 코드또한 변경되기 때문이다.
  • 피자 종류를 추가 할때마다 더 많은 피자 객체에 의존하게 됩니다.

DIP (Dependency Inversion) 의존 역전 원칙

SOLID 원칙의 D에 해당하는 의존 역전의 원칙을 적용하여 구상 클래스에 대한 의존성을 줄이는것이 좋다는 것이 좋다는것을 알고 있을 것이다.

DIP란

  • 추상화된 것에 의존하도록 만들어라.
  • 구상 클래스에 의존하도록 만들지 않도록 한다.
  • 고수준 구성요소가 저수준 구성요소에 의존하면 안된다.(고수준, 저수준 모듈이 둘 다 하나의 추상 클래스에 의존하는 것)

"고수준" 구성요소는 다른 "저수준" 구성요소에 의해 정의되는 행동이 들어있는 구성요소

  • 고수준: PizzaStore class
    PizzaStore는 다양한 피자 객체를 만들고, 피자를 준비, 굽고, 자르고, 포장
    PizzaStore에서 행동은 피자의 의해 정의
  • 저수준: Pizza class

DIP 적용 PizzaStore

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

원재료 공장 + 팩토리 메소드 적용 Pizza 코드

원재료 공장 interface

public interface PizzaIngredientFactory {
 	
  //각 재료별로 생성 메소드 정의.
  public abstract Dough createDough();
  public abstract Sauce createSauce();
  public abstract Cheese createCheese();
  public abstract Veggies[] createVeggies();
  public abstract Pepperoni createPepperoni();
  public abstract Clams createClam();
}

원재료 공장 인터페이스 적용.

  1. 지역별로 팩토리를 만든다. 각 생성 메소드를 구현하는 PizzaIngredientFactory 클래스를 만들어야 한다.
  2. Reggianocheese, RedPeppers, ThickCrustDough와 같이 팩토리에서 사용할 원재료 클래스들을 구현한다. 상황에 따라 서로 다른 지역에서 같은 재료 클래스를 쓸 수도 있다.
  3. 새로 만든 원재료 공장을 PizzaStore 코드에서 사용하도록 함으로써 모든 것을 하나로 묶어줘야 된다.
    모든 팩토리 인스턴스에서 공통적으로 사용하는 부분이 있으면 인터페이스가 아니라 추상클래스로 만들어된다.

원재료 공장 class

//========================================================================
//모든 재료 공장에서 구현해야 하는 인터페이스를 구현
//========================================================================
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 class

public abstract class Pizza {
  String name;
  String dough;
  String sauce;
  Veggies veggies[];
  Cheese cheess;
  Pepperoni pepperoni;
  Clams clam;
  
  abstract void prepare();
  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;
  }
  
  String getName(){
  	return name;
  }
  
  public String toString() { 
  	//피자 이름 출력
  }
}
  1. prepare()를 추상 메소드로 만든다. 이 부분에서 피자를 만드는 데 필요한 재료들을 원재료 팩토리에서 가져와 정돈

CheesePiza class

뉴욕 피자 클래스와 시카고 피자 클래스의 차이점을 무엇일까?
이들은 다른 재료를 사용하는 것 빼면 다른 점이 없다.
따라서, 원재료 공장을 통해 다른 재료를 준비한다면 지역별로 따로 클래스를 만들 필요가 없다.

pulic 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();
    }
}
  1. 생성자를 통해 각 피자 클래스는 원재료를 공급 받는다
  2. prepare() 함수에서 각 PizzaStore 원재료에 맞는 피자를 생성

PizzaStore class

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);
}
public class NYPizzaStore extends PizzaStore{
	protected Pizza createPizza(String item){
    	Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaStoreIngredientFactory();
    
    	if (item.equls("chesse")){
    		pizza = new CheesePizza(ingredientFactory);
            pizza.setName("New York Style Cheese Pizza);
	   	} else if (item.equls("veggie")){
    		pizza = new VeggiePizza(ingredientFactory);
            pizza.setName("New York Style Veggie Pizza);
	   	} else if (item.equls("clam")){
    		pizza = new ClamPizza(ingredientFactory);
            pizza.setName("New York Style Clam Pizza);
	   	} else if (item.equls("pepperoni")){
    		pizza = new PepperoniPizza(ingredientFactory);
            pizza.setName("New York Style Pepperoni Pizza);
	   	}     
        return pizza;
}
  1. 뉴욕 공장에는 뉴욕 피자 원재료 공장을 전달
  2. 각 피자를 만들때 사용할 재료를 각 피자 객체에 전달

장단점

  • 장점: Factory Method 패턴의 가장 큰 장점은 지금까지 본 것처럼 수정에 닫혀있고 확장에는 열려있는 OCP 원칙을 지킬 수 있다는 점입니다.
  • 단점: 간단한 기능을 사용할 때보다 많은 클래스를 정의해야 하기 때문에 코드량이 증가합니다.

참고자료

헤드퍼스트 디자인패턴, 한빛미디어, 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠 저

0개의 댓글