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

Embedding

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 field
fmt.Println(e.Describe()) // promoted method

เมื่อคุณเข้าถึง 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()

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

ความแตกต่างสำคัญ:

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 methodsEmbedded 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 embedding
type 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)
}
สิ่งที่ได้ประโยชน์ต้นทุน
struct embeddingreuse fields และ methods โดยไม่ต้อง inheritancefield shadowing ถ้า outer type มี field ชื่อเดียวกับ embedded type
interface embeddingcompose 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

`Employee` embeds `Named` คุณเข้าถึง field `Name` ที่ promote มาได้อย่างไร?
คุณ embed `Named` ใน `Employee` และประกาศ `func (e Employee) Describe() string` ด้วย `e.Describe()` เรียก method ใด?
Interface `io.ReadWriter` embeds `io.Reader` และ `io.Writer` type ที่ตอบสนอง `io.ReadWriter` ตอบสนอง... โดยอัตโนมัติ
ความแตกต่างหลักระหว่าง Go embedding กับ Java inheritance คืออะไร?