Inline Class
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”เอา class ที่หดจนแทบไม่เหลืออะไร คือมี field ไม่กี่ตัวกับ method หนึ่งสองตัว ซึ่งไม่คุ้มจะเป็น type แยกอีกแล้ว มายุบรวมกับ class ที่ถืออยู่ behavior ไม่เปลี่ยนเลย คุณแค่ถอดชั้นที่หมดประโยชน์ออกไป
อาการของปัญหา
หัวข้อที่มีชื่อว่า “อาการของปัญหา”นี่คือสิ่งตรงข้ามของ Extract Class และเป็นยารักษา class ที่กลายเป็นชั้นกลางที่ไม่จำเป็น สัญญาณบ่งชี้: class ที่มี field เดียวและ method เล็กน้อย type ที่มีอยู่เพียงเพื่อถูกห่อโดย caller เดียว หรือความรับผิดชอบในอดีตที่การ refactor ก่อนหน้าได้ทำให้กลวงเปล่า หากการอ่าน code หมายถึงการกระโดดไปยัง class เล็ก ๆ แล้วกลับมาทันที การกระโดดนั้นเป็นภาระล้วน ๆ
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”class TrackingInformation ถือ field สองตัวและจัดรูปแบบให้ โดยมี Shipment ใช้อยู่รายเดียว ตอนแรกข้อมูลกับ method เดียวนั้นอยู่ใน type แยก หลัง refactor ทั้งหมดพับเข้าไปอยู่ใน Shipment
// Beforeclass TrackingInformation { constructor(public shippingCompany: string, public trackingNumber: string) {}
display(): string { return `${this.shippingCompany}: ${this.trackingNumber}`; }}
class Shipment { constructor(public trackingInfo: TrackingInformation) {}
status(): string { return `Shipped via ${this.trackingInfo.display()}`; }}
// Afterclass Shipment { constructor(public shippingCompany: string, public trackingNumber: string) {}
private trackingDisplay(): string { return `${this.shippingCompany}: ${this.trackingNumber}`; }
status(): string { return `Shipped via ${this.trackingDisplay()}`; }}# Beforeclass TrackingInformation: def __init__(self, shipping_company, tracking_number): self.shipping_company = shipping_company self.tracking_number = tracking_number
def display(self): return f"{self.shipping_company}: {self.tracking_number}"
class Shipment: def __init__(self, tracking_info): self.tracking_info = tracking_info
def status(self): return f"Shipped via {self.tracking_info.display()}"
# Afterclass Shipment: def __init__(self, shipping_company, tracking_number): self.shipping_company = shipping_company self.tracking_number = tracking_number
def _tracking_display(self): return f"{self.shipping_company}: {self.tracking_number}"
def status(self): return f"Shipped via {self._tracking_display()}"// Beforetype TrackingInformation struct { ShippingCompany string TrackingNumber string}
func (t TrackingInformation) Display() string { return fmt.Sprintf("%s: %s", t.ShippingCompany, t.TrackingNumber)}
type Shipment struct { TrackingInfo TrackingInformation}
func (s Shipment) Status() string { return fmt.Sprintf("Shipped via %s", s.TrackingInfo.Display())}
// Aftertype Shipment struct { ShippingCompany string TrackingNumber string}
func (s Shipment) trackingDisplay() string { return fmt.Sprintf("%s: %s", s.ShippingCompany, s.TrackingNumber)}
func (s Shipment) Status() string { return fmt.Sprintf("Shipped via %s", s.trackingDisplay())}// Beforestruct TrackingInformation { shipping_company: String, tracking_number: String,}
impl TrackingInformation { fn display(&self) -> String { format!("{}: {}", self.shipping_company, self.tracking_number) }}
struct Shipment { tracking_info: TrackingInformation,}
impl Shipment { fn status(&self) -> String { format!("Shipped via {}", self.tracking_info.display()) }}
// Afterstruct Shipment { shipping_company: String, tracking_number: String,}
impl Shipment { fn tracking_display(&self) -> String { format!("{}: {}", self.shipping_company, self.tracking_number) }
fn status(&self) -> String { format!("Shipped via {}", self.tracking_display()) }}classDiagram
class ShipmentBefore {
trackingInfo
status()
}
class TrackingInformation {
shippingCompany
trackingNumber
display()
}
class ShipmentAfter {
shippingCompany
trackingNumber
status()
trackingDisplay()
}
ShipmentBefore --> TrackingInformation : has a
TrackingInformation ..> ShipmentAfter : Inline Class กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- บน class ที่ดูดซับ ให้ประกาศ method สาธารณะของ class ที่คุณกำลังจะ inline และให้แต่ละตัวเพียงมอบหมาย (delegate) ไปยัง instance ภายในไปก่อน
- อัปเดต caller ทุกตัวของ class ที่กำลังจะลบ ให้เรียกผ่าน class ที่ดูดซับแทน
- รัน test เพื่อยืนยันว่าการมอบหมายทำงานเหมือนกันทุกประการ
- ย้าย field และ method ของ class ภายในเข้าไปใน class ที่ดูดซับทีละตัว โดยใช้ Move Field และ Move Function
- แทนที่บอดี้ของ method ที่มอบหมายแต่ละตัวด้วย logic จริงที่ตอนนี้อาศัยอยู่ในเครื่องแล้ว
- รัน test หลังการย้ายแต่ละครั้ง เพื่อให้ความล้มเหลวชี้ไปยังขั้นตอนเดียว
- ลบ class ที่ตอนนี้ว่างเปล่าทิ้ง เมื่อไม่เหลืออะไรอ้างถึงแล้ว
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”Inline class เมื่อหดเล็กลงเหลือ field เดียวที่มี behavior เล็กน้อย เมื่อมีอยู่เพียงเพื่อห่อ caller เดียว หรือเมื่อการแยกครั้งก่อนไม่เคยเติบโตเป็นความรับผิดชอบที่แท้จริงและตอนนี้เพียงเพิ่มการอ้อมค้อม
ต้นทุนคือ class ที่ดูดซับจะใหญ่ขึ้น ดังนั้นอย่า inline type ที่ยังทำงานจริงและแยกออกได้ เพราะนั่นคือทางกลับไปสู่ Large Class ท่ากลับกันคือ Extract Class ถ้าวันหลัง class ที่รวมแล้วงอกความรับผิดชอบที่สองขึ้นมา ก็แยกออกไปใหม่ได้
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Inline Class เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| class นั้นไม่ได้ทำอะไรมากพอที่จะมีชีวิตอยู่เอง | class นั้นมี responsibility ที่ชัดเจน แม้จะเล็ก |
| หลัง refactoring ครั้งก่อน class กลายเป็นเปลือก | class นั้นถูกใช้โดยหลาย class — inline จะกระจาย dependency |
| ต้องการรวม logic ก่อนจะแบ่งใหม่ด้วย Extract Class | class มี behavior ที่อาจขยายในอนาคต |
⚠️ ไม่ควร Inline Class เมื่อ:
- class นั้นมี test ของตัวเอง — inline จะทำ test ซับซ้อนขึ้น
- ชื่อ class นั้นสื่อ intent ที่มีคุณค่า แม้ body จะเล็ก
- กำลัง inline เพราะ “ดูน้อยลง” ไม่ใช่เพราะลด complexity จริง