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

Replace Subclass with Fields

Replace Subclass with Fields สลายลำดับชั้นเล็ก ๆ ที่ subclass ไม่มี behavior ใด ๆ — มีเพียง ค่าคงที่ที่ต่างกัน ที่ถูกคืนกลับจาก method ที่ override เท่านั้น แต่ละ method แบบนั้นกลายเป็น field บน class concrete เดียว และค่าที่แยกความต่างของ subclass ก็ย้ายเข้าไปอยู่ใน constructor ของ class นั้น ลำดับชั้นหายไป ส่วนข้อมูลที่เข้ารหัสไว้ยังคงอยู่ในรูป field ธรรมดา

คุณเจอ subclass ที่ทุก override เป็นบรรทัดเดียวคืนค่า literal: isMale() { return true; }, code() { return "M"; } subclass นั้นไม่เพิ่ม logic ใด ไม่ถือ state ต่อ instance และไม่เคยเปลี่ยนคำตอบ ทั้ง type — พร้อมไฟล์ของตัวเอง constructor ของตัวเอง ช่องของตัวเองในลำดับชั้น — มีอยู่เพียงเพื่อ hard-code ค่าคงที่สองค่า นั่นคือ Lazy Class ที่แต่งตัวเป็น subtype และทำให้ความแตกต่างระหว่าง variant มองเห็นได้ยากกว่าการเขียน field เรียงกันเป็นแถว

Person ถูกแยกเป็น subclass Male กับ Female แต่ละตัว override method เพื่อคืนค่าคงที่ตายตัว หลังจากนั้น Person เดียวถือค่าคงที่เหล่านั้นไว้เป็น field ที่ factory ตั้งให้

// Before — subclasses that only return constants
abstract class Person {
abstract isMale(): boolean;
abstract code(): string;
}
class Male extends Person {
isMale(): boolean { return true; }
code(): string { return "M"; }
}
class Female extends Person {
isMale(): boolean { return false; }
code(): string { return "F"; }
}
// After — one class, the constants become fields
class Person {
private constructor(
private readonly male: boolean,
private readonly genderCode: string,
) {}
static createMale(): Person { return new Person(true, "M"); }
static createFemale(): Person { return new Person(false, "F"); }
isMale(): boolean { return this.male; }
code(): string { return this.genderCode; }
}
classDiagram
  class Person {
    <<abstract>>
    +isMale() bool
    +code() string
  }
  class Male
  class Female
  Person <|-- Male
  Person <|-- Female
  class PersonAfter["Person"] {
    -male: bool
    -code: string
    +isMale() bool
    +code() string
  }
subclass ที่มีแต่ข้อมูลสองตัวยุบรวมเป็น class เดียวที่มี field
  1. ใช้ Replace Constructor with Factory Function กับ superclass เพื่อให้ caller สร้าง instance ผ่าน method factory แทนการเรียก new Subclass() ตรง ๆ
  2. สำหรับแต่ละ method ที่ subclass override เพื่อคืนค่าคงที่ ให้เพิ่ม field บน superclass ที่ถือค่านั้นไว้
  3. ทำให้แต่ละ method factory ส่งค่าคงที่ที่เหมาะสมตอนสร้าง instance ของ superclass
  4. เปลี่ยน method ของ superclass ให้คืนค่า field ใหม่แทนการเป็น abstract
  5. รัน test ทุกจุดเรียกควรสร้าง instance ของ superclass ด้วยค่า field ที่ถูกต้องแล้ว
  6. ลบ subclass ที่ว่างเปล่าทิ้ง เมื่อไม่เหลืออะไรอ้างถึงแล้ว

หยิบสิ่งนี้มาใช้เมื่อใดก็ตามที่งานเดียวของ subclass คือการ hard-code ค่าคืนกลับ — ไม่มี field เพิ่ม ไม่มี behavior จริง ไม่มี logic ต่อการเรียก การพับค่าเหล่านั้นเข้าไปเป็น field จะลบ type หนึ่งออก ทำให้ลำดับชั้นแบนลง และจัดเรียง variant ให้ความแตกต่างอ่านได้เป็นข้อมูลแทนที่จะเป็นนิยาม class

อย่า นำมาใช้ถ้า subclass มี behavior จริงที่แตกต่างกัน หรือถ้าคุณคาดว่าจะงอก behavior ขึ้นมา เพราะถ้าเป็นเช่นนั้น ลำดับชั้น (หรือ strategy) ก็ยังคุ้มค่าที่จะมีอยู่ ท่ากลับกันคือ Replace Fields with Subclass — เลื่อน field ที่แยกความต่างกลับขึ้นไปเป็น subtype เมื่อ behavior ไม่ใช่เพียงข้อมูล เริ่มแยกตัวออกตาม field นั้น

ใช้ Replace Subclass with Fields เมื่อหลีกเลี่ยงเมื่อ
subclass ต่างกันแค่ค่า constant ไม่มี behavior ต่างกันsubclass มี behavior ที่แตกต่างกัน — ต้องการ subclass จริง ๆ
hierarchy ที่ลึกสำหรับแค่ data variationfield เพิ่มแล้วจะทำให้ superclass มี conditional มากขึ้น
ต้องการ simplify class ที่มี subclass แค่ override constantclient ต้อง instanceof check ซึ่ง defeat วัตถุประสงค์

⚠️ ไม่ควร Replace Subclass with Fields เมื่อ:

  • subclass มี method ที่ต่างกัน แม้แค่บางส่วน
  • field เพิ่มแล้วทำให้ต้องมี switch statement แทน polymorphism
  • จำนวน variant มีมากและน่าจะเพิ่มขึ้น
subclass แบบใดเป็นตัวเลือกสำหรับ Replace Subclass with Fields?
อะไรเข้ามาแทนที่แต่ละ method ที่ override เพื่อคืนค่าคงที่หลังจาก refactoring?
ขั้นแรกตามปกติที่ทำให้ caller เลิกใช้ new Subclass() ตรง ๆ คืออะไร?
Go และ Rust สัมพันธ์กับ refactoring นี้อย่างไร?