Embedding
Composition แทน Inheritance
หัวข้อที่มีชื่อว่า “Composition แทน Inheritance”Go ไม่มี extends, ไม่มี base class, และไม่มี virtual dispatch แต่มี embedding: คุณวาง type หนึ่งไว้ใน type อื่นโดยไม่มีชื่อ field และ outer type จะได้รับ fields และ methods ทั้งหมดของ inner type ราวกับว่าเป็นของตัวเอง
type Named struct { Name string}
func (n Named) Describe() string { return "Name: " + n.Name }
type Employee struct { Named // embedded — ไม่มีชื่อ field Department string}ตอนนี้ Employee มี field Name และ method Describe() ที่ promote จาก Named:
e := Employee{Named: Named{Name: "Alice"}, Department: "Engineering"}fmt.Println(e.Name) // promoted fieldfmt.Println(e.Describe()) // promoted methodPromotion ทำงานอย่างไร
หัวข้อที่มีชื่อว่า “Promotion ทำงานอย่างไร”เมื่อคุณเข้าถึง e.Name, Go มองหา field หรือ method ชื่อ Name ใน Employee โดยตรงก่อน ถ้าไม่พบ จะลงไปใน embedded type แต่ละอันตามลำดับ การ lookup นี้เป็นแค่ syntactic sugar — รูปแบบยาว e.Named.Name ใช้ได้เสมอ
คุณสามารถ override promoted method ได้โดยประกาศ method ที่มีชื่อเดียวกันบน outer type:
func (e Employee) Describe() string { return fmt.Sprintf("%s in %s", e.Name, e.Department)}ตอนนี้ e.Describe() เรียก method ของ outer แต่ inner method ยังเข้าถึงได้ผ่าน e.Named.Describe()
Interface embedding
หัวข้อที่มีชื่อว่า “Interface embedding”Interfaces ยัง embed interfaces อื่นได้:
type Stringer interface { String() string}
type Describer interface { Stringer // embeds Stringer Describe() string}type ใดที่ตอบสนอง Describer ก็ตอบสนอง Stringer โดยอัตโนมัติ standard library ใช้สิ่งนี้อย่างกว้างขวาง: io.ReadWriter embeds io.Reader และ io.Writer
Embedding ไม่ใช่ Inheritance
หัวข้อที่มีชื่อว่า “Embedding ไม่ใช่ Inheritance”ความแตกต่างสำคัญ:
| Inheritance (Java/C++) | Embedding (Go) |
|---|---|
| Virtual dispatch: method call บน outer type สามารถ dispatch ไปยัง override ของ inner | ไม่มี virtual dispatch: การเรียก inner method เรียก method นั้น เสมอ ไม่ว่า outer type จะมีอะไร |
instanceof / type hierarchy | ไม่มี hierarchy — types ไม่มีความสัมพันธ์ |
| Base class อ้างถึง derived class ได้ผ่าน virtual methods | Embedded type ไม่รู้จัก outer type |
เพราะไม่มี virtual dispatch, embedding ไม่อนุญาต polymorphism บน outer type ในการได้ polymorphism คุณยังต้องการ interface
package main
import "fmt"
type Named struct { Name string}
func (n Named) Describe() string { return "Name: " + n.Name}
type Employee struct { Named Department string}
// Interface embeddingtype Stringer interface { String() string}
type Describer interface { Stringer Describe() string}
func (e Employee) String() string { return fmt.Sprintf("%s (Dept: %s)", e.Name, e.Department)}
func print(d Describer) { fmt.Println(d.String()) fmt.Println(d.Describe())}
func main() { e := Employee{ Named: Named{Name: "Alice"}, Department: "Engineering", } // promoted field and method fmt.Println(e.Name) fmt.Println(e.Describe()) print(e)}Loading Go runtime (first run only, ~8 MB)…
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| สิ่งที่ได้ | ประโยชน์ | ต้นทุน |
|---|---|---|
| struct embedding | reuse fields และ methods โดยไม่ต้อง inheritance | field shadowing ถ้า outer type มี field ชื่อเดียวกับ embedded type |
| interface embedding | compose interface ขนาดใหญ่จาก interface เล็ก | ยิ่ง interface ใหญ่ implement ยิ่งยาก |
| method promotion | เรียก method ของ embedded type โดยตรง | อาจสับสนว่า method มาจาก type ใดเมื่อมี embedding หลายชั้น |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- embedding = inheritance — embedded type ไม่รู้จัก outer type; ไม่มี virtual dispatch; ไม่มี IS-A relationship
- embedding สร้าง subtype relationship — Person ที่ embed Address ไม่ใช่ subtype ของ Address — เป็นแค่ composition
- promoted method ทำงานบน outer type — promoted method เรียก receiver ของ embedded type เสมอ ไม่ใช่ outer type
💡 ตัวอย่างจากของจริง
io.ReadWriter embed io.Reader และ io.Writer — type ที่ implement ทั้งสองอัตโนมัติ satisfy io.ReadWriter โดยไม่ต้องประกาศเพิ่ม
sync.RWMutex expose Lock/Unlock จาก embedded sync.Mutex — embedding ช่วยเพิ่ม write-lock API โดยไม่ต้อง duplicate code