객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 한다. 즉, new 키워드를 사용하는 부분을 서브클래스에 위임함으로서 객체 생성을 캡슐화하고 구체적인 클래스에 대한 의존성을 줄인다.
abstract Product factoryMethod(String type)
orderPizza()
)에서 실제로 생성되는 실제 객체가 무엇인지 알 수 없게 만드는 역할인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구체적인 클래스를 지정하지 않고도 생성할 수 있는 디자인 패턴이다.
⭐ 디자인 원칙1: 클래스는 확장에 대해서는 열려 있어야 하지만 코드 변경에 대해서는 닫혀 있어야 한다.
⭐ 디자인 원칙2: 추상된 것에 의존하도록 만들어야한다. 구상 클래스에 의존하지 않도록 만든다.
피자 주문 어플리케이션
변경이 잦음
변경이 없음
Pizza orderPizza(){
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
pizza.pizza();
}
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();
} else if(type.equals("clam")){ // 메뉴 추가
pizza = new ClamPizza();
} else if(type.equals("veggie")){ // 메뉴 추가
pizza = new VeggiePizza();
}
// 바뀌지 않는 부분
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
pizza.pizza();
}
문제점: 피자 메뉴가 수정될 때마다 orderPizza 메소드는 해당 부분의 소스코드를 수정해야 한다.
=> 소스코드 변경에 대해서 닫혀있지 않다
바뀌는 부분과 바뀌지 않는 부분의 분리가 필요하다.
=> 메뉴 코드
와 기능 코드
의 분리
=> (메뉴 코드
담당 = 객체 생성 처리)하는 Factory 클래스
생성
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;
}
}
public class PizzaStore{
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza;
// 객체생성을 factory에 맡긴다.
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
pizza.pizza();
}
}
프렌차이즈 피자집 주문 어플리케이션
// 뉴욕 지역을 위한 피자 인스턴스를 관리하는 클래스 NYPizzaFactory
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.order("Veggie");
// 시카고 지역을 위한 피자 인스턴스를 관리하는 클래스 NYPizzaFactory
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.order("Veggie");
문제점
해결방법
: PizzaStore와 피자 제작 과정 전체를 묶어주면서 유연성은 지키는 방법
createPizza()
를 PizzaStore에 넣고, 추상 메소드로 정의 (서브클래스에서 피자 인스턴스 생성 가능)orderPizza()
를 추상 클래스에서 구현 (서브 클래스에서 수정하지 않는다면 orderPizza() 에서 정의한 동작을 따라야 함)createPizza
NYPizzaStore
, ChicagoPizzaStore
public abstract class PizzaStore {
void prepareToBoxing(Pizza pizza) {
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
}
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type); // pizza 객체 생성을 외부에 위임
prepareToBoxing(pizza);
return pizza; // 객체 반환
}
// factory 메소드
abstract Pizza createPizza(String type);
}
느슨한 결합 !!! Pizza 객체 생성과 pizza의 주문처리가 클래스로 완전히 분리됨
public class NYPizzaStore extends PizzaStore{
Pizza createPizza(String type){
if(type.equals("cheese")){
return new CheesePizza();
} else if(type.equals("pepperoni")){
return new PepperoniPizza();
} else if(type.equals("clam")){
return new ClamPizza();
} else if(type.equals("veggie")){
return new VeggiePizza();
} else return null;
}
}
public abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();
void prepare() {
System.out.println("Preparing " + name);
System.out.println("Tossing dough…");
System.out.println("Adding sauce…");
System.out.println("Adding toppings: ");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
public String getName() {
return name;
}
}
public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza () {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}
void cut() {
System.out.println("Cutting the pizza into square slices"); // 사각형 형태로 자르기 위해 cut 메소드 오버라이드하여 새로 구현
}
}
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan ordered a " + pizza.getName() + "\n");
pizza = chicagoStore.orderPizza("cheese");
System.out.println("Joel ordered a " + pizza.getName() + "\n");
}
}
분점마다 원재료 생산 공장을 가진 프랜차이즈 피자집 주문 어플리케이션
해당 지역 원재료 공장에서 만들기
지역별로 따로 만들 필요 X
원재료를 생산할 팩토리 인터페이스
: 관련되어있는 팩토리 메소드 함수들을 여러개 묶어놓은 클래스를 만들어서 제공
public interface PizzaIngredientFactory {
public Dough createDough(); // 팩토리 메소드
public Sauce createSauce(); // 팩토리 메소드
public Cheese createCheese(); // 팩토리 메소드
public Veggies[] createVeggies(); // 팩토리 메소드
public Pepperoni createPepperoni(); // 팩토리 메소드
public Clams createClam(); // 팩토리 메소드
}
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
public Dough createDough() {
return new ThinCrustDough();
}
public Sauce createSauce() {
return new MarinaraSauce();
}
public Cheese createCheese() {
return new ReggianoCheese();
}
public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
public Clams createClam() {
return new FreshClams();
}
}
public abstract class Pizza {
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;
abstract void prepare();
void bake() {
System.out.println("Bake for 25 minutes at 350");
}
void cut() {
System.out.println 피자 클래스("Cutting the pizza into diagonal slices");
}
void box() {
System.out.println("Place pizza in official PizzaStore box");
}
void setName(String name) {
this.name = name;
}
String getName() {
return name;
}
public String toString() {
// 피자 이름 출력
}
}
: 코드가 같음 (재료만 다를 뿐 결국 준비 단계는 같기 때문에 지역별로 따로 만들 필요가 없음)
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory; // 지역별로 다른 재료 원재료 공장에서 가져오기
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
void prepare() { // 재료 원재료 공장에서 가져온 것 쓰기
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
public class NYPizzaStore extends PizzaStore {
protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); // 뉴욕 원재료 공장 만들기
if (item.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");
} else if (item.equals("clam")) {
…
}
return pizza;
}
}