osul_world 2021. 12. 29. 20:32
728x90

 

생성패턴: FactoryMethod , AbstarctFactory


 

SimpleFactory 기법


  • 단순히 생성자를 별도의 생성만을 담당하는 클래스로 분리

  • 패턴이라고 하지 않고 기법이라고 한다.

  • 여기서 팩토리 메서드 패턴 및 추상팩토리 패턴이 파생되었다.

 

어떤 상황에서 조건에 따라 객체를 다르게 생성해야 할 때가 있습니다.

image

예를 들면, 사용자의 입력값에 따라 하는 일이 달라질 경우, 분기(if , switch)를 통해 특정 객체를 생성해야 합니다.

객체마다 하는 일이 다르기 때문에 조건문에 따라 객체를 다르게 생성하는 것은 이상한 일이 아닙니다.

 

SimpleFactory는 이렇게 분기에 따른 객체의 생성( new 연산자로 객체를 생성하는 부분 )을 직접하지 않고,

팩토리라는 클래스에 위임하여 팩토리 클래스가 객체를 생성하도록 하는 방식을 말합니다.

팩토리는 말 그대로 객체를 찍어내는 공장을 의미합니다.

 

image

  • PizzaStore의 객체 생성 부분을 SimplePizzaFactory에게 위임
public class PizzaStore {
    private PizzaFactory pizzaFactory;

    public void setPizzaFactory(PizzaFactory pizzaFactory) { //다양한 팩토리 구현체들로 갈아 끼울수 있도록 다형성 보장
        this.pizzaFactory = pizzaFactory;
    }
    /**
    setPizzaFactory
    >객체를 생성할때 생성자로 생성하는 대신에 생성을 담당하는 메서드로 객체를 생성할수도있다. 
    
    A a = new A(); //생성자로 객체 생성하는 경우
    A a  = setPizzaFactory(new Factory()); //method로 객체 생성하는 경우
    
    메서드로 객체를 생성하는 경우 장점
    > 메서드 네이밍을 통한 가독성 향상
    > 복잡한 생성과정 추상화 + 생성과정 은닉화+ 생성에만 집중된 메서드로 SRP설계가능
    */

    public Pizza orderPizza(String type) {
        Pizza pizza = pizzaFactory.createPizza(type);
        // 이 부분은 변하지 않을 부분
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}
public class SimplePizzaFactory extends PizzaFactory{ 
    //객체의 생성을 대신한다.
    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("calm") )
            pizza = new ClamPizza(); 
        return pizza; 
    } 
}

 

결론

기존의 PizzaStore는 CheesePizza PepperoniPizza ClamPizza등 앞으로 생성될 Pizza들과 계속해서 의존관계를 맺는다.

SimpleFactory의 적용으로 PizzaStore는 SimplePizzaFactory하고만 의존관계를 맺고 SimplePizzaFactory는 Pizza들과 의존관계를 맺는다.

  • SimplePizzaFactory는 SRP 단일 책임원칙을 만족한다.

  • 기존의 PizzaStore와 Pizza객체들 사이의 강한 의존관계를 완화할수있다.

  • 피자를 생성하는 작업을 한 클래스에 캡슐화시켜 놓았기 때문에, 수정 사항이 있을 때 여기저기 다 들어가서 고칠 필요 없이 factory class 하나만 고치면 된다.

    PizzaStore과 같은 클래스가 여러개인경우 SimpleFactory적용이 없으면 수정사항이 발생하면 고치기 힘들다.

 

 

팩토리 메소드 패턴 Factory Method Pattern


객체를 생성해내는 공장을 '인스턴스화'하여, 어떤 객체를 생성할지는 서브클래스에게 맡기는 방법이다.

다양한 PizzaStore가 존재하며, PizzaStore마다 피자생성에 다양성을 주고싶은경우

 

image

  • PizzaStore 내부에 객체 생성만을 처리하는 createPizza()메소드를 추상 메소드로 선언한다. ->인스턴스화
  • 그리고 PizzaStore를 상속받은 NYPizzaStore과 ChicagoPizzaStore에서 createPizza()메소드를 구현하며, 직접 필요한 객체를 생성한다. ->서브클래스에서 어떤 객체를 생성할지 결정한다.

 

[코드]

PizzaStore

//abstract PizzaStore(부모)
// abstract method createPizza 보유 -> 서브클래스에게 구현 강제
public abstract class PizzaStore {
    
	public final Pizza orderPizza(String type){
		Pizza pizza = createPizza(type);
		pizza.prepare(); 
		pizza.bake();
		pizza.cut();
		pizza.box();
		return pizza; 
	}

	// 생성 관련 로직은 하위 클래스에 위임
	protected abstract Pizza createPizza(String type);
}
//서브클래스 ChicagoPizzaStore(자손)
//createPizza를 입맛에 맞게 가공 가능

//DEPENDENCY: Pizza 객체들

public class ChicagoPizzaStore extends PizzaStore {
	@Override
	protected Pizza createPizza(String type) {
		Pizza pizza = null;
		switch(type){
		case "치즈": pizza = new ChicagoCheesePizza(); break;
		case "포테이토": pizza = new ChicagoPotatoPizza(); break;
		}
		return pizza;
	}
}

Pizza

//abstract Pizza(부모)
//피자도 공통 리모컨 상속 이용: 다형성 보장 및 코드중복 제거
public abstract class Pizza {
	private String name;
	protected Pizza(String name){
		this.name = name;
	}
	public String toString(){
		return name;
	}
	public void prepare(){
		System.out.printf("준비중: %s%n", name);
	} 
	public void bake(){
		System.out.println("25분 동안 350도에서 굽다.");
	}
	public void cut(){
		System.out.println("피자를 8조각으로 짜른다.");
	}
	public void box(){
		System.out.println("포장합니다!");
	}
}
//ChicagoPotatoPizza(자손)
public class ChicagoPotatoPizza extends Pizza {
	public ChicagoPotatoPizza(){
		super("포테이토 두꺼운 도우 피자");
	}
}

TEST

public class PizzaTest {
    
	public static void orderPizza(PizzaStore pizzaStore) { //부모 Store 다형성
		pizzaStore.orderPizza("포테이토");//해당 객체 생성
	}
    
	public static void main(String[] args) {
		orderPizza(new ChicagoPizzaStore());//구현클래스 주입
	}
}

 

장점

abstract 추상화를 이용하여 더욱 유연한 설게 가능

객체 생성에 전문적인 프레임워크를 개발할수있다.

생성을 담당하는 메서드를 캡슐화하고 has-a와 dip를 이용한다.

내부로직을 들어내지 않고 은닉화 할수있으며 동적처리가 가능하다.

단점

제품과 생성클래스간 강한 결합은 어쩔수없음

생산자 수정에는 코드 수정이 불가피함

상속이나 구현을 사용하는게 낭비인 경우도 있음

 

 

결론

Creator 클래스(PizzaStore)가 ConcreteProduct(ConcretePizzaStore)와 느슨하게 결합되어 있다.

공통 리모컨을 사용함으로 쉽게 갈아끼울수있다. 유연성+확장성

서브클래스가 직접 객체 생성을 자유롭게 정의할수있다.

 

 

 

 

 

 

추상 팩토리 패턴 Abstract Factory Pattern


  • 추상 팩토리 패턴은 서로 연관되거나 의존적인 객체들의 조합(family)을 만드는 인터페이스를 제공하는 방법이다.

     

 

팩토리 메소드 패턴까지 설명하자면, PizzaStore가 있고 그걸 상속받는 서브 클래스 (NYPizzaStore, ChicagoPizzaStore)가 있다.

여기서 뉴욕가게와 시카고가게에서 Pizza(객체)를 만들 때 사용되는 재료들은 중복된다.

세부 디테일이 다를 수 있지만 두 가게 모두 도우, 소스, 토핑, 치즈가 필요하듯이 큰 틀은 비슷하다.

하지만 가게마다 다른 재료 종류를 사용할수도있다. 이러한 부분을 묶어서 팩토리화하여 인터페이스를 제공하는것이 추상팩토리패턴이다.

 

image

//공통적인 부분을 interface로 묶는다.
public interface PizzaIngredientFactory { 
    public Dough createDough(); 
    public Sauce createSauce(); 
    public Cheese createCheese();
    public Pepperoni createPepperoni();
}
//피자마다 사용될 재료 팩토리를 구현한다.public class NYPizzaIngredientFactory implements PizzaIngredientFactory {     public Dough createDough() {         return new ThinCrustDough();     }    public Sauce createSauce() {         return new MarinaraSauce();     }     public Cheese createCheese() {         return new ReggianoCheese();     }     public Pepperoni createPepperoni() {         return new SlicePepperoni();     }     public Clams createClam() {         return new FreshClams();     } }
  • 위 Factory Method Pattern의 예제에서 부모Store abstract하여 서브클래스에서 createPizza를 구현했다. 이어서 추상펙토리 패턴을 적용해 보자
//서브클래스 createPizza구현public class NYPizzaStore extends PizzaStore {         protected Pizza createPizza(String item) { //Factory Method Pattern의 서브클래스's create method 구현                Pizza pizza = null;                 PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); // 위에서 구현했던 재료팩토리 포함관계 has - a 로 사용                if( item.equals("cheese") ) {             pizza = new CheesePizza(ingredientFactory); //재료팩토리 전달            pizza.setName("New York Style Cheese Pizza");         }         else if( item.equals("clam") ) {  //재료팩토리 전달            pizza = new ClamPizza(ingredientFactory);             pizza.setName("New York Style Clam Pizza");         }         else if( item.equals("pepperoni") ) {  //재료팩토리 전달            pizza = new PepperoniPizza(ingredientFactory);            pizza.setName("New York Style Pepperoni Pizza");         } 
  • 서브 클래스에서 객체 생성할때 재료팩토리를 전달한다.
public class CheesePizza extends Pizza {     PizzaIngredientFactory ingredientFactory;        public CheesePizza(PizzaIngredientFactory ingredientFactory) {         this.ingredientFactory = ingredientFactory; //재료팩토리 DI    }       public void prepare() {         System.out.println("Preparing " + name);         doug = ingredientFactory.createDough(); //팩토리DATA        sauce = ingredientFactory.createSauce();         cheese = ingredientFactory.createCheese();     } }
  • 해당 객체가 생성될때 주입받은 팩토리를 이용하여 생성한다.

 

 

정리

[장점]

구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있다.

제품군 변경에 용이하다.

객체간 일관성을 확인할수있다.

[단점]

제품군에 제품을 추가하려면 인터페이스를 바꿔야 한다.

위의 피자 예시로 보면, NYPizzaStore에 불고기피자를 추가하려면 PizzaIngredientFactory를 수정해야 하고, 그것을 상속받은 모든 서브 클래스(NYPizzaIngredientFactory)의 인터페이스도 수정이 필요하다.

종합정리

결국 모든 팩토리의 공통점은 객체 생성을 캡슐화해서 애플리케이션의 결합을 느슨하게 만들고, 특정 구현에 덜 의존이게 만든다는 것이다.

 

 

728x90