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

Struct Embedding

TypeScript ให้คุณ extend class ด้วย extends (single inheritance) และเลียนแบบ multiple inheritance ได้คร่าว ๆ ผ่าน mixin โดย mental model คือ hierarchy: child IS-A parent

Go ไม่มี class และไม่มี inheritance แต่ใช้ embedding แทน คือ type หนึ่งรวม type อื่นไว้เป็น anonymous field ได้ แล้ว method กับ field ของ type ที่ embed เข้ามาจะถูก promote ขึ้นมาที่ outer type โดย mental model คือ composition: outer HAS-A inner

ประกาศ embedded type ด้วยการเขียนชื่อ type ลงไปตรง ๆ โดยไม่ต้องตั้งชื่อ field ภายใน struct body

TypeScript
// TypeScript — class inheritance
class Animal {
name: string;
constructor(name: string) { this.name = name; }
speak(): string { return `${this.name} makes a sound`; }
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
speak(): string { return `${this.name} barks`; }
}
const d = new Dog("Rex", "Labrador");
console.log(d.speak()); // Rex barks
console.log(d.name); // Rex — inherited
Go
// Go — struct embedding (composition)
type Animal struct{ Name string }
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
type Dog struct {
Animal // embedded — ไม่มีชื่อ field
Breed string
}
func (d Dog) Speak() string {
return d.Name + " barks" // d.Name promote มาจาก Animal
}
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // Rex barks
fmt.Println(d.Animal.Speak()) // Rex makes a sound
fmt.Println(d.Name) // Rex — promoted field
}

เมื่อ method มีอยู่เฉพาะบน embedded type (ไม่ถูก override) Go จะ promote method นั้นขึ้นมาที่ method set ของ outer type ให้อัตโนมัติ ผลคือ outer type satisfy interface ที่ inner type implement ได้ไปด้วยเลย

TypeScript
// TypeScript — mixin pattern สำหรับ reuse
type Serializable = {
serialize(): string;
};
function withSerialize<T extends object>(Base: new(...a: any[]) => T) {
return class extends Base implements Serializable {
serialize() { return JSON.stringify(this); }
};
}
Go
type Logger struct{ Prefix string }
func (l Logger) Log(msg string) {
fmt.Println(l.Prefix+":", msg)
}
type UserService struct {
Logger // promoted: UserService.Log() ทำงานได้
repo UserRepository
}
// UserService มี .Log() โดยไม่ต้อง implement ใหม่
func (s UserService) CreateUser(name string) {
s.Log("creating user: " + name)
// ... business logic
}

เพราะ promoted method นับเป็น method set ของ outer struct embedding จึงมักใช้เพื่อ satisfy interface โดยไม่ต้องเขียน delegation boilerplate

TypeScript
// TypeScript — implement interface แบบ manual
interface Writer {
write(data: string): void;
}
class BufferedWriter implements Writer {
private inner: Writer;
constructor(inner: Writer) { this.inner = inner; }
write(data: string) {
this.inner.write(data); // delegation แบบ manual
}
}
Go
type Writer interface {
Write(p []byte) (n int, err error)
}
type CountingWriter struct {
io.Writer // embed interface นั้นเอง!
count int
}
func (cw *CountingWriter) Write(p []byte) (int, error) {
n, err := cw.Writer.Write(p) // delegate ไปยัง embedded
cw.count += n
return n, err
}
// CountingWriter satisfy Writer อัตโนมัติ
package main
import "fmt"
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return a.Name + " makes a sound"
}
func (a Animal) Describe() string {
return "Animal: " + a.Name
}
type Dog struct {
Animal // embedding promote Name, Speak, Describe
Breed string
}
// Override Speak — Describe ยังคง promote มาจาก Animal
func (d Dog) Speak() string {
return d.Name + " barks"
}
type Cat struct {
Animal
}
// Cat ไม่ override Speak — ใช้ version ที่ promote มา
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
c := Cat{Animal: Animal{Name: "Whiskers"}}
fmt.Println(d.Speak()) // Dog override
fmt.Println(d.Animal.Speak()) // เรียก base อย่างชัดเจน
fmt.Println(d.Describe()) // promote มาจาก Animal
fmt.Println(d.Name) // promoted field
fmt.Println(d.Breed) // field ของ Dog เอง
fmt.Println(c.Speak()) // promote มาจาก Animal
}
จะ embed type ใน Go struct ได้อย่างไร?
"method promotion" ใน Go embedding หมายความว่าอะไร?
ฟีเจอร์ Go ใดที่ให้ polymorphism — เรียก method เดิมบน type ต่างกัน?
Embedded type สองตัวมี method Save() เหมือนกัน จะเกิดอะไรเมื่อเรียก outerStruct.Save()?