흔히 말하는 팩토리 패턴(Factory Pattern)에는 팩토리 메소드 패턴(Factory Method Pattern)과 추상 팩토리 패턴(Abstract Factory Pattern) 두 가지 패턴이 있습니다. 이 패턴들에 대해 오늘 순서대로 알아보겠습니다.
new를 사용하는 것은 구상 클래스의 인스턴스를 만드는 것입니다. 구상 클래스를 바탕으로 코딩을 하면 나중에 코드를 수정해야할 가능성이 높아지고, 유연성이 떨어지게 됩니다.
그 예가 아래와 같습니다. code reference
구상 클래스를 사용하면 아래처럼 조건에 따라 만들려고 하는 구상 클래스를 명시해줘야 합니다. 이는 뭔가 변경하거나 확장해야할 때 코드를 또 확인하고 추가해야한다는 뜻이죠. 이걸 다른 메소드 또는 객체로 클래스 생성을 위임하여 역할을 분리하는 것이 바로 팩토리 패턴입니다.
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;
}
자, 이제 간단한 팩토리를 만들어 이를 클래스 생성을 위임해 봅시다.
이 작업 전에 바뀌는 부분과 안바뀌는 부분을 구분해야 합니다. 변하는 부분은 pizza 인스턴스를 생성하는 부분, 변하지 않는 부분은 나머지 작업이네요. 하나의 factory class를 생성하여 변하는 부분을 다른 클래스로 분리합니다.
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
}
return pizza;
}
}
위처럼하면 SimplePizzaFactory
가 클라이언트가 많을 때에 빛을 발휘합니다. 만약 SimplePizzaFactory
가 없었다면 모든 클라이언트에서 수정 작업을 해줘야 합니다.
팩토리 메소드 패턴(Factory Method Pattern)은 추상 메소드를 이용하여 생성하는 객체를 서브 클래스에서 결정하게 구현하는 것입니다.
PizzaStore.java
public abstract class PizzaStore {
Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type);
/* 피자를 준비하고 굽고 커팅하는 과정 */
}
/* PizzaStore에서는 만들 피자를 결정하지 않고 서브 클래스에서 결정 */
public abstract Pizza createPizza(String type);
}
}
ChicagoPizzaStore.java
public class ChicagoPizzaStore extends PizzaStore {
@Override
public Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new ChicacoStyleCheesePizza();
} else if (type.equals("greek")) {
return new ChicagoStyleGreekPizza();
} else if (type.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else {
return null;
}
}
}
팩토리 메소드는 객체 생성을 처리하며, 팩토리 메소드를 이용하면 객체를 생성하는 작업을 서브클래스에 캡슐화시킬 수 있습니다. 이렇게 하면 수퍼클래스에 있는 클라이언트 코드와 서브클래스에 있는 객체 생성 코드를 분리시킬 수 있습니다.
팩토리 메소드 패턴의 정의는 아래와 같습니다.
팩토리 메소드 패턴 - 팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듭니다. 팩토리 메소드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스에게 맡기는 것이죠.
간단한 팩토리 패턴은 일회성으로 사용가능한 반면, 팩토리 메소드 패턴을 이용하면 어떤 구현을 사용할지를 서브 클래스에서 결정하는 프레임워크를 만들 수 있습니다.
Design Principle 6.
추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다.
고수준 구성요소가 저수준 구성요소에 의존하면 안됩니다.
예를 들어, 예시에서 Pizza
라는 상위 클래스가 없었다면 PizzaStore
클래스는 여러 피자 종류를 감당하고 여러 피자 종류의 클래스에 의존하고 있었을 것입니다. 하지만 Pizza
라는 상위 클래스를 생성함으로써 PizzaStore
는 Pizza
를 구성으로 가지고 있고 나머지 피자 종류 클래스는 Pizza
에 의존하게 됩니다.
어떤 변수에도 구상 클래스에 대한 레퍼런스를 저장하지 맙시다.
구상 클래스에서 유도된 클래스를 만들지 맙시다.
→ 구상 클래스에서 유도된 클래스를 만들면 특정 구상 클래스에 의존하게 됩니다.
베이스 클래스에 이미 구현되어 있던 메소드를 오버라이드하지 맙시다.
→ 오버라이드가 필요한 경우는 추상화가 잘못된 경우라고 볼 수 있습니다.
추상 팩토리 패턴 - 추상 팩토리 패턴에서는 인터페이스를 이용하여 서로 연관된 , 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있습니다.
추상 팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스를 통해 제품을 공급받을 수 있습니다. 이를 통해 클라이언트에서는 구성 클래스에 대한 의존성을 분리할 수 있습니다.
다이어그램을 확인해보면 Client
에서는AbstractProductA
, AbstractProductB
를 구성하고 있습니다. Client
는 실제 어떤 구성 클래스를 포함하고 있는지 알지 못합니다.
이 구성 변수는 Client
코드 상에는 인터페이스를 통해 추상화시키고, 실제 객체 구상 클래스는 AbstractFactory
를 구현한 하위 클래스(ConcreteFactor1
인지 ConcreteFactory2
)에서 지정합니다.