Go tidak memiliki pewarisan (inheritance) dan kelas, sehingga penyusunan tipe dari tipe lain dilakukan melalui komposisi, dan penyematan struct (struct embedding) adalah bentuk komposisi yang disediakan bahasa ini. Penyematan menempatkan sebuah tipe ke dalam struct tanpa nama field, lalu field dan metode tipe yang disematkan naik (promoted) ke struct pembungkusnya. Topik ini membahas mekanisme tersebut, penutupan metode terpromosi, serta batasan yang membedakannya dari pewarisan.
Penyematan field tanpa nama#
Field yang dideklarasikan tanpa nama, hanya menyebut tipenya, disebut field tersemat (embedded field). Nama field itu otomatis menjadi nama tipenya sendiri, sehingga struct berikut memiliki field bernama Identitas bertipe Identitas. Program berikut menyematkan tipe Identitas ke dalam struct Sensor:
package main
import "fmt"
// Identitas menyimpan penanda satu alat pengukuran.
type Identitas struct {
Kode string
Lokasi string
}
// Sensor menyimpan pengukuran suhu beserta identitas alatnya.
type Sensor struct {
Identitas // penyematan: field bernama Identitas tanpa nama sendiri
Suhu float64
}
func main() {
sensor := Sensor{
Identitas: Identitas{Kode: "SN-01", Lokasi: "gudang"},
Suhu: 27.5,
}
fmt.Println(sensor.Kode) // field terpromosi dari Identitas
fmt.Println(sensor.Lokasi) // field terpromosi dari Identitas
fmt.Println(sensor.Suhu)
}SN-01
gudang
27.5Field Kode dan Lokasi tidak dideklarasikan pada Sensor, tetapi baris sensor.Kode tetap sah karena keduanya adalah field terpromosi (promoted fields), yaitu field dari tipe tersemat yang dapat diakses seolah milik struct pembungkusnya. Akses eksplisit melalui jalurnya juga sah, misalnya sensor.Identitas.Kode, dan bentuk inilah yang dipakai ketika struktur penyematannya perlu dinyatakan. Penulisan literal struct tetap menyebut field tersemat dengan nama tipenya, sebagaimana terlihat pada Identitas: Identitas{Kode: "SN-01", Lokasi: "gudang"}.
Keputusan antara field bernama biasa dan penyematan ditentukan oleh cara pemakaiannya. Field bernama ident Identitas sama-sama menyimpan data identitas, tetapi aksesnya selalu menulis jalur penuh, sedangkan penyematan memperpendek jalur akses menjadi sensor.Kode. Penyematan dipilih ketika struct pembungkus dianggap membawa seluruh ciri tipe yang disematkan, misalnya Sensor yang memang menampilkan dirinya sebagai alat lengkap dengan identitasnya.
Metode terpromosi dan penutupannya#
Selain field, metode dari tipe tersemat juga terpromosi, sehingga pemanggilan langsung pada struct pembungkusnya diteruskan ke tipe yang disematkan. Apabila struct pembungkus mendeklarasikan metode dengan nama yang sama, metode miliknya yang dipilih, dan metode terpromosi tetap dapat dicapai melalui nama tipe tersematnya:
package main
import "fmt"
// Identitas menyimpan kode satu alat pengukuran.
type Identitas struct {
Kode string
}
// ringkas pada Identitas merangkai kode alat.
func (i Identitas) Ringkas() string {
return fmt.Sprintf("identitas %s", i.Kode)
}
// Sensor menyimpan pengukuran suhu beserta identitas alatnya.
type Sensor struct {
Identitas
Suhu float64
}
// ringkas pada Sensor menutupi metode terpromosi dari Identitas.
func (s Sensor) Ringkas() string {
return fmt.Sprintf("sensor %s suhu %.1f", s.Kode, s.Suhu)
}
func main() {
sensor := Sensor{Identitas: Identitas{Kode: "SN-01"}, Suhu: 27.5}
fmt.Println(sensor.Ringkas())
fmt.Println(sensor.Identitas.Ringkas())
}sensor SN-01 suhu 27.5
identitas SN-01Peristiwa pada program di atas disebut penutupan (shadowing), karena nama Ringkas pada Sensor menutupi nama yang sama dari Identitas. Mekanisme ini berbeda dari overriding pada bahasa berorientasi objek, karena tidak ada pemanggilan dinamis yang mengganti perilaku metode milik Identitas itu sendiri. Pemanggilan sensor.Identitas.Ringkas() tetap menjalankan versi Identitas apa adanya, dan versi tersebut tidak terpengaruh oleh keberadaan metode dengan nama serupa pada struct pembungkus.
Promosi ini berlaku berjenjang apabila penyematannya bertingkat. Misalnya, struct luar menyematkan Sensor, dan Sensor sendiri menyematkan Identitas. Dalam susunan tersebut, struct luar tetap dapat mengakses Kode karena promosi nama menyusuri seluruh rantai penyematannya. Kedalaman seperti ini perlu dijaga tetap dangkal, karena rantai yang panjang menyulitkan pembaca menemukan letak asli sebuah field.
Komposisi, bukan pewarisan#
Penyematan menghasilkan hubungan memiliki (has-a), bukan hubungan menjadi (is-a). Sensor yang menyematkan Identitas tidak menjadi subjenis Identitas, dan keduanya tetap dua tipe yang berbeda tanpa ikatan turunan. Penugasan berikut gagal dikompilasi dengan pesan yang menyatakan ketidakcocokan tipe:
// tidak-dijalankan
var i Identitas = sensor // gagal dikompilasi: cannot use sensor (variable of struct type Sensor) as Identitas value in variable declarationBatasan ini sengaja dirancang. Pewarisan menautkan dua tipe sedemikian rupa sehingga perubahan tipe induk mengubah perilaku seluruh turunannya secara tersembunyi. Komposisi menjaga tiap tipe berdiri sendiri, dan kerja sama dinyatakan secara eksplisit di tempat penyematannya. Kebutuhan polimorfisme, yaitu kemampuan satu nilai menggantikan nilai lain yang setipe, ditangani Go melalui interface, bukan melalui hierarki tipe.
Polanya tetap memberi ruang yang luas. Satu lembar pencatatan dapat memuat banyak Sensor, dan tiap Sensor membawa Identitas beserta Suhu miliknya sendiri. Hubungan has-a semacam ini tidak memerlukan ikatan turunan, sehingga dua tipe yang bekerja sama dapat berubah masing-masing tanpa menyeret yang lain. Ketika perilaku bersama memang dibutuhkan, interface menetapkannya melalui sekumpulan metode yang dipenuhi secara implisit.
Kesalahan umum#
- Membaca penyematan sebagai pewarisan. Penyebabnya adalah kebiasaan dari bahasa berorientasi objek, dan akibatnya kode mengharapkan perilaku turunan yang tidak pernah dijanjikan Go. Perbaikannya, perlakukan tipe tersemat sebagai field biasa yang field dan metodenya dapat diakses langsung, lalu gunakan interface bila polimorfisme dibutuhkan.
- Menyematkan dua tipe yang sama-sama memuat nama metode, lalu memanggilnya tanpa jalur eksplisit. Kompilasi gagal dengan pesan
ambiguous selector alat.Ringkaskarena pemilihan metodenya ambigu. Perbaikannya, sebut jalurnya secara eksplisit, misalnyaalat.Identitas.Ringkas(). - Memberi nama pada field yang dimaksudkan sebagai penyematan. Bentuk
Identitas Identitasadalah field bernama biasa, sehingga promosi field dan metodenya tidak terjadi. Perbaikannya, tulis cukupIdentitastanpa nama field agar penyematan terbentuk. - Mengira field terpromosi terpisah dari struct tersematnya saat disalin. Penugasan struct menyalin seluruh field, termasuk struct tersemat beserta isinya, sehingga salinan
sensormemiliki salinanIdentitas-nya sendiri. Perbaikannya, gunakan receiver pointer atau field bertipe pointer bila dua salinan harus berbagi satu keadaan yang sama.
Ringkasan#
- Penyematan mendeklarasikan field tanpa nama, dan nama fieldnya otomatis menjadi nama tipe yang disematkan.
- Field dan metode tipe tersemat naik menjadi field dan metode terpromosi pada struct pembungkusnya.
- Metode dengan nama sama pada struct pembungkus menutupi metode terpromosi, dan jalur eksplisit mengakses versi tersembunyi.
- Penyematan adalah komposisi, bukan pewarisan, karena tidak ada hubungan subjenis yang ikut terbentuk.
Referensi#
- Effective Go — Go Documentation — Bagian embedding beserta perbedaannya dari subclassing.
- The Go Programming Language Specification — The Go Project — Aturan resmi field struct, termasuk field tersemat dan promosinya.