관리 메뉴

개발자의 스터디 노트

자바 디자인패턴 - 팩토리 패턴(Factory Pattern) 본문

자바/인스턴트코드

자바 디자인패턴 - 팩토리 패턴(Factory Pattern)

박개발씨 2022. 4. 13. 00:07

이번 포스팅은 

https://jusungpark.tistory.com/14

 

디자인패턴 - 팩토리 패턴 (factory pattern)

팩토리 패턴 (factory pattern) 팩토리 메소드 패턴 : 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를  만들지는 서브클래스에서 결정하게 만든다. 즉 팩토리 메소드 패

jusungpark.tistory.com

위 포스팅을 보고 작성하였습니다.

 

 

 

 

 

 

팩토리 패턴이란

 - 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듦. 즉 팩토리 메서드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것.

 

추상 팩토리 패턴이란

 - 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성.

 

 Duck duck;
 if ( type == picnic ) duck = new MallardDuck();
 else if ( type == hunting ) duck = new DecoyDuck();
 else if ( type == inBathTub) duck = new RubberDuck();

위와 같이 코드를 구성하면 뭔가 변경하거나 확장해야 할 때 코드를 다시 확인하고 추가 또는 제거해야 한다는 것을 의미합니다. 이렇게 구상 클래스를 바탕으로 코딩을 하면 나중에 코드를 수정해야 할 가능성이 높아지고, 유연성이 떨어지는 것을 확인할 수 있습니다. 즉 변화에 대해 닫혀 있는 코드가 되어버리는 것입니다.

바뀔 수 있는 부분을 찾아내서 바뀌지 않는 부분하고 분리시켜야 합니다.

 

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

구상 클래스를 이용하여 객체를 생성하는 부분을 캡슐화해보겠습니다. 그리고 새로 만들 객체에는 팩토리(Factory)라는 이름을 붙이기로 합니다.

 

 

간단한 피자 팩토리를 만듭니다.

public class SimplePizzaFactory {	
	public Pizza createPizza(String type){ //이런 경우에는 static메소드로 선언하는 경우가 종종 있음.

		Pizza pizza = null;
		if(pizza.equals("cheese")) pizza = new CheesePizza();
		if(pizza.equals("pepper")) pizza = new PepperoniPizza();
		if(pizza.equals("clam")) pizza = new ClamPizza();
		if(pizza.equals("veggie")) pizza = new VeggiePizza();
		return pizza;
	}
 }

 

간단한 팩토리(Simple Factory)

public class PizzaStore{

	SimplePizzaFactory simplePizzaFactory;

	public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
		this.simplePizzaFactory = simplePizzaFactory;
	}

	public Pizza orderPizza(String type){
    
		Pizza pizza;
		pizza = simplePizzaFactory.createPizza(type);
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}
 }

간단한 팩토리는 디자인 패턴이라고 할 수는 없습니다.

 

피자 가게가 사업이 확장되어 여러 지역별로 각각의 다른 스타일의 피자를 만들어 내야 하는 문제가 생겼습니다. 모든 프랜차이즈 분점에서 PizzaStore 코드를 사용하여 진행을 합니다.

 

- 간단한 팩토리를 사용한다면?

- SimplePizzaFactory를 빼고 세 가지 서로 다른 팩토리( NYPizzaFactory, ChicagoPizzaFactory, CalifornizPizzaFactory)를 만들어서 적용한다면?

 PizzaStore nyStore = new PizzaStore(new NYPizzaFactory());
 nyStore.orderPizza("cheese"); 

 PizzaStore chicagoStore = new PizzaStore(new ChicagoPizzafactory());
 chicagoStore.orderPizza("cheese");

이런 식으로 구현을 하다 보면 각 팩토리를 가진 피자가게 체인점들이 서로의 구현 방식이 달라지는 일이 발생할 수도 있게 됩니다.

 

1. 팩토리 메서드 패턴

피자가게와 피자 제작 과정 전체를 하나로 묶어주는 프레임워크를 만들어야 위에서 언급한 문제가 발생하지 않습니다.

public abstract class PizzaStore{

	public Pizza orderPizza(String type){

		Pizza pizza;	
		pizza = createPizza(type);	

		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza;
	}

	abstract Pizza createPizza(String type); //Pizza 인스턴스를 만드는 일은 팩토리 역할을 하는 메소드에서 맡아 처리
 }

이제 각 분점을 위한 지역별로 서브클래스를 만들어 줘야 한다. 피자의 스타일은 각 서브클래스에서 결정.

 

이제 ChicagoPizzaStore, NYPizzaStore에는 구상 피자 클래스를 분기해주는 각각의 createPizza 메서드가 있습니다.

 

- 뉴욕 피자가게

public class NYPizzaStore extends PizzaStore{

	@Override
	public Pizza createPizza(String type){

		Pizza pizza = null;
		if(type.equals("cheese")) pizza = new NYStyleCheesePizza();
		if(type.equals("peper")) pizza = new NYStylePepperoniPizza();
		if(type.equals("clam")) pizza = new NYStyleClamPizza();
		if(type.equals("veggie")) pizza = new NYStyleVeggiePizza();
		return pizza;
	}

 }

 

- 시카고 피자가게

 public class ChicagoPizzaStore extends PizzaStore{

	@Override
	public Pizza createPizza(String type){

		Pizza pizza = null;
		if(type.equals("cheese")) pizza = new ChicagoStyleCheesePizza();
		if(type.equals("peper")) pizza = new ChicagoStylePepperoniPizza();
		if(type.equals("clam")) pizza = new ChicagoStyleClamPizza();
		if(type.equals("veggie")) pizza = new ChicagoStyleVeggiePizza();
        
		return pizza;
	}
 }

 

- 피자를 만드는 추상 클래스

public abstract class Pizza{
	String name;
	String dough;
	String sauce;
	ArrayList<String> toppings = new ArrayList<>();
	
	public void prepare(){
		System.out.println("Preparing : "+name);
		System.out.println("Tossing dough...");
		System.out.println("Adding source");
		System.out.println("Adding toppings");
		for (String topping : toppings) {
			System.out.println("\ttopping : "+topping);
		}
	}
	
	public void bake(){
		System.out.println("Bake for 25 minutes at 350");
	}
	
	public void cut(){
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	public void box(){
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	public String getname(){
		return this.name;
	}
	
 }

 

- 뉴욕 스타일 치즈피자

public class NYStyleCheesePizza extends Pizza{

	public NYStyleCheesePizza() {

		this.name = "NY Style CheesePizza";
		this.dough = "Thin Crust Dough";
		this.sauce = "Marinara Sauce";
		this.toppings.add("Grated Reggiano Cheese");
	}
 }

 

- 시카고 스타일 치즈피자

public class ChicagoStyleCheesePizza extends Pizza{

	public ChicagoStyleCheesePizza() {

		this.name = "Chicago Style CheesePizza";
		this.dough = "Extra Thick Crust Dough";
		this.sauce = "Plum Tomato Sauce";
		this.toppings.add("Shredded mozzarella Cheese");
	}	

	@Override
	public 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 nySytpePizza = nyStore.orderPizza("cheese");
		System.out.println(nySytpePizza.getname());
		System.out.println();
		Pizza chicagoStypePizza = chicagoStore.orderPizza("cheese");
		System.out.println(chicagoStypePizza.getname());

	}

 }

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

 

- 객체를 생산하는 생산자 클랙스

 

 

- 제품을 생산하는 제품 클래스

 

위의 클래스 다이어 그램을 보면 생산을 담당하는 PizzaStore 추상 클래스에서 객체를 만들기 위한 메서드, 즉 팩토리 메서드를 위한 인터페이스를 제공한다는 것을 알수 있습니다. PizzStore에 구현되어 있는 다른 메소드 orderPizza에서는 팩토리 메소드에 의해 생산된 제품을 가지고 필요한 작업을 처리합니다. 하지만 실제 팩토리 메소드를 구현하고 제품(객체 인스턴스)을 만들어 내는 일은 서브 클래스에서만 할 수 있습니다.

 

객체 지향 디자인 원칙

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

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

 

의존성 뒤집기 원칙이란?

PizzaStore -> NYStyleCheesePizza

PizzaStore -> ChicagoStypeCheesePizza

PizzaStore -> NYStyleVeggiePizza


이런 식으로 의존이 되던 디자인이

 

PizzaStore -> Pizza

Pizza <- NYStyleCheesePizza

Pizza <- ChicagoStyleCheesePizza

Pizza <- NYStyleVeggiePizza


이런식으로 의존관계가 뒤집어지는 현상

 

팩토리 메서드 패턴을 적용하고 나면 고수준 구성요소(PizzaStore)와 저수준 구성요소(NYStyleCheesePizza,ChicagoStylePizza,...) 들이 모두 추상 클래스인 Pizza에 의존하게 됩니다.(고수준 모듈과 저수준 모듈이 둘다 하나의 추상클래스에 의존)

팩토리 메소드 패턴이 의존성 뒤집기 원칙을 준수하기 위해 쓸 수 있는 유일한 기법은 아니지만 가장 적합한 방법 가운데 하나입니다.

 

의존성 뒤집기 원칙에 위배되는 객체지향 디자인을 피하는데 도움이 되는 가이드.

 

 1. 어떤 변수에도 구상 클래스에 대한 레퍼런스를 지정하지 않는다.

     - new 연산자를 사용하면 레퍼런스를 사용하게 되는 것.

 

 2. 구상 클래스에서 유도된 클래스를 만들지 않는다.

     - 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 됨, 추상화된 것을 사용해야 함.

 

 3. 베이스 클래스에 이미 구현되어 있던 메서드를 오버라이드 하지 않는다.

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

        볼 수 있다. 베이스 클래스에서 메서드를 정의할 때는 모든 서브 클래스에서 공유할 수 있는 것만 정의해야 함.



 

 

2. 추상 팩토리 패턴

 

원재료  공장을 만듭니다.

 

1. 지역별로 팩토리를 만들어 각 생성 메서드를 구현하는 PizzaingredientFactory 클래스를 만들어야 함.

2. ReggianoiCheese, RedPeppers, ThickCrustDough와 같이 팩토리에서 사용할 원재료 클래스들을 구현한다.

3. 만든 원재료 공장을 PizzaStore 코드에서 사용하도록 함으로써 모든 것을 하나로 묶어준다.

 

 public interface PizzaIngredientFactory {

	public Dough createDough();
	public Sauce createSauce();
	public Cheese createCheese();
	public Veggies[] createVeggies();
	public Pepperoni createPepperoni();
	public Clams createClams();

 }
public class NYPizzaingredientFactory implements PizzaIngredientFactory{

	@Override
	public Dough createDough() {
		return new ThinCrustdough();
	}

	@Override
	public Sauce createSauce() {
		return new MarinaraSauce();
	}

	@Override
	public Cheese createCheese() {
		return new ReggianoCheese();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };
		return veggies;
	}

	@Override
	public Pepperoni createPepperoni() {
		return new SlicedPepperoni();
	}

	@Override
	public Clams createClams() {
		return new Freshclams();
	}

 }
public class ChicagoPizzaingredientFactory implements PizzaIngredientFactory{
	@Override
	public Dough createDough() {
		return new ThickCrustDough();
	}

	@Override
	public Sauce createSauce() {
		return new PlumTomatoSauce();
	}

	@Override
	public Cheese createCheese() {
		return new MozzarellaCheese();
	}

	@Override
	public Veggies[] createVeggies() {
		Veggies veggies[] = { new BlackOlives(), new Spinach(), new EggPlant()};
		return veggies;
	}

	@Override
	public Pepperoni createPepperoni() {
		return new Slicedpepperoni();
	}

	@Override
	public Clams createClams() {
		return new FrozenClam();
	}
 }
public abstract class Pizza{
	String name;
	Dough dough;
	Sauce sauce;
	Veggies veggies[];
	Cheese cheese;
	Pepperoni pepperoni;
	Clams clams;
	
	public abstract void prepare(); //추상 메소드로 변경됨.
	
	public void bake(){
		System.out.println("Bake for 25 minutes at 350");
	}
	
	public void cut(){
		System.out.println("Cutting the pizza into diagonal slices");
	}
	
	public void box(){
		System.out.println("Place pizza in official PizzaStore box");
	}
	
	public String getname(){
		return this.name;
	}
 }
public class CheesePizza extends Pizza{

	PizzaIngredientFactory ingredientFactory;	

	public CheesePizza(PizzaIngredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}

	@Override
	public void prepare() {
		this.dough = ingredientFactory.createDough();
		this.sauce = ingredientFactory.createSauce();
		this.cheese = ingredientFactory.createCheese();
	}
 }
public class ClamPizza extends Pizza{

	PizzaIngredientFactory ingredientFactory;	

	public ClamPizza(PizzaIngredientFactory ingredientFactory) {
		this.ingredientFactory = ingredientFactory;
	}

	@Override
	public void prepare() {
		this.dough = ingredientFactory.createDough();
		this.sauce = ingredientFactory.createSauce();
		this.cheese = ingredientFactory.createCheese();
		this.clams = ingredientFactory.createClams();
	}
 }
public class NYPizzaStore extends PizzaStore{

	@Override
	public Pizza createPizza(String type){

		Pizza pizza = null;
		PizzaIngredientFactory ingredientFactory = new NYPizzaingredientFactory();

		if(type.equals("cheese")){
			pizza = new CheesePizza(ingredientFactory);
			pizza.setName(ingredientFactory.NY_STYLE+" Cheese Pizza");
		}else if(type.equals("peper")){
			pizza = new PepperoniPizza(ingredientFactory);
			pizza.setName(ingredientFactory.NY_STYLE+" Pepperoni Pizza");
		}else if(type.equals("clam")){
			pizza = new ClamPizza(ingredientFactory);
			pizza.setName(ingredientFactory.NY_STYLE+" Clam Pizza");
		}else if(type.equals("veggie")){
			pizza = new VeggiePizza(ingredientFactory);
			pizza.setName(ingredientFactory.NY_STYLE+" Veggie Pizza");
		}

		return pizza;
	}
 }

 

전체적인 흐름은

 

1. 뉴욕 피자가게를 만든다.

 - 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 = nyIngredientFactory.createDought();

   sauce = nyIngredientFactory.createSauce();

  cheese = nyIngredientFactory.createCheese();

}

6. 준비단계가 끝나고 orderPizza() 메서드에서는 피자를 굽고, 자르고, 포장한다.

 

 

 

 

 

 

 

 

추상 팩토리 패턴 : 제품군을 생성하기 위한 인터페이스를 생성 그 인터페이스를 구성하여 사용할 수 있게끔 하는 것.

추상 메서드 패턴 : 하나의 추상 클래스에서 추상 메서드를 만들고 서브클래스들이 그 추상 메서드를 구현하여 인스턴스를 만들게끔 하는 것.