팩토리 패턴
팩토리 패턴
팩토리 패턴 정의
객체 생성 부분을 떼어내 추상화한 패턴, 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴이다.
팩토리 패턴이 필요한 이유
문제점
피자를 생성하는 메서드를 아래와 같이 구현한다면, 피자의 종류가 늘어나거나 줄어들었을 때마다 코드가 변경될 것이다.
public class PizzaStore {
public Pizza orderPizza(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();
}
// 피자 종류가 바뀔때마다 바뀌는 코드 ===========
// 바뀌지 않는 부분
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
팩토리 클래스 캡슐화
피자 객체 생성 부분을 전담할 클래스(SimplePizzaFactory)를 정의한다.
피자 객체 생성 작업을 팩토리 클래스로 캡슐화해 놓음으로써 코드 변경시 다른 부분 고칠 필요 없이 팩토리 클래스만 고치면 된다.
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();
}
return pizza;
}
}
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
팩토리 메서드 패턴
팩토리 메서드 패턴 정의
객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정한다 → 즉 팩토리 메소드 패턴을 이용하면 클래스 인스턴스를 만드는 일을 서브클래스에게 맡길 수 있다.
팩토리 메서드 패턴 필요한 이유
예를 들어, 각 나라마다 PizzaStore 클래스가 필요하다면 추상 클래스를 이용할 수 있다.
뉴욕PizzaStore 와 한국PizzaStore가 각 나라에 맞게 다른 피자들을 생성해야 한다면 구현한 서브클래스에서 createPizza 추상 메서드를 각 나라에 맞게 정의할 수 있다.
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
public class NewYorkPizzaStore extends PizzaStore{
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if(type=="cheese"){
pizza = new NyCheesePizza();
}else if(type=="pepperoni"){
pizza = new NyPepperoniPizza();
}
return pizza;
}
}
public class KoreaPizzaStore extends PizzaStore{
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
if(type=="cheese"){
pizza = new KrCheesePizza();
}else if(type=="pepperoni"){
pizza = new KrPepperoniPizza();
}
return pizza;
}
}
팩토리 메서드 패턴- 정리
- 팩토리 패턴은 객체 생성을 캡슐화한다.
- 팩토리 메서드 패턴은 서브클래스에서 어떤 클래스를 만들지 결정함으로써 객체 생성을 캡슐화한다.
- 크게 Creator 클래스 와 Product 클래스로 나뉜다.
- Creator 클래스 : PizzaStore
- Product 클래스 : Pizza
- 장점
- 코드 중복을 최소화할 수 있다.
- 효율적으로 관리할 수 있다.
- 단점
- 클래스가 많아진다.
- 각 객체마다 팩토리 메서드를 각 제공해야한다. → 많은 팩토리 메서드를 가지게 될 수 있음
추상 팩토리 패턴
추상 팩토리 패턴 정의
구상 클래스에 의존하지 않고, 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공한다. 구상 클래스는 서브 클래스에서 만든다.
추상 팩토리 패턴이 필요한 이유
의존성 뒤집기 원칙
- 만약, 팩토리 메서드 패턴을 적용하지 않고, 아래와 같이 PizzaStore에서 피자를 생성하게 된다면 pizzaStore 객체가 모든 pizza 구상 클래스를 의존한다.
- 의존성 뒤집기 원칙 : 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만들어야 한다.
- 위의 원칙처럼 현재 코드는 고수준 구성 요소(pizzaStore)가 저수준 구성 요소(pizza)에 의존되어 있다. 그렇게 된다면 구상 클래스가 변경될때마다 pizzaSore도 변경해야 할 수도 있음
- 팩토리 메서드 패턴을 이용해, 의존성을 뒤집어 보자
public class PizzaStore {
public Pizza orderPizza(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();
}
// 피자 종류가 바뀔때마다 바뀌는 코드 ===========
// 바뀌지 않는 부분
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
아래와 같이 팩토리 메서드 패턴을 이용하면, PizzaStore가 Pizza 추상 클래스에 의존하게 되고, 나머지 pizza 구상 클래스들이 pizza 추상 클래스들을 의존하게 된다.
→ 고수준 모듈과 저수준 모듈이 둘다 하나의 추상클래스에 의존하게 된다.
→ 하지만 팩토리메서드로 여러 제품군을 다루기에는 힘들다.
public abstract class Pizza {
protected Source source;
protected Topping topping;
public abstract void prepare();
public abstract void bake();
public abstract void last();
}
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.last();
return pizza;
}
protected abstract Pizza createPizza(String type);
추상 팩토리 패턴
각 제품마다의 팩토리를 제공하여 피자 종류와 지역에 따라 다양한 기능을 추가할 수 있다.
class Main {
public static void main(String args[]) throws Exception {
System.out.println(" NY 피자집 준비");
PizzaStore pizzaStore = new NewYorkPizzaStore();
pizzaStore.orderPizza("cheese");
}
}
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.last();
return pizza;
}
protected abstract Pizza createPizza(String type);
}
public class NewYorkPizzaStore extends PizzaStore{
@Override
protected Pizza createPizza(String type) {
Pizza pizza = null;
PizzaFactory nyPizzaFactory = new NyPizzaFactory();
if(type=="cheese"){
pizza = new NyCheesePizza(nyPizzaFactory);
}else if(type=="pepperoni"){
pizza = new NyPepperoniPizza(nyPizzaFactory);
}else if(type=="custom"){
pizza = new NySimplePizza(nyPizzaFactory);
}
return pizza;
}
}
public abstract class Pizza {
protected Source source;
protected Topping topping;
public abstract void prepare();
public abstract void bake();
public abstract void last();
}
public class NyCheesePizza extends Pizza{
private PizzaFactory pizzaFactory;
public NyCheesePizza(PizzaFactory pizzaFactory){
this.pizzaFactory = pizzaFactory;
}
@Override
public void prepare() {
this.source = pizzaFactory.createSource();
this.topping = pizzaFactory.createTopping();
}
@Override
public void bake() {
}
@Override
public void last() {
System.out.println(source.getSource());
System.out.println(topping.getTopping());
}
}