1 Dasar-dasar Struct
Dalam bahasa Go, struct adalah tipe data komposit yang digunakan untuk menggabungkan tipe data yang berbeda atau identik menjadi satu entitas tunggal. Struct memiliki posisi penting dalam Go karena mereka berfungsi sebagai aspek fundamental dari pemrograman berorientasi objek, meskipun dengan perbedaan sedikit dari bahasa pemrograman berorientasi objek tradisional.
Kebutuhan akan struct muncul dari aspek-aspek berikut:
- Mengorganisir variabel-variabel dengan keterkaitan yang kuat untuk meningkatkan keterjagaan kode.
- Memberikan cara untuk mensimulasikan "kelas", memfasilitasi fitur enkapsulasi dan agregasi.
- Saat berinteraksi dengan struktur data seperti JSON, catatan database, dll., struct menawarkan alat pemetaan yang nyaman.
Mengorganisir data dengan struct memungkinkan representasi yang lebih jelas dari model objek dunia nyata seperti pengguna, pesanan, dll.
2 Mendefinisikan Struct
Syntax untuk mendefinisikan struct adalah sebagai berikut:
type NamaStruct struct {
Field1 TipeField1
Field2 TipeField2
// ... variabel anggota lainnya
}
- Kata kunci
type
memperkenalkan definisi struct. -
NamaStruct
adalah nama tipe struct, mengikuti konvensi penamaan Go, biasanya kapital untuk menunjukkan keboleh dipakaiannya. - Kata kunci
struct
menandakan bahwa ini adalah tipe struct. - Dalam kurung kurawal
{}
, variabel anggota (field) dari struct didefinisikan, dengan masing-masing diikuti oleh tipe nya.
Tipe dari anggota struct bisa berupa tipe apa saja, termasuk tipe dasar (seperti int
, string
, dll.) dan tipe kompleks (seperti array, slice, struct lain, dll.).
Sebagai contoh, mendefinisikan struct yang mewakili seorang individu:
type Person struct {
Name string
Age int
Emails []string // bisa mencakup tipe kompleks, seperti slice
}
Dalam kode di atas, struct Person
memiliki tiga variabel anggota: Name
bertipe string, Age
bertipe integer, dan Emails
bertipe slice string, menunjukkan bahwa seseorang mungkin memiliki beberapa alamat email.
3 Membuat dan Menginisialisasi Struct
3.1 Membuat Instansi Struct
Terdapat dua cara untuk membuat instansi struct: deklarasi langsung atau menggunakan kata kunci new
.
Deklarasi langsung:
var p Person
Kode di atas membuat sebuah instansi p
dengan tipe Person
, di mana setiap variabel anggota dari struct tersebut adalah nilai nol dari tipe nya masing-masing.
Menggunakan kata kunci new
:
p := new(Person)
Membuat struct menggunakan kata kunci new
menghasilkan pointer ke struct tersebut. Variabel p
pada titik ini memiliki tipe *Person
, menunjuk ke variabel yang baru dialokasikan dengan tipe Person
di mana variabel anggotanya telah diinisialisasi dengan nilai nol.
3.2 Menginisialisasi Instansi Struct
Instansi struct dapat diinisialisasi sekaligus saat mereka dibuat, menggunakan dua metode: dengan nama-nama field atau tanpa nama-nama field.
Inisialisasi dengan Nama-Nama Field:
p := Person{
Name: "Alice",
Age: 30,
Emails: []string{"[email protected]", "[email protected]"},
}
Saat menginisialisasi dengan bentuk assignment field, urutan inisialisasi tidak perlu sama dengan urutan pendeklarasian struct, dan setiap field yang belum diinisialisasi akan tetap mempertahankan nilai-nolnya.
Inisialisasi tanpa Nama-Nama Field:
p := Person{"Bob", 25, []string{"[email protected]"}}
Saat menginisialisasi tanpa nama-nama field, pastikan nilai awal dari setiap variabel anggota berada dalam urutan yang sama seperti saat struct didefinisikan, dan tidak ada field yang dapat dihilangkan.
Selain itu, struct dapat diinisialisasi dengan field-field tertentu, dan setiap field yang tidak spesifik akan mengambil nilai-nol:
p := Person{Name: "Charlie"}
Dalam contoh ini, hanya field Name
yang diinisialisasi, sementara Age
dan Emails
akan keduanya mengambil nilai-nol masing-masing.
4 Mengakses Anggota Struct
Mengakses variabel anggota dari sebuah struct dalam Go sangat mudah, dicapai dengan menggunakan operator titik (.
). Jika Anda memiliki variabel struct, Anda dapat membaca atau memodifikasi nilainya dengan cara ini.
5 Komposisi dan Penanaman Struct
Struct tidak hanya dapat ada secara independen tetapi juga dapat disusun dan ditanam bersama untuk membuat struktur data yang lebih kompleks.
5.1 Struct Tanpa Nama
Struct tanpa nama tidak secara eksplisit mendeklarasikan tipe baru, tetapi langsung menggunakan definisi struct. Ini berguna ketika Anda perlu membuat struct sekali dan menggunakannya secara sederhana, menghindari penciptaan tipe yang tidak perlu.
Contoh:
package main
import "fmt"
func main() {
// Mendefinisikan dan menginisialisasi struct tanpa nama
person := struct {
Name string
Age int
}{
Name: "Eve",
Age: 40,
}
// Mengakses member dari struct tanpa nama
fmt.Println("Nama:", person.Name)
fmt.Println("Usia:", person.Age)
}
Dalam contoh ini, daripada membuat tipe baru, kita langsung mendefinisikan struct dan membuat contohnya. Contoh ini menunjukkan cara menginisialisasi struct tanpa nama dan mengakses membernya.
5.2 Penanaman Struct
Penanaman struct melibatkan menanamkan satu struct sebagai anggota dari struct lain. Hal ini memungkinkan kita membangun model data yang lebih kompleks.
Contoh:
package main
import "fmt"
// Mendefinisikan struct Address
type Address struct {
City string
Country string
}
// Menanamkan struct Address dalam struct Person
type Person struct {
Name string
Age int
Address Address
}
func main() {
// Menginisialisasi instance Person
p := Person{
Name: "Charlie",
Age: 28,
Address: Address{
City: "New York",
Country: "Amerika Serikat",
},
}
// Mengakses member dari struct yang ditanamkan
fmt.Println("Nama:", p.Name)
fmt.Println("Usia:", p.Age)
// Mengakses member dari struct Address
fmt.Println("Kota:", p.Address.City)
fmt.Println("Negara:", p.Address.Country)
}
Dalam contoh ini, kita mendefinisikan struct Address
dan menanamkannya sebagai anggota dalam struct Person
. Ketika membuat contoh Person
, kita juga membuat contoh Address
secara bersamaan. Kita dapat mengakses member dari struct yang ditanamkan menggunakan notasi titik.
6 Metode Struct
Fitur pemrograman berbasis objek (OOP) dapat diimplementasikan melalui metode struct.
6.1 Konsep Dasar Metode
Dalam bahasa Go, meskipun tidak ada konsep kelas dan objek tradisional, fitur OOP yang serupa dapat dicapai dengan mengaitkan metode ke struct. Metode struct adalah jenis fungsi khusus yang terkait dengan tipe struct tertentu (atau pointer ke struct), memungkinkan tipe tersebut memiliki seperangkat metode sendiri.
// Mendefinisikan struct sederhana
type Rectangle struct {
panjang, lebar float64
}
// Mendefinisikan metode untuk struct Rectangle untuk menghitung luas persegi
func (r Rectangle) Area() float64 {
return r.panjang * r.lebar
}
Pada kode di atas, metode Area
terkait dengan struct Rectangle
. Dalam definisi metode, (r Rectangle)
adalah penerima, yang menentukan bahwa metode ini terkait dengan tipe Rectangle
. Penerima muncul sebelum nama metode.
6.2 Penerima Nilai dan Penerima Pointer
Metode dapat dikategorikan sebagai penerima nilai dan penerima pointer berdasarkan jenis receiver-nya. Penerima nilai menggunakan salinan dari struk untuk memanggil metode, sedangkan penerima pointer menggunakan pointer ke struk dan dapat memodifikasi struk asli.
// Mendefinisikan sebuah metode dengan penerima nilai
func (r Rectangle) Keliling() float64 {
return 2 * (r.panjang + r.lebar)
}
// Mendefinisikan sebuah metode dengan penerima pointer, yang dapat memodifikasi struk
func (r *Rectangle) AturPanjang(panjangBaru float64) {
r.panjang = panjangBaru // dapat memodifikasi nilai asli dari struk
}
Pada contoh di atas, Keliling
adalah metode penerima nilai, memanggilnya tidak akan mengubah nilai dari Rectangle
. Namun, AturPanjang
adalah metode penerima pointer, dan memanggil metode ini akan memengaruhi instansi Rectangle
asli.
6.3 Penggilan Metode
Anda dapat memanggil metode dari sebuah struk menggunakan variabel struk dan pointer-nya.
func main() {
rect := Rectangle{panjang: 10, lebar: 5}
// Memanggil metode dengan penerima nilai
fmt.Println("Luas:", rect.Luas())
// Memanggil metode dengan penerima nilai
fmt.Println("Keliling:", rect.Keliling())
// Memanggil metode dengan penerima pointer
rect.AturPanjang(20)
// Memanggil metode dengan penerima nilai lagi, perhatikan bahwa panjang telah dimodifikasi
fmt.Println("Setelah dimodifikasi, Luas:", rect.Luas())
}
Saat Anda memanggil sebuah metode menggunakan sebuah pointer, Go secara otomatis menangani konversi antara nilai dan pointer, tanpa memperhatikan apakah metode Anda didefinisikan dengan penerima nilai atau penerima pointer.
6.4 Pemilihan Jenis Penerima
Ketika mendefinisikan metode, Anda harus memutuskan apakah akan menggunakan penerima nilai atau penerima pointer berdasarkan situasi. Berikut adalah beberapa pedoman umum:
- Jika metode perlu memodifikasi isi struktur, gunakan penerima pointer.
- Jika strukturnya besar dan biaya penyalinannya tinggi, gunakan penerima pointer.
- Jika Anda ingin metode tersebut memodifikasi nilai yang ditunjuk oleh receiver, gunakan penerima pointer.
- Secara efisiensi, bahkan jika Anda tidak memodifikasi isi struktur, wajar untuk menggunakan penerima pointer untuk struktur yang besar.
- Untuk struktur yang kecil, atau saat hanya membaca data tanpa perlu modifikasi, penerima nilai seringkali lebih sederhana dan efisien.
Melalui metode struk, kita dapat mensimulasikan beberapa fitur pemrograman berorientasi objek dalam Go, seperti enkapsulasi dan metode. Pendekatan ini dalam Go menyederhanakan konsep objek sementara memberikan kemampuan yang cukup untuk mengatur dan mengelola fungsi terkait.
7 Struk dan Serialisasi JSON
Dalam Go, seringkali diperlukan untuk melakukan serialisasi struk menjadi format JSON untuk transmisi jaringan atau sebagai file konfigurasi. Demikian pula, kita juga perlu mampu melakukan deserialisasi JSON menjadi instansi struk. Paket encoding/json
dalam Go menyediakan fungsionalitas ini.
Berikut adalah contoh bagaimana mengonversi antara sebuah struk dan JSON:
package main
import (
"encoding/json"
"fmt"
"log"
)
// Mendefinisikan struktur Person, dan menggunakan tag json untuk mendefinisikan pemetaan antara bidang struk dan nama bidang JSON
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Emails []string `json:"emails,omitempty"`
}
func main() {
// Membuat instansi baru dari Person
p := Person{
Name: "John Doe",
Age: 30,
Emails: []string{"[email protected]", "[email protected]"},
}
// Serialisasi ke JSON
dataJSON, err := json.Marshal(p)
if err != nil {
log.Fatalf("Pengelompokan JSON gagal: %s", err)
}
fmt.Printf("Format JSON: %s\n", dataJSON)
// Deserialisasi menjadi sebuah struk
var p2 Person
if err := json.Unmarshal(dataJSON, &p2); err != nil {
log.Fatalf("Pengelompokan JSON gagal: %s", err)
}
fmt.Printf("Struk Dipulihkan: %#v\n", p2)
}
Pada kode di atas, kita mendefinisikan struktur Person
, termasuk sebuah bidang tipe slice dengan opsi "omitempty". Opsi ini menentukan bahwa jika bidangnya kosong atau tidak ada, itu tidak akan disertakan dalam JSON.
Kita menggunakan fungsi json.Marshal
untuk melakukan serialisasi sebuah instansi struk menjadi JSON, dan fungsi json.Unmarshal
untuk melakukan deserialisasi data JSON menjadi instansi struk.
8 Topik Lanjutan dalam Struk
8.1 Perbandingan Structs
Di Go, membandingkan dua instansi dari struct secara langsung diperbolehkan, namun perbandingan ini didasarkan pada nilai-nilai dari field-field di dalam struct. Jika semua nilai field sama, maka dua instansi dari struct dianggap sama. Perlu diketahui bahwa tidak semua tipe field dapat dibandingkan. Sebagai contoh, sebuah struct yang berisi slice tidak dapat dibandingkan secara langsung.
Berikut ini adalah contoh membandingkan structs:
package main
import "fmt"
type Point struct {
X, Y int
}
func main() {
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{1, 3}
fmt.Println("p1 == p2:", p1 == p2) // Output: p1 == p2: true
fmt.Println("p1 == p3:", p1 == p3) // Output: p1 == p3: false
}
Dalam contoh ini, p1
dan p2
dianggap sama karena semua nilai field pada keduanya sama. Dan p3
tidak sama dengan p1
karena nilai dari Y
berbeda.
8.2 Menyalin Structs
Di Go, instansi dari struct dapat disalin dengan menggunakan assignment. Apakah salinan ini merupakan deep copy atau shallow copy tergantung pada tipe-tipe field di dalam struct.
Jika struct hanya berisi tipe-tipe dasar (seperti int
, string
, dll.), maka salinannya akan menjadi deep copy. Jika struct berisi tipe-tipe referensi (seperti slice, map, dll.), maka salinannya akan menjadi shallow copy, dan instansi asli dan instansi yang disalin akan berbagi memori dari tipe-tipe referensi.
Berikut adalah contoh menyalin sebuah struct:
package main
import "fmt"
type Data struct {
Numbers []int
}
func main() {
// Inisialisasi sebuah instansi dari struct Data
original := Data{Numbers: []int{1, 2, 3}}
// Salin struct
copied := original
// Modifikasi elemen-elemen dari slice yang disalin
copied.Numbers[0] = 100
// Lihat elemen-elemen dari instansi original dan yang disalin
fmt.Println("Original:", original.Numbers) // Output: Original: [100 2 3]
fmt.Println("Copied:", copied.Numbers) // Output: Copied: [100 2 3]
}
Seperti yang ditunjukkan dalam contoh, instansi original
dan copied
berbagi slice yang sama, sehingga memodifikasi data slice di copied
juga akan memengaruhi data slice di original
.
Untuk menghindari masalah ini, Anda dapat mencapai deep copying yang sebenarnya dengan menyalin konten slice secara eksplisit ke slice baru:
newNumbers := make([]int, len(original.Numbers))
copy(newNumbers, original.Numbers)
copied := Data{Numbers: newNumbers}
Dengan cara ini, setiap modifikasi pada copied
tidak akan memengaruhi original
.