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:

go
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:

go
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:

bash
go run embedded-demo.go

Output yang Dihasilkan:

text
Kancil : daya 120 HP
daya mesin: 120
meong
Oyen berbunyi ...
...

Mari kita telaah baris demi baris program di atas:

  1. Baris 5–11 (Mesin dan methodnya): mendeklarasikan struct Mesin beserta method Spesifikasi yang membaca field DayaHP.
  2. Baris 13–23 (Hewan dan dua methodnya): mendeklarasikan struct Hewan dengan method Suara dan Berbicara; perhatikan Berbicara memanggil h.Suara() — method milik Hewan sendiri.
  3. Baris 25–28 (Mobil): menyematkan Mesin sebagai embedded field di samping field biasa Merek.
  4. Baris 30–36 (Kucing): menyematkan Hewan, lalu mendefinisikan method Suara miliknya sendiri dengan nama yang sama seperti method Hewan.
  5. Baris 39–44 (mobil): membuat Mobil dengan literal keyed untuk embedded field Mesin; baris 43 memanggil mobil.Spesifikasi() (promosi method) dan baris 44 membaca mobil.DayaHP (promosi field).
  6. Baris 46–47 (kucing.Suara()): memanggil method Suara milik Kucing — nama paling dangkal menang, sehingga hasilnya meong.
  7. Baris 48–49 (kucing.Berbicara() dan kucing.Hewan.Suara()): method Berbicara milik Hewan berjalan pada nilai Hewan yang tersemat, sehingga h.Suara() di dalamnya tetap menghasilkan ... — bukan meong; akses eksplisit kucing.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:

go
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:

go
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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

embedded-demo.go

Unduh berkas
go
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())
}