관리 메뉴

IT.FARMER

팩토리 메소드 패턴(Factory method pattern / Factory pattern) 본문

소프트웨어공학/Design Pattern

팩토리 메소드 패턴(Factory method pattern / Factory pattern)

아이티.파머 2022. 12. 9. 18:26
반응형

팩토리 메소드 패턴(Factory method pattern / Factory pattern)

생성패턴에 속한 팩토리 메소드패턴(Factory Method pattern)은 보통 팩토리 패턴(Factory Pattern )으로도 알려져 있다. 여러 가지 샘플과 개념들이 많다보니 조금 햇갈릴수있는데 여기서 개념을 확실하게 하고 가야한다. 성패턴에 속한 팩토리 패턴엔 3가지가 있다.

  • 심플팩토리 패턴 - 인스턴스화 로직을 클라이온트에 노출시키지 않고 객체를 장성하고 공통인터페이스를 통해 새로 작성된 객체를 참조한다.
  • 팩토리메서드 패턴 - 오브젝트를 생성하기위한 인터페이스를 정의하지만 서브클레스에서 인스턴스화 시킬 클래스를 결정하고 공통인터페이스를 통해 새로 만들어진 오브젝트를 참조하도록 한다.
  • 추상팩토리 패턴 - 클레스를 명시적으로 지정하지 않고 관련 패밀리를 작성하기 위한 인터페이스를 제공한다.

이 세가지 모두 각자의 특성이 있으니 필요에 따라 사용하면 되겠다.

심플팩토리 패턴이란?

심플팩토리를 패턴이라 칭하지는 않지만, 우리가 흔히 팩토리를 만들어 사용할때 사용하는 방식이다. 새로운 클레스를 생성하려고 할때 if else 로 분기를 태워가면서 만들었던 생성자를 생송팩토리를 두어 생성팩토리에서 클레스를 생성하도록 위임하는 것이다.

그림으로 보면 다음과 같다.

Pizza Inferface 와, SimplePizzaFactory 클레스가 있다. SimplePizzaFactory 에서 실제 필요한 피자들을 생성하고 각각의 특성이 있는 피자들은(Veggie, Cheese, Pepperoni)Pizza 를 상속받아 구현한다. (생성과 구현의 분리)

상속구조

설명

피자를 만들기위한 기능으로 Pizza 인터페이스에 추상메소드를 선언한다.

  • papare() : 재료준비
  • bake() : 굽기
  • cut() : 자르기
  • box() : 박스담기

피자들은(Veggie, Cheese, Pepperoni)Pizza 를 상속받아 실제 각 피자별로 필요한 재료와, 굽기정도, 자르기, 박스담기를 구현한다. SimplePizzaFactory 에서는 방금 구현한 피자들을 생성한다.

의존관계

예제)

구현체


public interface Pizza {

    void prepare();
    void bake();
    void cut();
    void box();

}

public class CheesePizza implements Pizza {
    @Override
    public void prepare() {
        System.out.println("치즈 피자 재료를 준비합니다.");
    }

    @Override
    public void bake() {
        System.out.println("치즈 피자흫 화덕에 넣고 굽숩니다.");
    }

    @Override
    public void cut()
    {
        System.out.println("치즈 피자를 컷팅 합니다.");
    }

    @Override
    public void box()  {
        System.out.println("치즈 피자를 박싱합니다.");
    }
}

public class PepperoniPizza implements Pizza {
   ... 생략
}

public class VeggiePizza implements Pizza {
    ... 생략
}

팩토리

public class PizzaStore  {

    SimplePizzaFactory simplePizzaFactory;

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

    public Pizza order (String name) {

        Pizza pizza = simplePizzaFactory.createPizza(name);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }
}

public class SimplePizzaFactory {

    public Pizza createPizza(String name) {
        Pizza pizza = null;

        if (name.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (name.equals("veggie")) {
            pizza = new VeggiePizza();
        } else if (name.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else {
            throw new IllegalArgumentException();
        }
        return pizza;
    }
}

메인 클라이언트

public class PizzaClient {

    public static void main(String[] args) {
        PizzaStore pizzaStore = new PizzaStore(new SimplePizzaFactory());
        pizzaStore.order("cheese");

        System.out.println("-----------------------------------");
        pizzaStore.order("veggie");

        System.out.println("-----------------------------------");
        pizzaStore.order("pepperoni");
    }
}

실행 결과

치즈 피자 재료를 준비합니다.
치즈 피자흫 화덕에 넣고 굽숩니다.
치즈 피자를 컷팅 합니다.
치즈 피자를 박싱합니다.
-----------------------------------
Veggie 피자 재료를 준비합니다.
채소 피자흫 화덕에 넣고 굽숩니다.
채소 피자를 컷팅 합니다.
채소 피자를 박싱합니다.
-----------------------------------
페페로니  피자 재료를 준비합니다.
페페로니 피자흫 화덕에 넣고 굽숩니다.
페페로니 피자를 컷팅 합니다.
페페로니 피자를 박싱합니다.

심플팩토리 패턴은 왜 그리고 언제 사용해야 할까 ?

생성자와 구현을 분리해둔것이기 때문에 새로운 피자를 연구해서 만들었을때 즉, 파인에플피자, 포테이토피자와 같이 신규피자(기능)이 생겼을때 Pizza 인터페이스를 상속받아 SimplePizzaFactory 에 해당 클레스를 생성하는 로직을 추가하면 메인클레스를 수정하지 않고 적은 유지보수로 새로운 기능을 추가할수있다.

팩토리메소드패턴이란?

팩토리 패턴은 생성패턴중 하나이다. 객체를 생성할때 이것을 인스턴스화 한다고 이야한다. 이때 클레스 생성시 new 를 이용하여 필요한 객체를 생성하게 되는데 이러한 일련의 작업들을 일일히 필요한 클레스에서 생성하게되면 하나의 기능을 추가하거고 삭제하거나 혹은 파라미터값을 추가해야 할때 객체 생성에대한 유지보수가(오류수정이) 일어날수 밖에 없다. 이에 객체 생성에 대한 부분과 구현부분을 분리시켜 시스템의 유지보수와 확장성을 높여 놓은 패턴이다.

특징

  • 객체생성을 위한 패턴
  • 객체 생성에 필요한 과정을 템플릿 처럼 정해 놓고 각 과정을 다양하게 구현 할 수 있다.
  • 구처젝으로 생성할 클래스를 유연하게 정할수 있다.
  • 객체 생성에 대한 인터페이스와 구현을 분리한다.

개발시 다양한 객체들을 구현하게 되는데 이 다양하게 구현해야할 구현체들을 팩토리를통해 공통 부분을 추상화하고 팩토리를 상속받아 서브클레스에서 객체를 생성하도록 한다.

예제)

앞에서예제에서 피자를 생성할때 생성자와 구현을 분리하여 간단한 기능을 추가하려 했다면 이번엔 장사가잘되어 피자를 지점별로 만들었다고 생각해보자. 그럼 각 지점별로 프로세스는 같지만 피자를 생성하는 곳은 달라진다. 이때 위에 언급한것처럼 필요한 기능을 가진 클레스의 생성을 서브클레스에 위임하게 된다.

구현체

public interface Pizza {
    void prepare();
    void bake();
    void cut();
    void box();
}

public class ChicagoPepperoniPizza implements Pizza {
    @Override
    public void prepare() {
        System.out.println("시카고의 페페로니  피자 재료를 준비합니다.");
    }

    @Override
    public void bake() {
        System.out.println("시카고의 페페로니 피자흫 화덕에 넣고 굽숩니다.");
    }

    @Override
    public void cut()
    {
        System.out.println("시카고의 페페로니 피자를 컷팅 합니다.");
    }

    @Override
    public void box()  {
        System.out.println("시카고의 페페로니 피자를 박싱합니다.");
    }
}

public class ChicagoStyleCheesePizza implements Pizza {
   ... 생략
}

public class ChicagoVeggiePizza implements Pizza {
   ... 생략
}

// 뉴욕피자
public class NYPepperoniPizza implements Pizza {
    @Override
    public void prepare() {
        System.out.println("뉴욕 페페로니  피자 재료를 준비합니다.");
    }

    @Override
    public void bake() {
        System.out.println("뉴욕 페페로니 피자흫 화덕에 넣고 굽숩니다.");
    }

    @Override
    public void cut()
    {
        System.out.println("뉴욕 페페로니 피자를 컷팅 합니다.");
    }

    @Override
    public void box()  {
        System.out.println("뉴욕 페페로니 피자를 박싱합니다.");
    }
}

public class NYStyleCheesePizza implements Pizza {
    ... 생략
}
public class NYVeggiePizza implements Pizza {
    ... 생략
}

구현 부분은 심플팩토리 패턴과 똑같다. 각 지점별로 피자를 만들때 Pizza 를 상속받아 구현한다.

팩토리 생성

public abstract class PizzaStore {

    public Pizza order (String name) {

        Pizza pizza = this.createPizza(name);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;

    }

    abstract Pizza createPizza(String name) ;
}

public class NYPizzaFactory extends PizzaStore {
    @Override
    Pizza createPizza(String name) {
        Pizza pizza = null;

        if (name.equals("cheese")) {
            pizza = new NYStyleCheesePizza();
        } else if (name.equals("veggie")) {
            pizza = new NYVeggiePizza();
        } else if (name.equals("pepperoni")) {
            pizza = new NYPepperoniPizza();
        } else {
            throw new IllegalArgumentException();
        }

        return pizza;
    }
}

public class ChicagoPizzaFactory extends PizzaStore {
    @Override
    Pizza createPizza(String name) {
        Pizza pizza = null;
        if (name.equals("cheese")) {
            pizza = new ChicagoStyleCheesePizza();
        } else if (name.equals("veggie")) {
            pizza = new ChicagoVeggiePizza();
        } else if (name.equals("pepperoni")) {
            pizza = new ChicagoPepperoniPizza();
        } else {
            throw new IllegalArgumentException();
        }

        return pizza;
    }
}

팩도리 생성부분이 달라졌다. 기존에는 SimpleFactory 라는 곳에서 피자를 만들어 내었지만 지금은 지점별로 피자를 만들기 위해 PizzaStore 가 추상클레스가 되고, abstract createPizza(String) 라는 추상메소드가 추가 되었다. 그림으로 보면 다음과 같다.

 

추상 클레스가된 PizzaStore를 상속받아 Chicago 지점과 NewYouk 지점의 팩토리를 생성한뒤 각 지점별로 createPizza(String)을 오버라이딩하여 구현하도록 한다.

// 시카고
@Override
Pizza createPizza(String name) {
    Pizza pizza = null;
    if (name.equals("cheese")) {
        pizza = new ChicagoStyleCheesePizza();
    } else if (name.equals("veggie")) {
        pizza = new ChicagoVeggiePizza();
    } else if (name.equals("pepperoni")) {
        pizza = new ChicagoPepperoniPizza();
    } else {
        throw new IllegalArgumentException();
    }
    return pizza;
}

// 뉴욕 
@Override
Pizza createPizza(String name) {
    Pizza pizza = null;
    if (name.equals("cheese")) {
        pizza = new NYStyleCheesePizza();
    } else if (name.equals("veggie")) {
        pizza = new NYVeggiePizza();
    } else if (name.equals("pepperoni")) {
        pizza = new NYPepperoniPizza();
    } else {
        throw new IllegalArgumentException();
    }
    return pizza;
}

메인클라이언트

public class PizzaClientEx3 {
    public static void main(String[] args) {
        System.out.println("----------------------NY--------------------------");
        PizzaStore pizzaStore = new NYPizzaFactory();
        pizzaStore.order("cheese");
        pizzaStore.order("veggie");
        pizzaStore.order("pepperoni");

        System.out.println("------------------------Chicago------------------------");
        pizzaStore = new ChicagoPizzaFactory();
        pizzaStore.order("cheese");
        pizzaStore.order("veggie");
        pizzaStore.order("pepperoni");

    }
}

실행결과

----------------------NY--------------------------
뉴욕 치즈 피자 재료를 준비합니다.
뉴욕 치즈 피자흫 화덕에 넣고 굽숩니다.
뉴욕 치즈 피자를 컷팅 합니다.
뉴욕 치즈 피자를 박싱합니다.
뉴욕 Veggie 피자 재료를 준비합니다.
뉴욕 채소 피자흫 화덕에 넣고 굽숩니다.
뉴욕의 채소 피자를 컷팅 합니다.
뉴욕의 채소 피자를 박싱합니다.
뉴욕 페페로니  피자 재료를 준비합니다.
뉴욕 페페로니 피자흫 화덕에 넣고 굽숩니다.
뉴욕 페페로니 피자를 컷팅 합니다.
뉴욕 페페로니 피자를 박싱합니다.
------------------------Chicago------------------------
시카고  치즈 피자 재료를 준비합니다.
시카고 치즈 피자흫 화덕에 넣고 굽숩니다.
시카고의 치즈 피자를 컷팅 합니다.
시카고의 치즈 피자를 박싱합니다.
Veggie 피자 재료를 준비합니다.
채소 피자흫 화덕에 넣고 굽숩니다.
시카고의 채소 피자를 컷팅 합니다.
시카고의 채소 피자를 박싱합니다.
시카고의 페페로니  피자 재료를 준비합니다.
시카고의 페페로니 피자흫 화덕에 넣고 굽숩니다.
시카고의 페페로니 피자를 컷팅 합니다.
시카고의 페페로니 피자를 박싱합니다.

팩토리 메소드 패턴(FactoryMethodPattern) 은 그럼 언제? 왜? 사용하면 좋을까 ?

팩토리 메소드패턴은 위에서 구현해 보았듯이 구현 및 생성이 분리되어 있다. 거기에 생성에 필요한 클레스와 메소드를 추상화하여 심플팩토리(Sumple Factory)보다 더욱 확장성에 용이해 졌다.

이처럼 무언가를 확장해서 기능을 추가할때 팩토리 메소드 패턴을 이용하면 메인클레스의 수정을 최소화하고 서브클레스와 (Subclass) 구현체(Inferface) 를 추가 할 수 있게 된다. 본문의 예시는 피자를만드는것으로 예를 들었지만, 아이템생이나, 케릭터 생성시… 등등 여러부분에서 사용할 수 있다.

팩토리 메소드패턴을 업무에 적용하기 위해서는 생성과 구현을 분리한다는 취지로 접근하여 공통적인 부분과 확장적인 측면을 들여다보고 구현과 생성부분을 분리하면 좋은 그림이 나올것 같다.

참고

▪️ inferface로 구현한 PizzaFactory

이후 피자팩토리를 상속받아 구현하는 방법은 위에 예시로 구현한 부분과 동일하다.

public interface PizzaFactory {
    default void order(String name) {
        if (this.validation(name)) {
            Pizza pizza = this.createPizza(name);
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();

        }
    }

    Pizza createPizza(String name);

    private boolean validation(String pizzaName) {

        if (pizzaName == null || pizzaName.isBlank()) {
            throw new IllegalArgumentException();
        }

        return true;
    }
}

결론

결국에 팩토리패턴(abstract factory, simple factory, method factory)은 객체 생성시 캡슐화를 통해 결합도를 낮춰 의존성을 줄임이고 기능추가에는 개방적이며 수정에는 폐쇠적인 구조를 가져가는 생성패턴임을 알 수있다.

반응형