ข้ามไปยังเนื้อหา

Replace Constructor with Factory Function

เปลี่ยนจากการเรียก constructor ตรง ๆ มาเรียก factory function แทน ตัว constructor มีข้อจำกัดสองข้อ คือต้องใช้ชื่อเดียวกับ type และต้องคืน instance ของ type นั้นเป๊ะ ๆ เสมอ ส่วน factory function เป็นอิสระจากทั้งสองข้อ คือตั้งชื่อให้สื่อความหมายได้ เลือกได้ว่าจะคืน subtype ตัวไหน คืน instance ที่ cache ไว้ก็ได้ หรือจะ validate ก่อนสร้างก็ได้ caller แค่บอก factory ว่าต้องการอะไร แล้ว factory เป็นคนตัดสินใจว่าจะสร้างอย่างไร

constructor ดิบ ๆ ทำให้การตัดสินใจเรื่องการสร้างรั่วไปถึง caller ทุกตัว ลองดู new Employee("Sara", "manager") แล้ว caller ต้องจำเองว่า string ตัวไหนแปลว่าอะไร ที่หนักกว่านั้นคือเมื่อ type มีหลายแบบ เช่น manager เป็นพนักงานคนละชนิดกับ engineer constructor เปล่า ๆ คืน subtype ที่ถูกต้องไม่ได้ caller จึงต้องแตก branch ตาม type code เอง แถมชื่อ constructor ยังถูกตรึงไว้กับ class จะเรียกว่า hire, fromJson หรือ default ก็ไม่ได้ ทุกข้อชี้ตรงกันว่า constructor แคบเกินไปสำหรับงานนี้

type พนักงานที่ constructor รับ stringtype หลังจากนั้น function factory เลือก subtype ที่ถูกต้องและอ่านได้ชัดเจน

// Before
class Employee {
constructor(public name: string, public type: string) {}
monthlyBonus(): number {
return this.type === "manager" ? 1000 : 200;
}
}
const e = new Employee("Sara", "manager");
// After
abstract class Employee {
constructor(public name: string) {}
abstract monthlyBonus(): number;
}
class Manager extends Employee {
monthlyBonus(): number {
return 1000;
}
}
class Engineer extends Employee {
monthlyBonus(): number {
return 200;
}
}
function hire(name: string, role: string): Employee {
switch (role) {
case "manager":
return new Manager(name);
case "engineer":
return new Engineer(name);
default:
throw new Error(`Unknown role: ${role}`);
}
}
const e = hire("Sara", "manager");
  1. เขียน factory function ที่ข้างในไม่ทำอะไรนอกจากเรียก constructor เดิมแล้วคืนผลลัพธ์ ตั้งชื่อให้บอกว่าสร้างอะไร เช่น hire หรือ defaultConfig
  2. เปลี่ยนเส้นทาง caller ไปยัง factory ทีละตัว แทนการเรียก constructor แต่ละครั้งด้วยการเรียก factory รัน test หลังแต่ละครั้ง
  3. เมื่อ caller ทุกตัวผ่าน factory แล้ว คุณก็ได้จุดคอขวดจุดเดียว ตอนนี้จะเสริมอะไรก็ได้ เช่น validate argument คืน instance ที่ cache ไว้ หรือแตก branch ไปยัง subtype
  4. ถ้ากำลังจะเพิ่ม subtype ก็สร้างขึ้นมา ย้าย behavior ของแต่ละ branch ไปไว้บน subtype ที่ตรงกัน แล้วให้ factory คืนตัวที่ถูกต้องตาม input
  5. รัน test หลังเปลี่ยนแต่ละครั้ง signature ของ factory คงที่ในสายตา caller ถึงแม้สิ่งที่สร้างข้างหลังจะเปลี่ยนไปเรื่อย ๆ
  6. หากภาษาอนุญาต ให้ทำ constructor ดิบเป็น private หรือ non-public เพื่อให้ factory เป็นทางเข้าที่รองรับเพียงทางเดียว

หยิบ factory มาใช้เมื่อการสร้างต้องการชื่อที่ชัดกว่าที่ชื่อ class จะให้ได้ เมื่อ type จริงที่ต้องสร้างขึ้นกับ input เมื่อคุณอยากซ่อน instance หรือรวมไว้เป็น pool หรือเมื่อต้องการ validate ก่อน object จะเกิดขึ้นจริง factory จะกลายเป็นประตูบานเดียวที่มีชื่อดี ๆ ให้ object เดินเข้าระบบ

ข้อแลกเปลี่ยนคือชั้นของการอ้อม: caller ไม่เห็น new และ type ที่เป็นรูปธรรมโดยตรงอีกต่อไป ซึ่งอาจทำให้การไล่ตาม code ชัดเจนน้อยลงเล็กน้อย สำหรับ type ค่าธรรมดาที่มีรูปทรงเดียวและไม่มีตัวแปร constructor โดยตรงเรียบง่ายกว่า และ factory ก็เป็นพิธีรีตองที่ไม่จำเป็น เพิ่ม factory เมื่อความแข็งทื่อของ constructor เริ่มทำให้คุณต้องเสีย — ไม่ใช่ก่อนหน้านั้น

ใช้ Replace Constructor with Factory เมื่อหลีกเลี่ยงเมื่อ
ต้องการ subtype ที่แตกต่างกันตาม inputtype เดียว รูปทรงเดียว ไม่มี variant
ชื่อ constructor ไม่สื่อความหมายของ use caseเพิ่ม indirection โดยไม่ได้รับประโยชน์จริง
ต้องการ validate input ก่อน constructfactory ที่ซับซ้อนเกินไปจนเข้าใจยากกว่า constructor

⚠️ ไม่ควร Replace Constructor with Factory เมื่อ:

  • type ธรรมดา ไม่มี variant และไม่ต้องการชื่อพิเศษ — constructor ตรงไปตรงมากว่า
  • factory ทำแค่ return new Thing(args) โดยไม่เพิ่มค่าอะไร
  • team ไม่คุ้นชินกับ pattern นี้และ codebase ไม่มี convention รองรับ
อะไรคือข้อจำกัดสำคัญของ constructor ที่ function factory หลีกเลี่ยงได้?
อะไรคือขั้นตอนแรกที่ปลอดภัยเมื่อนำ factory เข้ามา?
ทำไมการให้การสร้างทั้งหมดผ่าน factory เดียวจึงช่วยได้?
เมื่อใดที่ constructor ธรรมดาดีกว่า factory?