Embedding dalam satu contoh
Field yang punya tipe tetapi tidak punya nama adalah embedded field. Field dan method dari tipe yang di-embed bisa diakses langsung dari tipe luar.
Output:
Ana
Hi, I'm Ana
ana@example.com
{User:{Name:Ana Email:ana@example.com} Level:2}
Nama embedded field adalah nama tipenya, User. Dengan nama itulah kamu menyebutnya di literal (User: User{...}) dan saat kamu butuh nilai dalamnya (a.User). Promoted field tidak bisa diisi dengan nama pendeknya di literal: Admin{Name: "Ana"} gagal dengan unknown field Name in struct literal of type Admin.
Promoted method memenuhi interface
Promosi menambahkan method dari tipe yang di-embed ke method set tipe luar. Jadi tipe luar memenuhi setiap interface yang dipenuhi tipe dalam.
Aturan method set mengikuti tipe yang di-embed. Embedding T mempromosikan method value receiver milik T ke nilai luar dan juga ke pointer luar, tetapi method pointer receiver milik *T hanya ke pointer luar. Embedding *T mempromosikan kedua kelompok itu ke keduanya. Kalau ragu, embed pointer-nya, atau pakai tipe luar lewat pointer.
Embedding pointer punya harga: pointer-nya bisa nil. Service{} tanpa Logger tetap bisa dikompilasi, lalu svc.Log(...) akan panic.
Embedding bukan inheritance
Embedding terlihat seperti subclass, tetapi ada tiga hal yang berperilaku berbeda dari Java atau Python.
Tipe luar bukan tipe dalam. Sebuah Admin bukanlah User. Fungsi yang menerima User tidak akan menerima Admin; kirim a.User.
Tidak ada virtual dispatch. Promoted method berjalan pada nilai yang di-embed, dan ia tidak tahu apa pun tentang tipe luar. Kalau method itu memanggil method lain, yang dipanggil adalah versi milik tipenya sendiri, walaupun tipe luar mendefinisikan method dengan nama yang sama.
Output:
woof
The animal says ...
Di bahasa berbasis class, Speak akan mencetak "woof". Di Go, d.Speak() adalah singkatan dari d.Animal.Speak(), dan receiver panggilan itu adalah Animal. Kalau kamu ingin perilaku yang berbeda per tipe, pakai interface: kirim sebuah Sounder ke kode yang membutuhkannya.
Tipe dalam tidak tahu bahwa ia di-embed. Tidak ada super. Untuk memperluas promoted method, definisikan method dengan nama yang sama pada tipe luar dan panggil method dalamnya secara eksplisit:
func (a Admin) Greet() string {
return a.User.Greet() + " (admin)"
}
Konflik nama dan shadowing
Field atau method pada tipe luar menutupi (shadow) promoted field atau method dengan nama yang sama, seperti yang dilakukan Dog.Sound di atas. Nama yang lebih dangkal yang menang.
Kalau dua tipe yang di-embed pada kedalaman yang sama mempromosikan nama yang sama, nama itu menjadi ambigu. Program tetap bisa dikompilasi selama tidak ada yang memakai nama yang ambigu itu:
Selesaikan konflik itu dengan memakai path lengkap, atau dengan mendefinisikan ID pada tipe luar.
Embedding interface
Sebuah interface bisa berisi interface lain yang di-embed. Pustaka standar membangun interface yang lebih besar dengan cara ini:
type ReadWriter interface {
Reader
Writer
}
Struct juga bisa berisi interface yang di-embed. Struct itu lalu memenuhi interface tersebut lewat nilai apa pun yang kamu simpan di field-nya, dan kamu hanya menimpa method yang kamu perlukan. Inilah pola decorator dengan kode yang sangat sedikit:
c.Reader.Read(p) yang eksplisit itu wajib. Menulis c.Read(p) di dalam Read akan memanggil dirinya sendiri tanpa henti.
Embedding interface di test fake adalah jalan pintas yang umum: embed interface yang besar, implementasikan satu method yang dibutuhkan tes, dan biarkan sisanya. Method yang tidak diimplementasikan akan panic dengan nil pointer dereference kalau dipanggil, yang sering kali memang kamu inginkan di dalam tes.
Contoh nyata yang umum: embedding sync.Mutex
type Stats struct {
sync.Mutex
hits map[string]int
}
func (s *Stats) Hit(page string) {
s.Lock()
defer s.Unlock()
s.hits[page]++
}
Kode ini enak dibaca, tetapi juga mengekspor Lock dan Unlock sebagai bagian dari API Stats, sehingga pemanggil mana pun bisa mengunci struct-mu. Untuk tipe yang dipakai di luar package, field bernama (mu sync.Mutex) menjaga kunci itu tetap privat. Pertimbangan itu berlaku untuk semua embedding: semua yang diekspor tipe yang di-embed menjadi bagian dari permukaan publik tipemu.
Kesalahan umum
- Mengharapkan virtual dispatch. Promoted method tidak pernah memanggil override milik tipe luar.
- Mengisi promoted field di literal. Pakai nama tipe yang di-embed:
Admin{User: User{Name: "Ana"}}. - Embedded pointer yang nil.
*Tatau interface yang di-embed harus diisi sebelum method-nya dipanggil. - Permukaan API yang tidak disengaja. Embedding mengekspor semua method ekspor milik tipe dalam pada tipemu.
Pertanyaan yang sering diajukan
Apa itu struct embedding di Go?
Mendeklarasikan field hanya dengan tipe, tanpa nama: type Admin struct { User; Level int }. Field dan method dari User yang di-embed akan dipromosikan, jadi a.Name dan a.Greet() langsung bisa dipakai pada sebuah Admin. Nilai yang di-embed tetap field biasa, yang bisa diakses lewat a.User.
Apakah Go punya inheritance?
Tidak. Go tidak punya class dan tidak punya inheritance subtipe. Embedding memberi cara memakai ulang kode lewat komposisi: tipe luar mendapat method dari tipe dalam, tetapi sebuah Admin bukanlah User. Kamu tidak bisa mengirim Admin ke tempat yang mengharapkan User, dan promoted method tidak bisa memanggil balik method milik tipe luar. Polimorfisme di Go berasal dari interface.
Bagaimana cara menginisialisasi struct yang di-embed di Go?
Di dalam composite literal, sebut field yang di-embed dengan nama tipenya: Admin{User: User{Name: "Ana"}, Level: 2}. Kamu tidak bisa mengisi promoted field secara langsung di literal: Admin{Name: "Ana"} gagal dengan unknown field Name in struct literal of type Admin.
Apakah interface bisa di-embed di dalam struct di Go?
Bisa. Struct tersebut lalu memenuhi interface itu lewat nilai yang di-embed, dan kamu bisa menimpa method satu per satu. Pola ini umum untuk decorator dan test fake. Kalau field interface yang di-embed bernilai nil, memanggil method yang tidak kamu override akan panic dengan nil pointer dereference.