Generalization & Inheritance
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”กลุ่ม generalization and inheritance ย้ายฟีเจอร์ทั้ง แนวตั้ง และ แนวข้าง ไปมาในลำดับชั้นของ type ที่เกี่ยวข้องกัน สมาชิกที่ subclass พี่น้องมีเหมือนกันจะดึงขึ้นไปไว้บน superclass สมาชิกที่มีแค่สาขาเดียวใช้ก็ผลักลงมา ส่วนสอง class ที่ไม่เกี่ยวกันแต่โตจนคล้ายกันก็งอก superclass ร่วมขึ้นมา และเมื่อ inheritance กลายเป็นเครื่องมือที่ผิด คือ subclass ใช้ superclass แค่เสี้ยวเดียว หรือความสัมพันธ์ “is-a” เป็นเรื่องโกหก ท่าเหล่านี้ก็ช่วยให้คุณสลับไปใช้ composition ได้โดยไม่เปลี่ยนสิ่งที่โปรแกรมทำ
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”inheritance ทรงพลังและล้ำเส้นได้ง่ายมาก เช่น method ชุดเดียวกันถูก copy-paste ลงไปใน subclass สามตัว หรือ field วางอยู่บน superclass แต่มี subclass แค่ครึ่งเดียวที่แตะ หรือ class หนึ่ง extends อีก class เพียงเพื่อยืม helper สองตัว แล้วพลอย inherit อีกสิบสองตัวที่ต้องแกล้งทำเป็นไม่มี ทุกกรณีคือลำดับชั้นที่เลิกบอกความจริงเรื่องความสัมพันธ์ระหว่าง type ไปแล้ว refactoring ในโมดูลนี้จะจัดลำดับชั้นให้ตรง หรือสลายทิ้งไปเป็น delegation เมื่อ composition เหมาะกว่า
| Code Smell | อาการที่บ่งชี้ | ท่าที่แนะนำ |
|---|---|---|
| Duplicated Code | method เดิมคัดลอกวางใน subclass พี่น้องหลายตัว | Pull Up Method / Field |
| Refused Bequest | subclass inherit แต่ต้องแกล้งทำเป็นว่า member ไม่มีอยู่ | Replace Inheritance with Delegation |
| Speculative Generality | hierarchy ที่สร้างขึ้นสำหรับอนาคตที่ยังไม่มาถึง | Collapse Hierarchy |
| Inappropriate Intimacy | subclass ใช้ superclass เพียงเสี้ยวเดียวแต่ยึดทั้งหมด | Replace Subclass with Delegation |
| Parallel Inheritance Hierarchies | เพิ่ม subclass ที่หนึ่งต้องเพิ่มอีก hierarchy ด้วย | Extract Superclass / Interface |
classDiagram
class Superclass {
+shared() members
}
class SubclassA
class SubclassB
Superclass <|-- SubclassA : pull up / push down
Superclass <|-- SubclassB : extract superclass
class Interface {
<<interface>>
+protocol()
}
Interface <|.. SubclassA : extract interface
class Delegate {
+behaviour()
}
SubclassB --> Delegate : replace inheritance / subclass with delegation โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”refactoring แปดตัว แต่ละตัวย้อนกลับได้:
- Pull Up Method / Field — เมื่อ subclass พี่น้องมีสมาชิกเหมือนกัน ให้ยกขึ้นไปไว้บน superclass จะได้อยู่ที่เดียวเป๊ะ ๆ ส่วน Push Down เป็นท่ากลับกัน คือสมาชิกที่มีแค่บาง subclass ใช้ ก็ผลักลงไปอยู่ใน subclass เหล่านั้น
- Extract Superclass / Interface — เมื่อสอง class มีฟีเจอร์ร่วมกัน ให้ยกความเหมือนขึ้นไปเป็นพ่อแม่ใหม่ และเมื่อมีเพียง protocol ร่วมกัน ให้ extract เป็น interface (เป็น trait ใน Rust)
- Replace Inheritance with Delegation — เมื่อ subclass พึ่ง superclass แค่บางส่วน ให้ถือ superclass เดิมไว้เป็น field แล้ว delegate เข้าไป
- Replace Subclass with Delegation — เมื่อ subclass แตกต่างกันตามแกนเดียว ให้แทนลำดับชั้นทั้งหมดด้วย class เดียวที่ถือ strategy object ไว้
- Collapse Hierarchy — เมื่อ subclass กับ superclass ใกล้กันเกินกว่าจะแยกเป็นสอง type ก็ยุบรวมเป็นตัวเดียว
- Replace Subclass with Fields — เมื่อ subclass ต่างกันแค่ข้อมูลคงที่ที่ method คืนออกมา ให้ยุบเหลือ class เดียวที่เก็บข้อมูลนั้นไว้เป็น field
- Replace Superclass with Delegation — เมื่อ subclass ใช้ superclass แค่บางส่วน หรือ inherit interface ที่เก้งก้างมา ให้ถือ superclass เดิมไว้เป็น field แล้ว delegate เข้าไป
- Pull Up Constructor Body — เมื่อ constructor ของ subclass พี่น้อง initialize เหมือนกัน ให้ย้ายส่วนที่เหมือนกันไปไว้ใน constructor ของ superclass แล้วเรียกขึ้นไป
แต่ละบทเรียนแสดง before-and-after เดียวกันใน TypeScript, Python, Go และ Rust สังเกตว่า Go และ Rust ไม่มี class inheritance: Go ประกอบ behavior ผ่าน struct embedding และ interface ส่วน Rust ผ่าน trait และ field ที่ถือไว้ ดังนั้นตัวอย่างในภาษาเหล่านั้นจึงจำลองเจตนาเดียวกันด้วย composition — ซึ่งอย่างที่คุณจะได้เห็น คือจุดที่ refactoring หลายตัวเหล่านี้พยายามนำคุณไปอยู่แล้ว