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

Replace Type Code with Subclasses

นำ field ที่ถือ Type Code — enum หรือ string อย่าง "engineer", "manager", "sales" — ที่ขับเคลื่อน behavior เชิงเงื่อนไข แล้วแทนที่แต่ละกิ่งด้วย Subclass class ฐานที่ใช้ร่วมกันประกาศการทำงาน ส่วน Subclass แต่ละตัวจัดเตรียมเวอร์ชันของตัวเอง switch หายวับไป และการเพิ่มชนิดใหม่ก็หมายถึงการเพิ่ม class แทนที่จะแก้ไขทุกเงื่อนไข

กลิ่นเหม็นคือ Type Code ที่จับคู่กับ logic switching: field เดียวที่ค่าถูกตรวจสอบซ้ำ ๆ ด้วย switch หรือ if ที่ต่อกันเป็นลูกโซ่ซึ่งเลือก behavior ปัญหาทบทวีคูณ switch เดียวกันบน Type Code เดียวกันมักจะปรากฏในหลาย method ดังนั้นทุกชนิดใหม่จึงหมายถึงการแก้ไขแต่ละ method และคอมไพเลอร์ก็ไม่ช่วยอะไรเมื่อคุณลืมไปสักอัน Polymorphism เปลี่ยนกิ่งที่กระจัดกระจายเหล่านั้นให้เป็น dispatch เดียวที่ภาษาทำให้คุณ

Employee ที่ค่าจ้างขึ้นอยู่กับ string type ซึ่งถูกตรวจสอบด้วย switch หลังจากนั้น พนักงานแต่ละชนิดเป็น Subclass ที่รู้กฎค่าจ้างของตัวเอง

// Before
class Employee {
constructor(public type: string, public base: number) {}
payAmount(): number {
switch (this.type) {
case 'engineer': return this.base;
case 'manager': return this.base * 1.2;
case 'sales': return this.base + 500;
default: throw new Error(`unknown type ${this.type}`);
}
}
}
// After
abstract class Employee {
constructor(public base: number) {}
abstract payAmount(): number;
}
class Engineer extends Employee {
payAmount(): number { return this.base; }
}
class Manager extends Employee {
payAmount(): number { return this.base * 1.2; }
}
class Salesperson extends Employee {
payAmount(): number { return this.base + 500; }
}
flowchart TD
  subgraph Before["Before — a type code"]
    A["Employee<br/>type: 'engineer' | 'manager' | 'sales'"]
    A --> B{"switch type"}
    B --> C["pay logic for engineer"]
    B --> D["pay logic for manager"]
    B --> E["pay logic for sales"]
  end
  subgraph After["After — a hierarchy"]
    F["Employee (abstract)<br/>payAmount()"]
    F --> G["Engineer"]
    F --> H["Manager"]
    F --> I["Salesperson"]
  end
  Before -.->|"Replace Type Code with Subclasses"| After
switch ของ Type Code กลายเป็นลำดับชั้น class ที่มีหนึ่ง method ต่อหนึ่ง Subclass
  1. ถ้า Type Code ยังไม่ถูกห่อหุ้ม ให้ซ่อนไว้หลัง accessor ก่อน caller จะได้ไม่พึ่ง field ดิบ
  2. สร้าง Subclass สำหรับค่าหนึ่งค่าของ Type Code จัดเตรียม factory (หรือ constructor) ที่คืน Subclass ที่ถูกต้องสำหรับ Code ที่กำหนด เพื่อให้การสร้างอยู่ในจุดเดียว
  3. ย้าย behavior สำหรับค่านั้น — กิ่งหนึ่งของ switch — เข้าไปเป็น method ที่ override บน Subclass
  4. รัน test
  5. ทำซ้ำสำหรับค่า Type Code ที่เหลือแต่ละค่า โดยลบแต่ละกิ่งออกจากเงื่อนไขเดิมไปเรื่อย ๆ
  6. เมื่อ branch สุดท้ายหายไป ให้ลบ switch ที่ว่างเปล่าออก และถ้าไม่มีใครต้องใช้แล้ว ก็ลบ field Type Code ทิ้งด้วย ตอนนี้ operation ฐานกลายเป็น abstract ที่ subclass ทุกตัวเป็นคน implement

ใช้ Subclasses เมื่อชนิดถูกตรึงไว้ตลอดอายุของ object และเมื่อมีหลาย method ที่แตกกิ่งบน Code เดียวกัน Polymorphism ยุบกิ่งเหล่านั้นและให้คอมไพเลอร์รับประกันว่าทุก Subclass อิมพลีเมนต์การทำงาน ดังนั้นเคสที่ลืมไปจึงกลายเป็น error ตอน build แทนที่จะเป็นเรื่องเซอร์ไพรส์ตอน runtime

แต่ Subclasses ไม่ใช่เครื่องมือที่ถูกต้องเสมอไป หากชนิดของ object เปลี่ยนระหว่างอายุ — ออเดอร์ที่ขยับจาก “pending” ไป “shipped” ไป “delivered” — คุณไม่สามารถสลับ class ได้ ดังนั้นจึงต้องหันไปใช้แพตเทิร์น State: ถือ object สถานะแยกต่างหากที่เอนทิตีสามารถแทนที่ได้เมื่อเปลี่ยนผ่าน และหากคุณต้องการแค่ให้อัลกอริทึมหนึ่งแปรเปลี่ยน แทนที่จะเป็น behavior ทั้งตระกูล Strategy — การฉีด objectbehavior เข้าไป — เบากว่าลำดับชั้นเต็มรูปแบบ เลือก Subclasses สำหรับตัวตนที่คงที่ State สำหรับตัวตนที่เปลี่ยนแปลง และ Strategy สำหรับอัลกอริทึมเดี่ยวที่สลับได้

ใช้ Replace Type Code with Subclasses เมื่อหลีกเลี่ยงเมื่อ
แต่ละ type มี behavior ที่แตกต่างกันชัดเจนtype เปลี่ยนได้ใน runtime — ใช้ State pattern แทน
ต้องการ polymorphism แทน switch statementobject ต้องการเป็นหลาย type พร้อมกัน
type ใหม่จะเพิ่มขึ้นบ่อยsubclass hierarchy จะ combine กับ hierarchy อื่น

⚠️ ไม่ควร Replace Type Code with Subclasses เมื่อ:

  • type เปลี่ยนได้หลัง object ถูกสร้าง — ต้องใช้ State แทน
  • subclass จะมีแค่ data ต่างกัน ไม่มี behavior ต่างกัน — ใช้ parameter แทน
  • สร้าง class hierarchy ที่ลึกเกินไป ทำให้ navigate ยาก
Replace Type Code with Subclasses กำจัดอะไรออกไป?
ทำไมคอมไพเลอร์จึงช่วยได้หลังจากการรีแฟคเตอร์นี้?
ออเดอร์ขยับจาก pending ไป shipped ไป delivered ระหว่างอายุ แพตเทิร์นใดเหมาะที่สุด?
เมื่อใดที่แพตเทิร์น Strategy เบากว่าลำดับชั้น Subclass เต็มรูปแบบ?