Dua tipe yang punya perilaku sama tidak harus menyalin kode yang sama, dan satu tipe besar tidak harus dideklarasikan sebagai satu blok raksasa. Go menggantikan pewarisan (inheritance) dengan mekanisme embedding: struct lain diletakkan di dalam struct tanpa nama field, lalu seluruh field dan method-nya bisa diakses seolah milik struct terluar. Pada chapter ini kita pelajari promosi field, promosi method, aturan saat nama bentrok, serta embedding pointer dan interface.
Field Tersemat Tanpa Nama #
Struct tersemat (embedded field) dideklarasikan dengan menuliskan hanya tipenya, tanpa nama field:
type Mobil struct {
Mesin
Merek string
}Field Mesin di dalam Mobil adalah embedded field. Nama field-nya otomatis menjadi Mesin — sama dengan nama tipenya — sehingga struct literal tetap mengisinya dengan Mesin: Mesin{DayaHP: 120}. Satu struct boleh memuat beberapa embedded field sekaligus.
Promosi Field dan Method #
Akses mobil.DayaHP diterjemahkan kompiler menjadi mobil.Mesin.DayaHP; ini disebut promosi field. Promosi juga berlaku untuk method — fungsi yang menempel pada tipe lewat receiver, bentuknya func (m Mesin) Spesifikasi() string. Method akan dibedah tuntas pada chapter Method
; untuk sekarang, anggap method sebagai fungsi milik tipe yang dipanggil dengan notasi titik. Pemanggilan mobil.Spesifikasi() mencapai method milik Mesin tanpa perlu menulis mobil.Mesin.Spesifikasi().
Program Utuh #
Berkas embedded-demo.go memperagakan promosi field, promosi method, dan satu kejutan penting tentang pemanggilan method tersemat:
package main
import "fmt"
type Mesin struct {
DayaHP int
}
func (m Mesin) Spesifikasi() string {
return fmt.Sprintf("daya %d HP", m.DayaHP)
}
type Hewan struct {
Nama string
}
func (h Hewan) Suara() string {
return "..."
}
func (h Hewan) Berbicara() string {
return h.Nama + " berbunyi " + h.Suara()
}
type Mobil struct {
Mesin
Merek string
}
type Kucing struct {
Hewan
}
func (k Kucing) Suara() string {
return "meong"
}
func main() {
mobil := Mobil{
Mesin: Mesin{DayaHP: 120},
Merek: "Kancil",
}
fmt.Println(mobil.Merek, ":", mobil.Spesifikasi())
fmt.Println("daya mesin:", mobil.DayaHP)
kucing := Kucing{Hewan: Hewan{Nama: "Oyen"}}
fmt.Println(kucing.Suara())
fmt.Println(kucing.Berbicara())
fmt.Println(kucing.Hewan.Suara())
}Jalankan program dari direktori chapter:
go run embedded-demo.goOutput yang Dihasilkan:
Kancil : daya 120 HP
daya mesin: 120
meong
Oyen berbunyi ...
...Mari kita telaah baris demi baris program di atas:
- Baris 5–11 (
Mesindan methodnya): mendeklarasikan structMesinbeserta methodSpesifikasiyang membaca fieldDayaHP. - Baris 13–23 (
Hewandan dua methodnya): mendeklarasikan structHewandengan methodSuaradanBerbicara; perhatikanBerbicaramemanggilh.Suara()— method milikHewansendiri. - Baris 25–28 (
Mobil): menyematkanMesinsebagai embedded field di samping field biasaMerek. - Baris 30–36 (
Kucing): menyematkanHewan, lalu mendefinisikan methodSuaramiliknya sendiri dengan nama yang sama seperti methodHewan. - Baris 39–44 (
mobil): membuatMobildengan literal keyed untuk embedded fieldMesin; baris 43 memanggilmobil.Spesifikasi()(promosi method) dan baris 44 membacamobil.DayaHP(promosi field). - Baris 46–47 (
kucing.Suara()): memanggil methodSuaramilikKucing— nama paling dangkal menang, sehingga hasilnyameong. - Baris 48–49 (
kucing.Berbicara()dankucing.Hewan.Suara()): methodBerbicaramilikHewanberjalan pada nilaiHewanyang tersemat, sehinggah.Suara()di dalamnya tetap menghasilkan...— bukanmeong; akses eksplisitkucing.Hewan.Suara()memperlihatkan nilai yang sama.
Embedding Bukan Inheritance #
Inheritance membangun relasi “adalah” (is-a) dengan hierarki bertingkat; embedding membangun relasi “memiliki sebagian isi” (has-a). Struktur Kucing secara teknis memuat salinan Hewan, dan setiap method Hewan menerima nilai Hewan yang ada di dalamnya. Polimorfisme sejati di Go dicapai lewat interface, bukan lewat pewarisan struct.
Nama Bentrok: Shadowing dan Ambiguitas #
Jika struct terluar mendefinisikan field dengan nama yang sama milik struct tersemat, field terluar menang — ini disebut shadowing. Nilai yang tersembunyi tetap bisa diakses lewat jalur penuh:
type Dasar struct {
Nama string
}
type Turunan struct {
Dasar
Nama string // meng-shadow Dasar.Nama
}Dengan pola di atas, t.Nama mengembalikan field Turunan.Nama, sedangkan t.Dasar.Nama mengembalikan field milik Dasar. Bentrok berbeda kasusnya bila dua struct tersemat menyediakan nama field yang sama pada kedalaman yang sama: kompiler menolak selektor ambigu dengan pesan galat ambiguous selector g.Nama, sehingga konflik semacam ini tertangkap sejak kompilasi.
Embedded Pointer dan Embedded Interface #
Embedded field juga boleh berupa pointer, seperti *Logger pada struct Server:
type Server struct {
*Logger
Alamat string
}Promosi tetap bekerja: kompiler mengikuti pointer saat mengakses field atau method. Konsekuensinya, pointer nil pada embedded pointer membuat pemanggilan method tersemat panic — pastikan pointer terisi sebelum struct dipakai. Go juga mengizinkan embedding interface; method pada struct terluar akan diteruskan ke implementasi yang tersimpan di interface tersebut — pola yang dibedah pada chapter Interface Composition
.
Referensi #
- Effective Go: Embedding — penjelasan resmi embedding dan promosi.
- Spesifikasi Go: Struct types — aturan embedded field.
- A Tour of Go: Methods — tur interaktif method dan embedding.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
embedded-demo.go
Unduh berkaspackage main
import "fmt"
type Mesin struct {
DayaHP int
}
func (m Mesin) Spesifikasi() string {
return fmt.Sprintf("daya %d HP", m.DayaHP)
}
type Hewan struct {
Nama string
}
func (h Hewan) Suara() string {
return "..."
}
func (h Hewan) Berbicara() string {
return h.Nama + " berbunyi " + h.Suara()
}
type Mobil struct {
Mesin
Merek string
}
type Kucing struct {
Hewan
}
func (k Kucing) Suara() string {
return "meong"
}
func main() {
mobil := Mobil{
Mesin: Mesin{DayaHP: 120},
Merek: "Kancil",
}
fmt.Println(mobil.Merek, ":", mobil.Spesifikasi())
fmt.Println("daya mesin:", mobil.DayaHP)
kucing := Kucing{Hewan: Hewan{Nama: "Oyen"}}
fmt.Println(kucing.Suara())
fmt.Println(kucing.Berbicara())
fmt.Println(kucing.Hewan.Suara())
}