Nested Struct vs Embedded Struct di Go: Perbedaan Utama - Part 1
Dalam bahasa Go, kita bisa membuat tipe data sendiri yang menggabungkan banyak tipe data. Itu dibuat dengan menggunakan keyword struct
. Untuk merepresentasikan objek yang lebih kompleks, suatu struct
bisa dibentuk dengan menggunakan struct
lain, dengan cara nested atau embedded. Saat pertama kali belajar Go, saya cukup bingung membedakan nested struct dengan embedded struct. Masing-masing punya sintaks dan karakteristik yang beda.
Nested Struct
Suatu struct
yang digunakan sebagai tipe field di dalam struct
lain bisa disebut nested struct. Ini contoh nested struct:
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Person struct {
Name string
Age int
Address Address // Nested struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Address.Street) // Output: Durian Street
fmt.Println(person.Address.City) // Output: Bandung
person.Address.Print() // Output: Durian Street, Bandung
}
Pada kode di atas, terdapat tipe Address
dan tipe Person
. Tipe Address
memiliki field Street
dan City
, juga method Print()
. Tipe Person
memiliki field Name
, Age
, dan Address
.
Tipe Address
merupakan nested struct karena digunakan sebagai tipe dari field Address
yang ada di dalam Person
. Perhatikan bahwa field Street
dan City
, serta method Print()
, hanya bisa diakses melalui field Address
yang dimiliki Person
.
fmt.Println(person.Address.Street) // Output: Durian Street
fmt.Println(person.Address.City) // Output: Bandung
person.Address.Print() // Output: Durian Street, Bandung
Embedded Struct
Suatu struct
yang digunakan di dalam struct
lain tanpa menuliskan nama field disebut embedded struct. Dengan embedded struct, kita bisa menggunakan kembali (reuse) field dan method struct
yang di-embed. Ini mirip dengan inheritance pada bahasa pemrograman lain.
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Person struct {
Name string
Age int
Address // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
fmt.Println(person.City) // Output: Bandung
person.Print() // Output: Durian Street, Bandung
}
Seperti contoh kode sebelumnya, terdapat tipe Address
dan tipe Person
. Tipe Address
memiliki field Street
dan City
, juga method Print()
. Tipe Person
memiliki field Name
, Age
, dan Address
.
Tapi kali ini tipe Address
digunakan di dalam Person
secara langsung tanpa menuliskannya sebagai tipe dari field di dalam Person
. Address
bisa disebut sebagai embedded struct. Perhatikan bahwa kali ini field Street
dan City
, serta method Print()
, bisa diakses langsung dari objek Person
.
fmt.Println(person.Street) // Output: Durian Street
fmt.Println(person.City) // Output: Bandung
person.Print() // Output: Durian Street, Bandung
Mengakses Field
Gimana Kalau Ada Nama yang Sama dari Dua Struct dengan Tingkatan yang Beda?
Embedded Struct
Misalnya ada tipe Address
dan Person
. Tipe Address
punya method Print()
, field Street
, field City
, dan field Country
. Tipe Person
punya field Name
, Age
, dan Address
. Pada program di bawah, kita memberikan value “Indonesia”
untuk field Country
yang dimiliki Address
.
package main
import "fmt"
type Address struct {
Street string
City string
Country string
}
func (a Address) Print() {
fmt.Printf("%s, %s, %s\n", a.Street, a.City, a.Country)
}
type Person struct {
Name string
Age int
Address // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
fmt.Println(person.City) // Output: Bandung
fmt.Println(person.Country) // Output: Indonesia
person.Print() // Output: Durian Street, Bandung, Indonesia
}
Kalau program di atas dijalankan, maka person.Country
menghasilkan value “Indonesia”
.
fmt.Println(person.Country) // Output: Indonesia
Sekarang coba kita tambahkan field Country
di dalam Person
. Perhatikan bahwa sekarang terdapat dua field dengan nama yang sama, tapi dimiliki struct
yang beda. Field Country
ada di dalam Person
, tapi field Country
juga ada di dalam Address
. Pada program di bawah, kita memberikan value “Indonesia”
untuk field Country
yang dimiliki Address
. Tapi untuk field Country
yang dimiliki langsung oleh Person
, kita berikan value “Australia”
.
package main
import "fmt"
type Address struct {
Street string
City string
Country string
}
func (a Address) Print() {
fmt.Printf("%s, %s, %s\n", a.Street, a.City, a.Country)
}
type Person struct {
Name string
Age int
Country string
Address // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Country: "Australia",
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
fmt.Println(person.City) // Output: Bandung
fmt.Println(person.Country) // Output: Australia
person.Print() // Output: Durian Street, Bandung, Indonesia
}
Kalau program di atas dijalankan, maka person.Country
menghasilkan value “Australia”
, bukan “Indonesia”
. Itu terjadi karena menambahkan field Country
di tingkatan/ lapisan yang lebih tinggi, akan menyembunyikan field Country
yang ada di tingkatan/ lapisan struct
yang lebih dalam.
fmt.Println(person.Country) // Output: Australia
Nested Struct
Sebelumnya kita sudah melihat bahwa menambahkan field X
di tingkatan/ lapisan yang lebih tinggi, akan menyembunyikan field X
yang ada di tingkatan/ lapisan struct
yang lebih dalam. Dengan catatan, field X
di tingkatan/ lapisan struct
lebih dalam itu didapatkan dari struct
yang di-embed (embedded struct).
Hal itu tidak akan terjadi kalau ada field yang namanya sama dengan field yang dimiliki nested struct. Ini contohnya:
package main
import "fmt"
type Address struct {
Street string
City string
Country string
}
func (a Address) Print() {
fmt.Printf("%s, %s, %s\n", a.Street, a.City, a.Country)
}
type Person struct {
Name string
Age int
Country string
Address Address // Nested struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Country: "Australia",
Address: Address{
Street: "Durian Street",
City: "Bandung",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Country) // Output: Australia
fmt.Println(person.Address.Street) // Output: Durian Street
fmt.Println(person.Address.City) // Output: Bandung
fmt.Println(person.Address.Country) // Output: Indonesia
person.Address.Print() // Output: Durian Street, Bandung
}
Misalnya ada tipe Address
dan Person
. Tipe Address
punya method Print()
, field Street
, field City
, dan field Country
. Tipe Person
punya field Name
, Age
, Country
, dan Address
. Field Country
yang dimiliki langsung oleh Person
, kita berikan value “Australia”
. Sementara field Country
yang dimiliki Address
, kita berikan value “Indonesia”
.
Kalau program di atas dijalankan, maka person.Country
menghasilkan value “Australia”
, sementara person.Address.Country
menghasilkan value “Indonesia”
. Hal tersebut sesuai dengan ekspektasi, karena untuk mengakses field yang dimiliki nested struct memang harus melalui nama field yang secara eksplisit diberikan di dalam struct
di mana nested struct itu berada. Mudah bagi komputer untuk menentukan value dari field Country
mana yang perlu diambil.
fmt.Println(person.Country) // Output: Australia
fmt.Println(person.Address.Country) // Output: Indonesia
Gimana Kalau Ada Nama yang Sama dari Dua Struct dengan Tingkatan yang Sama?
Embedded Struct
Misalnya ada struct
yang punya nama field dan method yang sama. Pada contoh di bawah, ada tipe Address
dan tipe Birthplace
. Tipe Address
punya method Print()
, field Street
, field City
. Tipe Birthplace
punya method Print()
, field City
, dan field Country
. Perhatikan bahwa tipe Address
dan tipe Birthplace
sama-sama punya field City
dan method Print()
.
Tipe Address
dan tipe Birthplace
di-embed ke dalam tipe Person
. Address
dan Birthplace
bisa disebut sebagai embedded struct.
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Birthplace struct {
City string
Country string
}
func (b Birthplace) Print() {
fmt.Printf("%s, %s\n", b.City, b.Country)
}
type Person struct {
Name string
Age int
Address // Embedded struct
Birthplace // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
Birthplace: Birthplace{
City: "Jakarta",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
fmt.Println(person.City) // Output: error
person.Print() // Output: error
}
Program di atas tidak akan bisa di-compile karena ada nama field dan method yang sama di tingkat yang sama. Seperti yang kita ketahui, struct
yang di-embed akan membagikan field dan method yang dimilikinya ke struct
di mana dia di-embed. Kita jadi bisa mengakses field dan method yang dimiliki Address
dan Birthplace
langsung dari Person
.
Tapi karena Address
dan Birthplace
sama-sama punya field dengan nama City
dan method dengan nama Print()
, ada kebingungan harus mengambil dari struct
yang mana, dan program jadi tidak bisa di-compile.
Kalau field City
dan method Print()
tidak digunakan, program tetap bisa di-compile dan tidak akan menghasilkan error. Kode di bawah sama seperti contoh kode sebelumnya, tapi bagian akses field City
dan pemanggilan method Print()
dijadikan komentar.
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Birthplace struct {
City string
Country string
}
func (b Birthplace) Print() {
fmt.Printf("%s, %s\n", b.City, b.Country)
}
type Person struct {
Name string
Age int
Address // Embedded struct
Birthplace // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
Birthplace: Birthplace{
City: "Jakarta",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
// fmt.Println(person.City) // Output: error
// person.Print() // Output: error
}
Untuk tetap bisa mengakses field City
dan method Print()
, kita bisa mengaksesnya melalui masing-masing struct
yang di-embed ke dalam Person
.
fmt.Println(person.Address.City) // Output: Bandung
person.Address.Print() // Output: Durian Street, Bandung
fmt.Println(person.Birthplace.City) // Output: Jakarta
person.Birthplace.Print() // Output: Jakarta, Indonesia
Ini kodenya secara keseluruhan:
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Birthplace struct {
City string
Country string
}
func (b Birthplace) Print() {
fmt.Printf("%s, %s\n", b.City, b.Country)
}
type Person struct {
Name string
Age int
Address // Embedded struct
Birthplace // Embedded struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
Birthplace: Birthplace{
City: "Jakarta",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Street) // Output: Durian Street
// fmt.Println(person.City) // Output: error
// person.Print() // Output: error
fmt.Println(person.Address.City) // Output: Bandung
person.Address.Print() // Output: Durian Street, Bandung
fmt.Println(person.Birthplace.City) // Output: Jakarta
person.Birthplace.Print() // Output: Jakarta, Indonesia
}
Nested Struct
Dari bagian sebelumnya, kita tau kalau ada nama yang sama pada embedded struct dan nama tersebut diakses langsung dari struct
di mana embedded struct itu ada, maka program tidak bisa di-compile. Hal itu tidak akan terjadi pada nested struct.
package main
import "fmt"
type Address struct {
Street string
City string
}
func (a Address) Print() {
fmt.Printf("%s, %s\n", a.Street, a.City)
}
type Birthplace struct {
City string
Country string
}
func (b Birthplace) Print() {
fmt.Printf("%s, %s\n", b.City, b.Country)
}
type Person struct {
Name string
Age int
Address Address // Nested struct
Birthplace Birthplace // Nested struct
}
func main() {
person := Person{
Name: "John",
Age: 46,
Address: Address{
Street: "Durian Street",
City: "Bandung",
},
Birthplace: Birthplace{
City: "Jakarta",
Country: "Indonesia",
},
}
fmt.Println(person.Name) // Output: John
fmt.Println(person.Age) // Output: 46
fmt.Println(person.Address.Street) // Output: Durian Street
fmt.Println(person.Address.City) // Output: Bandung
person.Address.Print() // Output: Durian Street, Bandung
fmt.Println(person.Birthplace.City) // Output: Jakarta
person.Birthplace.Print() // Output: Jakarta, Indonesia
}
Pada contoh di atas, kita bisa lihat bahwa tipe Address
dan tipe Birthplace
sama-sama punya field City
dan method Print()
. Tipe Address
dan tipe Birthplace
dijadikan sebagai tipe field yang ada di dalam Person
. Address
dan Birthplace
bisa disebut sebagai nested struct.
Field City
dan method Print()
dari tipe Address
dan tipe Birthplace
masing-masing diakses melalui field Address
dan field Birthplace
yang dimiliki Person
. Kalau program di atas dijalankan, program itu akan berjalan dengan sesuai. Kita tidak akan melihat error.