팩토리패턴

임종혁·2024년 11월 6일

특정 구현을 바탕으로 프로그래밍 하지마라 -> 허나 new를 쓸때마다 결국 특정 구현을 사용하는 것 아닌가?

항상 코드를 짜올때마다 이런 의문이 있었다. 이번에 이런 의문을 해소해 줄 수 있는 팩토리 패턴을 만나 볼 것이다.

Duck duck = new MallarDuck();

이런식으로 Duck이라는 인터페이스를 써서 코드를 유연하게 하고 싶어도 MallardDuck()이라는 구상 클래스의 인스턴스를 만들면 나중에 코드 수정 가능성도 커지고 유연성도 떨어진다.

일련의 구성 클래스가 있다면 어쩔수 없이

Duck duck;
if(picnic){
	duck = new MallardDuck();
}else if(hunting){
	duck = new DecoyDuck();
}else if (inBathTub){
	duck = new RubberrDuck();
}

위와 같은 경우 오리를 나타내는 클래스는 여러 있지만 컴파일 하기 전까지는 어떤것의 인스턴스를 만들어야 하는지 알 수 없다.

허나 코드를 변경하거나 확장해야 할시 코드를 다시 확인하고 새로 코드를 추가하거나 기존 코드를 제거해야할 상황이 생긴다.
이런식이면 관리와 갱신이 어려워지고 오류가 발생할 가능성이 높다.


public class Pizza {

    public void prepare(){
        System.out.println("준비하다");
    }
    public void bake(){
        System.out.println("굽다");
    }
    public void cut(){
        System.out.println("자르다");
    }
    public void box(){
        System.out.println("포장하다");
    }

}

다음과 같이 Pizza 클래스가 있고

class Order{

    public Pizza orderPizza(){
        Pizza pizza = new Pizza();

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

주문클래스의 orderPizza()를 통해 Pizza를 생성하고 Pizza 알고리즘을 수행한다

Pizza의 종류는 많을 것이다.

class CheesePizza extends Pizza{
    
}
class PepperoniPizza extends Pizza{
    
}

다음과 같이 Pizza를 상속받는 여러 피자를 만들고

class Order{

    public Pizza orderPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

주문클래스에 orderPizza 메서드를 위처럼 바꾸어 보자
그럼 메소드에 들어오는것에 따라 피자를 선택할 수 있다.

허나 이 orderPizza() 메서드에서 가장 문제가 되는 부분은 인스턴스를 만드는 구상 클래스를 선택하는 부분이다.
이 부분이 상황이 변하면 코드를 변경해야한다

변하는 부분 변하지 않는 부분 분리하기

이제 변하는 부분 if문 변하지 않는 부분 나머지 를 분간할 수 있기에 분리를 해줄 차레이다 그럼 변하는 부분을 분리해보겠다.

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

다음과 같이 변하는 부분을 분리하여 SimplePizzaFactory 를 만들어 전담하였다.

이제 해당 부분이 변할때 SimplePizzaFactory만 변경하면 될것이다.

다시 주문 클래스 Order는

class Order{
    
    SimplePizzaFactory factory;

    public Order(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza = factory.createPizza(type);
//        if(type.equals("cheese")){
//            pizza = new CheesePizza();
//        } else if (type.equals("pepperoni")) {
//            pizza = new PepperoniPizza();
//        } => 변하는 부분 분리
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

다음과 같이 사용하면 될것이다.

class Main{
    public static void main(String[] args){
        SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
        Order order = new Order(simplePizzaFactory);
        order.orderPizza("cheese");
    }
}

main 클래스(클라이언트)에서 사용시

정상적으로 작동됨을 볼수있다.

SimplePizzaFactory simplePizzaFactory = new SimplePizzaFactory();
        Order order = new Order(simplePizzaFactory);

다음 코드는 의존성 생성자 주입

다양한 팩토리 만들기

추상팩토리 패턴

각 지점마다 다양한 스타일의 피자(뉴욕 스타일, 시카고 스타일 , 캘리포니아 스타일 등)으로 만들어야 할서이다.

public abstract class Order {

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

주문클래스 order를 다음과 같이 추상 메소드로 변경하였다.

public class NYPizzaOrder extends Order{

    @Override
    Pizza createPizza(String item) {
        if(item.equals("cheese")){
            return new NYStyleCheeseePizza();
        } else if (item.equals("veggie")) {
            return new NYStyleVeggiePizza();
            
        }
    }
}

다음과 같이 구현 클래스로 NYPizzaOrder 를 구현하였다.

""" 이건 템플릿 메소드 패턴 아닌가 ??"""
"" 팩토리 메소드 패턴은 템플릿과 다른점은 생성뿐인가?"""

Pizza도 이제 여러 클래스가 올것이니 추상화 시킬것이다.

abstract class Pizza{
    String name;
    String dough;
    String sauce;
    List<String> toppings = new ArrayList<>();

    void prepare(){
        System.out.println("준비중 :" +name);
        System.out.println("도우를 돌리는 중....");
        System.out.println("소스를 뿌리는중....");
        System.out.println("토핑을 올리는 중....");
        for(String topping : toppings){
            System.out.println(" " + topping);
        }
    }

    void bake(){
        System.out.println("175도 25분 굽기 ");
    }

    void cut(){
        System.out.println("자르기 ");
    }
    void box(){
        System.out.println("상자에 담기  ");
    }

    public String getName(){
        return name;
    }

}

그리고 해당 각 구현클래스를 만든다

class NYStyleCheesePizza extends Pizza{
    public NYStyleCheesePizza(){
        name = "뉴욕 스타일 소스와 치즈 피자";
        dough = "신 크러스트 도우";
        sauce = "마리나라 소스";

        toppings.add("잘게 썬 레지아노 치즈 ");
    }
}

class ChicagoStyleCheesePizza extends Pizza{
    public ChicagoStyleCheesePizza(){
        name = "시카고  스타일 딥 디쉬 치즈  피자";
        dough = "아주 두꺼운 크러스트 도우";
        sauce = "플럼토마토 소스";

        toppings.add("잘게 썬 레지아노 치즈 ");
    }

    @Override
    void cut() {
        System.out.println("네모난 모양으로 피자 자르기");
    }
}

지금까지 짠 코드들을 실행시켜본다

 class PizzaTestDrive{
    public static void main(String[] args){

        Order order = new NYPizzaOrder();
        Order order1 = new ChicagoOrder();

        Pizza pizza = order.orderPizza("cheese");
        System.out.println("에딘이 주문한" + pizza.getName());

        pizza = order1.orderPizza("cheese");
        System.out.println("조엘이 주문한 피자 " +pizza.getName());
    }

}

코드를 보면 order는 NyPizzaOrder로 선언이 되었으며
해당 order에서 orderPizza("cheese") 를 넣으면

 public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = createPizza(type); // 1. 여기를 타고 

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
    
        abstract Pizza createPizza(String type); 2. 여기를 타서 서브 클래스로 위임
    }
class NYPizzaOrder extends Order{

    @Override
    Pizza createPizza(String item) { // 3
        Pizza pizza =null;
        if(item.equals("cheese")){
            pizza =  new NYStyleCheesePizza();
        }

        return pizza;
    }
}

서브클래스를 통해 NYStyleCheesPiza 가 완성된다

앞선 코드를 보면

생성자 클래스

  • Order
  • NYPizzaOrder
  • ChicagoOrder

생성하는 클래스

제품 클래스

  • Pizza
  • NYStyleCheesePizza
  • ChicagoStyleCheesePizza

제품과 생산자 클래스는 둘다 추상클래스로 시작하고 서로 병렬로 구성되어 있다.

팩토리 메소드 정의

팩토리 메소드 패턴 에서는 객체를 생성할때 필요한 인터페이스를 만듭니다. 어떤 클래스에서 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메소드 패턴을 사용시 클래스 인스턴스 만드는일을 서브클래스에 맡기게 됩니다.

의존성 뒤집기

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

그동안 Order 는 NYStlyeCheesePizza , ChicagoStyleCheesePizza 에 의존 했다.

Order는 Pizza에 의해 정의되므로 고수준 구성요소 Pizza 는 저수준 구성요소이다
고수준 구성요소가 저순준 구성요소에 의존하면 안되고 항상 추상화에 의존하게 만들어야한다

위 팩토리를 만ㄷ르어 줌으로써 다양한 구상 형식이 추상화된 Pizza클래스에 의존하고 order 도 Pizza 에 의존한다

의존성 뒤집기 원칙

  • 변수에 구상 클래스의 래퍼런스 저장하지 말기
  • 구상 클래스에서 유도된 클래스 만들지 말기
  • 베이스 클래스에 이미 구현되어 있는 메소드 오버라이드 하지 말기

0개의 댓글