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

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 — no field name
Breed string
}
func (d Dog) Speak() string {
return d.Name + " barks" // d.Name promoted from 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 for 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() works
repo UserRepository
}
// UserService now has .Log() without re-implementing it
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 manually
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); // manual delegation
}
}
Go
type Writer interface {
Write(p []byte) (n int, err error)
}
type CountingWriter struct {
io.Writer // embed the interface itself!
count int
}
func (cw *CountingWriter) Write(p []byte) (int, error) {
n, err := cw.Writer.Write(p) // delegate to embedded
cw.count += n
return n, err
}
// CountingWriter satisfies Writer automatically
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 promotes Name, Speak, Describe
Breed string
}
// Override Speak — Describe is still promoted from Animal
func (d Dog) Speak() string {
return d.Name + " barks"
}
type Cat struct {
Animal
}
// Cat does not override Speak — uses promoted version
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()) // explicit base call
fmt.Println(d.Describe()) // promoted from Animal
fmt.Println(d.Name) // promoted field
fmt.Println(d.Breed) // Dog's own field
fmt.Println(c.Speak()) // promoted from Animal
}
จะ embed type ใน Go struct ได้อย่างไร?
"method promotion" ใน Go embedding หมายความว่าอะไร?
ฟีเจอร์ Go ใดที่ให้ polymorphism — เรียก method เดิมบน type ต่างกัน?
Embedded type สองตัวมี method Save() เหมือนกัน จะเกิดอะไรเมื่อเรียก outerStruct.Save()?