Design Pattern 101 — Factory Pattern

Phayao Boonon
4 min readAug 14, 2019

--

ใน 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 ซึ่งสามารถอ่านรายละเอียดเพิ่มเติมได้จากหนังสือเล่มนี้

--

--

Phayao Boonon

Software Engineer 👨🏻‍💻 Stay Hungry Stay Foolish