Design Pattern 101 — Factory Pattern
ใน Design Pattern ของ GoF (Gang of Four) Factory Pattern เป็น pattern แรกๆ ที่จัดอยู่ในกลุ่มของ Creation Pattern สำหรับสร้าง Object ในโลกของ OOP โดยที่จะมี 2 pattern คือ Factory method และ Abstract Factory ซึ่งในบทความนี้จะมาทบทวนและทำความเข้าใจว่า Factory pattern นี้ใช้สำหรับแก้ปัญหาอะไร
Factory Method
Problem
ถ้าเรามีร้าน Pizza ร้านหนึ่ง (PizzaStore) และเราก็จะมี method ที่ใช้สำหรับ order pizza ดังนี้
ถ้าเราเป็นเจ้าของร้าน Pizza ร้านหนึ่งในเมืองเล็กๆ ในการทำ pizza ให้สำหรับลูกค้า เราอาจจะต้องมี orderPizza
method เพื่อสร้างและจัดเตรียม pizza ดังนี้
Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
แต่ถ้าร้าน Pizza ของเรามี menu pizza เพิ่มเติ่มนอกจาก pizza ธรรมดาล่ะ เราจำเป็นต้องมีเงื่อนไขเพิ่มเติมเพื่อสร้าง pizza แต่ละชนิดเพื่อจัดเตรียม pizza ต่อไป โดยการสร้าง instance ของ pizza แต่ละ type ซึ่งพิจารณาชื่อของชนิดของ pizza
Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("mushrooms")) {
pizza = new MushroomsPizza();
} else {
pizza = new CheesePizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
แต่ถ้าเราต้องการแก้ไขชนิดของ pizza เราก็จะต้องแก้ไข orderPizza
method นี้เสมอเพื่อเพิ่มหรือลดชนิดของ pizza ซึ่งจะเห็นได้ว่า orderPizza
method ไม่ close for modification ขัดกับกฎ SOLID ในส่วนของ Open/Close
Solution
ดังนั้นเราจะเอาส่วนที่สร้าง pizza instance ออกไปจาก orderPizza
method เสีย โดยที่เอาไปสร้างเป็น createPizza
method ในส่วนนี้เราเรียกว่า Factory Method และเรียกใช้ createPizza
method ใน orderPizza
method ดังนี้
Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("greek")) {
pizza = new GreekPizza();
} else if (type.equals("mushrooms")) {
pizza = new MushroomsPizza();
} else if (type.equals("cheese")) {
pizza = new CheesePizza();
}
return pizza;
}
ซึ่งจะเห็นได้ว่าถ้าเราต้องการแก้ไขชนิดของ pizza จะไม่ต้องมาแก้ไขใน orderPizza
method อีกต่อไป แต่ไปแก้ไขใน createPizza
method แทน เป็นการแยกความรับผิดชอบ (responsibility) ในการสร้าง pizza ให้กับ Factory method และ orderPizza
method ก็จะรับผิดชอบเฉพาะการเตรียม pizza เท่านั้น
Factory Method Pattern เป็นการกำหนด interface สำหรับสร้าง object แต่ให้ subclass สร้าง instance ได้เอง โดย Factory Method เป็นการยกหน้าที่การสร้าง instance ให้กับ subclass
Abstract Factory
Problem
เมื่อร้านของเรามีชื่อเสียงขึ้นมา ก็มีคนมาซื้อแฟรนไซส์ร้าน Pizza ของเราไปในจังหวัดอื่นๆ คือ เชียงใหม่ และ ขอนแก่น และร้านเดิมที่กรุงเทพ ซึ่งแต่ละร้านก็จะมีวิธีเตรียม pizza เหมือนกัน แต่ชนิดของ pizza จะเปลี่ยนแปลงตามจังหวัดที่ร้านตั้งอยู่ โดยปรับ PizzaStore
class ให้เป็น abstract class และให้ createPizza
เป็น abstract method
public abstract class PizzaStore { Pizza orderPizza(String type) {
Pizza pizza = createPizza(type); pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box(); return pizza;
} abstract Pizza createPizza(String type);
}
และร้านสาขาก็จะ extend มาจาก PizzaStore
class ดังนี้
public class BangkokPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null; if (type.equals("pepperoni")) {
pizza = new BKKPepperoniPizza();
} else if (type.equals("greek")) {
pizza = new BKKGreekPizza();
} else if (type.equals("mushrooms")) {
pizza = new BKKMushroomsPizza();
} else if (type.equals("cheese")) {
pizza = new BKKCheesePizza();
} return pizza;
}}public class ChiangmaiPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null; if (type.equals("pepperoni")) {
pizza = new CMPepperoniPizza();
} else if (type.equals("greek")) {
pizza = new CMGreekPizza();
} else if (type.equals("mushrooms")) {
pizza = new CMMushroomsPizza();
} else if (type.equals("cheese")) {
pizza = new CMCheesePizza();
} return pizza;
}
}public class KhonkaenPizzaStore extends PizzaStore {
@Override
Pizza createPizza(String type) {
Pizza pizza = null; if (type.equals("pepperoni")) {
pizza = new KKPepperoniPizza();
} else if (type.equals("greek")) {
pizza = new KKGreekPizza();
} else if (type.equals("mushrooms")) {
pizza = new KKMushroomsPizza();
} else if (type.equals("cheese")) {
pizza = new KKCheesePizza();
} return pizza;
}
}
จะเห็นได้ว่าชนิดของ pizza ในแต่ละสาขามีความหลากหลายมาก แต่จริงๆ แล้วส่วนที่แตกต่างกันคือส่วนผสมของ pizza แต่ละชนิดต่างหาก
Solution
ดังนั้นเราจะสร้าง ingredient factory ของแต่ละสาขาขึ้น โดยที่จะเป็นการสืบทอดมาจาก PizzaIngredientFactory
interface ซึ่งกำหนด method ของสวนผสมต่างๆ
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
}
ซึ่งในแต่ละร้านสาขาก็จะ implement ส่วนผสมต่างๆ ที่หาได้ตามท้องถิ่นของตัวเอง
public class ChiangmaiPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new ChiangmaiSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoChesse();
}
}
เราก็ refactor class ชนิดของ pizza ให้รองรับ ingredient factory class ดังนี้
public class CheesePizza extends Pizza {
Dough dough;
Sauce sauce;
Cheese cheese;
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
และใน pizza store class ของแต่ละสาขาก็ refactor ให้รองรับ ingredient factory
public class ChiangmaiPizzaStore extends PizzaStore {
PizzaIngredientFactory ingredientFactory;
public ChiangmaiPizzaStore(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("pepperoni")) {
pizza = new PepperoniPizza(ingredientFactory);
} else if (type.equals("greek")) {
pizza = new GreekPizza(ingredientFactory);
} else if (type.equals("mushrooms")) {
pizza = new MushroomsPizza(ingredientFactory);
} else if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
}
return pizza;
}
}
Abstract Factory pattern จะมี interface สำหรับสร้างกลุ่มของ object ที่สัมพันธ์กันหรือ dependency กันโดยปราศจากการกำหนด concrete class ของ object นั้นๆ
สรุป
จากการทบทวน Factory Pattern ทั้ง Factory Method และ Abstract Factory ซึ่งเป็น creation pattern นั้นจะเห็นได้ว่าถ้าเราใช้งาน pattern นี้จะทำให้ code ที่เขียนมี loose couple และซ่อน (encapsulate) การสร้าง object ทำให้ code เรา decouple จาก concrete (implementation) type ซึ่งบทความนี้อ้างอิงตัวอย่างจาก Head First Design Pattern ซึ่งสามารถอ่านรายละเอียดเพิ่มเติมได้จากหนังสือเล่มนี้