본문 바로가기
스터디/헤드퍼스트 디자인패턴

[헤드퍼스트 디자인패턴] 05. 팩토리 패턴(Factory Pattern)

by 디토20 2022. 7. 3.
반응형

 

 

 

[헤드퍼스트 디자인패턴] 05. 팩토리 패턴(Factory Pattern)

 

 

1. 두가지 팩토리 패턴

추상 팩토리 패턴

- 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생성하는 인터페이스를 제공한다. 구상 클래스는 서브클래스에서 만든다.

 

팩토리 메소드 패턴

- 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다. 팩토리 메소드를 사용하면 인스턴스 만드는 일을 서브 클래스에 맡길 수 있다.

 

 

 

2. 요구사항

- 피자 가게에서 여러 피자를 판매 한다.

 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){ 
        Pizza pizza = null;		
        
        if(type.equals("cheese")) pizza = new CheesePizza();		
        else if(type.equals("pepper")) pizza = new PepperoniPizza();		
        else if(type.equals("clam")) pizza = new ClamPizza();		
        else if(type.equals("veggie")) pizza = new VeggiePizza();		

        return pizza;	
    } 
}


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

위와 같이 설계하는 것은 "간단한 팩토리"로, 이것은 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다.

 

 

 

3. 요구 사항의 수정

- 피자 가게가 사업이 확장되어 여러 지역에 지점을 내고, 각 지점마다 그 지역의 특성과 입맛을 반영한 다양한 스타일의 피자를 만들어야 한다.

 

만약 앞에서 배운 간단한 팩토리를 사용한다면 어떻게 될까?

SimplePizzaFactory 를 빼고 세가지 서로 다른 팩토리 (NYPizzaFactory, ChicagoPizzaFactory, CalifornizPizzaFactory)를 만들어서 적용해보자.

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

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

지점에서 본사에서 만든 팩토리 패턴을 사용하긴 하는데, 굽는 방식이 달라진다거나 상자의 모양이 맞지 않는 일이 생긴다. 이 문제를 해결하려면 PizzaStore와 피자 제작 코드 전체를 하나로 묶어주면서, 유연성을 잃어 버리지 않는 프레임워크를 만들어야 한다.

 

 

 

4. 팩토리 메소드 패턴

피자를 만드는 일 전부를 PizzaStore 클래스에 진행하면서도 지점의 스타일을 살릴수 있는 방법이 있다. 이번에는 createPizza()를 다시 PizzaStore에 추상메소드로 선언하고, 지역별 스타일에 맞게 PizzaStore의 서브 클래스를 만든다.

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); // 팩토리 메소드가 PizzaStore의 추상 메소드로 바뀌었다. 
}

이제 각 지점에 맞는 서브클래스를 만들어 보자. 피자의 스타일은 각 서브 클래스에서 결정 한다.

 

public class NYPizzaStore extends PizzaStore{

    @Override	
    public Pizza createPizza(String item){			
        if(item.equals("cheese")) return new NYStyleCheesePizza();		
        if(item.equals("peperoni")) return new NYStylePepperoniPizza();		
        if(item.equals("clam")) return new NYStyleClamPizza();		
        if(item.equals("veggie")) return new NYStyleVeggiePizza();		
        return null;
    } 
} 


public class ChicagoPizzaStore extends PizzaStore{	

    @Override	
    public Pizza createPizza(String item){		
        if(item.equals("cheese")) return new ChicagoStyleCheesePizza();		
        if(item.equals("peper")) return new ChicagoStylePepperoniPizza();		
        if(item.equals("clam")) return new ChicagoStyleClamPizza();		
        if(item.equals("veggie")) return new ChicagoStyleVeggiePizza();	
        return null;
	} 
}

 

피자를 실제로 만들어 보자.

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(pizza.getname());		
        
        pizza = chicagoStore.orderPizza("cheese");		
        System.out.println(pizza.getname());	
    } 
}

모든 팩토리 패턴은 객체 생성을 캡슐화 한다. 슈퍼클래스는 어떤 스타일의 피자가 만들어졌는지 전혀 알지 못한다. 다만 피자라는 건 알고 있기 때문에 그 피자를 준비하고, 굽고, 자르고, 포장하는 작업을 완료한다.

 

이렇게 함으로써, 기존에 모든 피자 객체를 PizzaStore 클래스 내에서 직접 만들었을 때의 심하게 의존적인 PizzaStore의 의존성을 줄일 수 있다.

 

디자인 원칙

추상화된 것에 의존하게 만들고
구상 클래스에 의존하지 않게 만든다.

 

 

5. 추상 팩토리 패턴

- 지역마다 같은 메뉴여도, 재료가 다를 수 있어 원재료를 생산하는 팩토리를 만들어 본다.

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 abstract class Pizza{	
    String name;	
    
    Dough dough;	
    Sauce sauce;	
    Veggies veggies[];	
    Cheese cheese;	
    Pepperoni pepperoni;	
    Clams clams;		

    abstract void prepare(); //추상 메소드로 변경됨	

    //기타 메소드
}
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();	
    } 
}

Pizza의 prepare()를 추상 메소드화 하고, 구상 클래스에서 원재료를 팩토리에서 바로 가져와 prepare()를 구현하면, 피자마다 클래스를 지역별로 따로 만들 필요가 없다.

 

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

 

 

6. 정리

  • 팩토리 패턴을 쓰면 객체 생성을 캡슐화할 수 있다.
  • 팩토리 메소드 패턴에서는 상속을 활용한다. 객체 생성이 서브클래스에게 위임된다. 서브 클래스에서는 팩토리 메소드를 구현하여 객체를 생산한다.
  • 추상 팩토리 패턴에서는 객체 구성을 활용한다. 객체 생성이 팩토리 인터페이스에서 선언한 메소스들에서 구현된다.
  • 모든 팩토리 패턴에서는 애플리케이션의 구상 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합을 도와준다.
  • 추상 팩토리 패턴은 구상 클래스에 직접 의존하지 않고도 서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰인다.
  • DIP에 따르면 구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있다.
  • 팩토리는 구상 클래스가 아닌 추상 클래스, 인터페이스에 맞춰 코딩할 수 있게 해주는 강력한 기법이다.
728x90
반응형

댓글