Nested Struct vs Embedded Struct di Go: Perbedaan Utama - Part 1

Annisa NadiaSep 9, 2024 · 11 min read · ___ views
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.

Last updated: Nov 19, 2024Edit on GitHub

Get in Touch

© 2021 - 2024 Annisa Nadia Neyla