Java/디자인패턴
Factory Pattern
Bubbles
2024. 1. 17. 11:10
🤔 인터페이스를 사용한다고 해도 구상 클래스의 인스턴스를 만들어야 하지 않나?
Duck duck = new MallardDuck();
// Duck이라는 인터페이스를 써서 유연하게 만들려고 해도, 그럼에도 구상 클래스의 인스턴스를 만들어야 한다.
Duck duck;
if (picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}
- 이 코드를 보면 구상 클래스의 인스턴스가 여러 개 있고, 그 인스턴스의 형식은 실행 시 조건에 따라 결정된다.
- 변경하거나 확장할 때, 코드를 다시 확인하고 새롭게 추가하거나 기존 코드 제거
=> 관리와 갱신이 어렵다.
=> 인터페이스에 맞춰서 코딩하면 시스템에서 일어날 수 있는 여러 변화에 대응 가능 (인터페이스만 구현하면 사용 가능, 다형성)
=> 구상 클래스를 많이 사용하면 새로운 구상 클래스 추가될 때마다 코드 고쳐야 함
=> 새로운 구상 형식을 써서 확장할 때에는 어떻게 해서든 다시 열 수 있게 만들어야 한다...!
Pizza orderPizza(String type) {
Pizza pizza;
// 이 부분이 피자가 바뀔 때마다 코드를 계속 추가하고 삭제해야 하는 것이 문제이다.
// ( 인스턴스를 만드는 구상 클래스 선택하는 부분 )
// 이 부분을 따로 빼두자(팩토리)
if (type.equals("cheese")) {
pizza = new CheezePizza();
} else if (type.equals("pepperoni") {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
// 이 아래부분은 바뀌지 않음
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
인스턴스를 만드는 구상 클래스를 선택하는 부분 -> 이 부분을 따로 빼내자(피자 종류에 따라 계속 바뀌는 부분이므로)
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
if (type.equals("cheese")) {
pizza = new CheezePizza();
} 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(팩토리 사용하는 클라이언트)
- SimplePizzaFactory (객체를 생성하는 팩토리, 구상 Pizza 클래스를 직접 참조)
- Pizza (팩토리에서 만드는 피자)
- CheesePizza, VeggiePizza, ClamPizza, PepperoniPizza 등등
- 팩토리에서 생산하는 제품에 해당하는 구상 클래스
- 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);
}
- createPizza : PizzaStore 상속받은 NYStylePizzaStore, ChicagoStylePizzaStore 2가지
- ex ) NYStylePizzaStore의 createPizza(type)
- NYStyleCheesePizza()
- NYStylePepperoniPizza()
- ex ) NYStylePizzaStore의 createPizza(type)
- orderPizza : 그냥 구현하지 않고 가져다 사용, 만약 못 고치도록 막고 싶다면 final 붙이면 된다
🤔 팩토리 메서드
- 객체를 생성할 때 필요한 인터페이스를 만든다.
- 어떤 클래스의 인스턴스를 만들 지는 서브 클래스에서 결정한다.
- 즉, 클래스 인스턴스 만드는 일을 서브 클래스에게 맡기게 된다.
🍕 Creator
- 추상 클래스, 팩토리 메소드 용 인터페이스를 제공한다.
- 인터페이스만 제공, 실제 팩토리 메소드를 구현하고 객체 인스턴스 만드는 일은 서브 클래스에서만 할 수 있음
- PizzaStore 추상클래스 상속받는 NyPizzaStore, ChicagoPizzStore
- PizzaStore의 orderPizza : pizza = createPizzas(type) 이렇게 호출
- 사용하는 쪽에서 어느 PizzaStore의 orderPizza호출하냐 따라 NyPizza가 만들어질수도, ChicagoPizza가 만들어질수도 있다!
- 즉, 서브 클래스에서 어떤 객체가 생성될지 결정되는 것 !
🤔 간단한 팩토리와 팩토리 메서드 비교
🤔 추상 팩토리
구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공하고, 구상 클래스는 서브클래스에서 만들어진다.
- 위의 시나리오에서 원재료들을 생산하는 팩토리도 만들자!
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce creaetSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();
}
- 각 스타일의 피자별로 implements PizzaIngredientFactory 해서 사용
- 각각 createXXX 메소드를 지점별로 알맞게 만든다.
- ex ) 냉동 조개를 쓸 수도 있고, 생 조개 쓸 수도 있고..!
- 기존 PizzaClass에서도 prepare()을 추상 메소드로 만든다.
EX) 🧀🧀 CheesePizza
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() {
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
// 등등 재료가 필요할 때마다 팩토리에 있는 메소드 호출하여 만든다
}
}
EX) 🧀🧀 NYPizzaStore
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item){
Pizza pizza = null;
PizzaIntgredientFactory ingredientFactory = new NYPizzaIngredientFactory();
// .. 중략, item별로 치즈 피자 만들기 등등 수행
}
}
🤔 팩토리 메소드 패턴(PizzaStore)과 추상 팩토리 패턴(PizzaIngredientFactory) 비교
🍎 팩토리 메소드 패턴 🍎 | 🍏 추상 팩토리 패턴 🍏 |
어플리케이션을 특정 구현으로부터 분리하기 위해 사용 | |
🍎 클래스를 사용해서 만든다. - 상속을 통해 객체를 만든다. - 클래스를 확장, 팩토리 메소드 오버라이드 🍎 PizzaStore ~ createPizza() 라는 팩토리 메소드 override 🍎 NYPizzaStore, ChicagoPizzaStore (상속) |
🍏 객체 구성을 통해 객체를 만든다. - 필드로 클래스 인스턴스 참조 - 제품군을 만드는 추상 형식 제공 - 제품이 생산되는 방법은 이 형식의 서브 클래스에서 정의 🍏 PizzaIngredientFactory - createDough, createCheese 등 추상 형식을 제공 - 구체적으로 어떤 dough, cheese인지는 Ny, Chic(하위)에서 🍏 NyPizzaStore의 createPizza() PizzaIngredientFactory ingredientFactory = new NyPizzaIngredientFactory 넣어주기 |
🍎 클라이언트 코드와 인스턴스를 만들어야 할 구상 클래스를 분리 | 🍏 새로운 제품을 추가 시 인터페이스 변경이 일어난다. ex) 현재는 dough, cheese인데 ham 이런거 추가한다면? |
🍎 클라이언트는 자신이 사용할 추상 형식만 알고 있으면 OK 서브 클래스에서 구상 형식을 처리 해준다. |
🍏 팩토리를 사용하려면, 팩토리 인스턴스를 만들고, 추상 형식을 써서 만든 코드에 전달 |
🍎 어떤 구상 클래스가 필요할지 미리 알 수 없을 때에도 유용 | 🍏 클라이언트에서 서로 연관된 일련의 제품을 만들 때 즉, 제품 군을 만들 때 활용 |