팩토리(factory)란 무엇인가.
팩토리를 말 그대로 직역하면 공장이다. 공장에서는 무슨 일을 하는가?
공장은 어떠한 물건을 만들어 내는 일을 진행한다. 이를 객체지향에 빗대어 보자.
그렇다. 팩토리라는 것은 어떠한 객체를 생성하는 일을 진행해주는 것이다.
피자가게를 예시로 들어보자.
피자 가게라는 객체가 존재하고 이를 구현한 뉴욕 피자가게, 워싱턴 피자 가게, 이탈리안 피자 가게등이 있다고 해보자.
이들 피자 가게객체가 하는 일을 적어보자.
여기서 바뀌는 부분과 바뀌지 않는 부분을 알아보자. 객체지향 설계에서 가장 중요한 핵심이다.
이 부분은 어떤 피자가 오든 동일한 일을 수행할 것이다.
피자를 만드는 부분이다.
새로운 피자가 나오거나, 피자가 메뉴에서 없어진다면 이러한 부분들은 계속해서 변화가 이루어진다.
그렇다면 이 부분을 따로 빼보자. 라는 부분이 바로 팩토리 패턴이다.
3가지로 나뉘어져 있다.
간단한 팩토리, 팩토리 메소드 패턴, 추상 팩토리 패턴 위 3가지가 존재한다.
말 그대로 간단한 팩토리이다.
다음과 같이 구상한다. 그저 피자 객체를 생성하는 부분을 따로 나눈 것밖에 없다.
만약 코드로 표현하면 이렇다.
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;
}
// 기타 메소드
}
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
CheesePizza, PepperoniPizza, ClamPizza, VeggiePizza와 같은 구현체들은 적지 않겠다.
public 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();
}
else if(type.equals("clam")){
pizza = new ClamPizza();
}
else if(type.equals("veggie")){
pizza = new VeggiePizza();
}
return pizza;
}
}
뉴욕피자 팩토리, 시카고 피자 팩토리등 여러가지 팩토리들을 만들어서 사용하도록 하면된다.
이렇게 만들면 객체 생성관리를 PizzaStore에서 하지 않으니 편해보인다.
하지만 PizzaStore에서 객체 생성에 전혀 관여하지 않다보니 객체들이 자기 멋대로 나오는 경우가 생길 수 있다.
이를 PizzaStore에서 Pizza를 제작할 수 있도록 만들어보자.
객체를 생성할 때 필요한 인터페이스를 만듭니다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정합니다. 팩토리 메소드 패턴을 사용하면 클래스 인스턴스를 만드는 일을 서브 클래스에게 맡기게 됩니다.
위에 나온 정의처럼 PizzaStore에서 Pizza를 제작하도록 해보자. 그러면서 유연성은 떨어지지 않도록 만들어보자.
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);
}
여기서 주목할 점은 abstract Pizza createPizza(String type);
함수이다.
PizzaStore객체는 createPizza(type)
을 호출하여 피자를 생성한다. 하지만 어떤 피자가 올지는 모른다.
어떤 피자를 만들기 위한 구현은 서브 클래스에 위임하는 형태인 것이다.
즉, 팩토리 메소드를 추상 메소드로 선언해서 서브클래스가 객체 생성을 책임지도록 위임하는 형태인 것이다. 클라이언트(PizzaStore에서 orderPizza함수와 같은 코드)는 실제로 생성되는 객체가 무엇인지 알 수 없는 형태로 만드는 것이다.
public class NYPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
switch (type) {
case "cheese":
pizza = new NYStyleCheesePizza();
break;
case "pepperoni":
pizza = new NYStylePepperoniPizza();
break;
case "clam":
pizza = new NYStyleClamPizza();
break;
case "veggie":
pizza = new NYStyleVeggiePizza();
break;
default:
System.out.println("다시 입력해주세요.");
break;
}
return pizza;
}
}
public interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
피자를 구현한 객체들은 적지 않겠다.
public class Main {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYStylePizzaStore();
Pizza pizza = pizzaStore.orderPizza("cheese");
System.out.println(pizza.toString());
}
}
다음과 같이 테스트해보면 뉴욕스타일의 치즈 피자가 나오게 된다.
팩토리 메소드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화 하는 것이다.
그렇게 함으로써 제품을 생산하는 부분과 제품을 사용하는 부분으로 분리할 수 있다. 느슨하게 결합을 유지시켜주는 것이다.
지금까지 피자 인터페이스에 재료들이 무엇이 있는지 명시하지 않았지만 있다고 생각하고 글을 적어보겠다.
피자 가게에서 원재료들을 어디에서 받는지 명시하지 않다보니 각 지점에서 알아서 원재료를 받아오게 되었다.
그런데 원재료들을 잘 가져오면 모르겠지만 값 싼 재료들을 받아오거나 자기들 마음대로 원재료들을 받아오는 경우가 생기게 되었다.
그래서 우리가 각 지점별로 원재료를 받아오는 팩토리를 지정해주도록 하려고 한다. 어떻게 하면 될까?
구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공합니다. 구상 클래스는 서브 클래스에서 만든다.
위의 정의를 빗대어보자. 여러 지점들의 피자가게가 존재한다. 각 지점마다 원재료를 받아오는 팩토리가 존재해야할 텐데 이 때 같은 공장에서 받아오는 지점들도 존재할 것이다.
이를 만들어보자.
public interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
Veggies[] createVeggies();
Pepperoni createPepperoni();
Clams createClam();
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new Dough();
}
@Override
public Sauce createSauce() {
return new Sauce();
}
@Override
public Cheese createCheese() {
return new Cheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new Pepperoni();
}
@Override
public Clams createClam() {
return new Clams();
}
}
이렇게 원재료를 만드는 추상 팩토리를 각 지점별로 구현한다.
public abstract class Pizza {
String name;
public Dough dough;
public Sauce sauce;
public Veggies Veggies[];
public Cheese cheese;
public Pepperoni pepperoni;
public Clams clam;
abstract void prepare();
void bake() {
System.out.println("피자 굽기");
}
void cut() {
System.out.println("피자 자르기");
}
void box() {
System.out.println("상자에 피자 담기");
}
}
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
@Override
public void bake() {
}
@Override
public void cut() {
}
@Override
public void box() {
}
}
각 지점별로 원재료를 갖고 오는 부분을 만들었으니 이제 각 지점별 피자가 아닌 CheesePizza
로 묶어둔 뒤, 원재료를 만들어주는 추상 팩토리를 인자로 받아 만든다.
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);
}
public class NYStylePizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
switch (type) {
case "cheese":
pizza = new CheesePizza(ingredientFactory);
break;
case "pepperoni":
pizza = new PepperoniPizza(ingredientFactory);
break;
case "clam":
pizza = new ClamPizza(ingredientFactory);
break;
case "veggie":
pizza = new VeggiePizza(ingredientFactory);
break;
default:
System.out.println("다시 입력해주세요.");
break;
}
return pizza;
}
}
지점별로는 원재료 팩토리를 달리 갖고 있게 한다.
public class Main {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYStylePizzaStore();
Pizza pizza = pizzaStore.orderPizza("cheese");
System.out.println(pizza.toString());
}
}
다음과 같이 진행하면 뉴욕 치즈 피자가 만들어진다.
좀 복잡하니깐 한 번 정리해보자.
orderPizza()
를 이용해 피자를 주문받는다.createPizza()
라는 메소드를 호출하여 서브클래스(NYPizzaStore) 에서 피자를 만들도록 한다.그림으로 표현하면 다음과 같다.
둘 다 객체를 생성하는 부분을 분리하기 위해 사용된다.
그렇다면 무슨 차이가 존재할까?
객체를 사용하여 제품을 만든다. 즉, 구성으로 이루어져 있다.
일련의 연관된 제품들을 하나로 묶을 수 있다.
변경이 이루어질 때 인터페이스를 바꿔야 하기 때문에 서브 클래스들도 변경이 일어나야 한다.
구상 팩토리를 팩토리 메소드를 이용해 구현하는 경우도 있다.
만약 도우를 만드는 createDough()
함수를 팩토리 메소드로 구현하는 것이다. 어떤 도우를 쓸 지는 서브클래스에서 만들어주는 것이다.
구상 클래스에 직접 의존하지 않고 만들 수 있다.
참고
http://wiki.gurubee.net/pages/viewpage.action?pageId=1507401
헤드퍼스트 디자인 패턴