Metode di Go tidak terikat pada struct. Setiap tipe yang didefinisikan sendiri, yaitu named type atau tipe bernama, dapat menerima metode, termasuk tipe yang berbasis bilangan, string, bahkan slice dan peta. Topik ini membahas cara mendefinisikan tipe baru dari tipe dasar, menempelkan metode padanya, dan batasan yang mengatur tipe mana yang boleh menerima metode. Setelah membacanya, perilaku dapat ditempelkan pada tipe kecil buatan sendiri tanpa memerlukan kelas.
Tipe bernama dari tipe dasar#
Deklarasi type Suhu float64 menciptakan tipe baru bernama Suhu yang tipe dasarnya float64. Tipe baru ini bukan alias, melainkan tipe yang berdiri sendiri, sehingga metode dapat dideklarasikan padanya sebagaimana pada struct. Program berikut mendefinisikan tipe Suhu beserta dua metodenya:
package main
import "fmt"
// Suhu adalah tipe baru yang berbasis float64 untuk pengukuran suhu.
type Suhu float64
// fahrenheit mengonversi suhu Celsius ke Fahrenheit.
func (s Suhu) Fahrenheit() float64 {
return float64(s)*9/5 + 32
}
// String membuat paket fmt mencetak nilai dengan format khusus.
func (s Suhu) String() string {
return fmt.Sprintf("%.1f°C", float64(s))
}
func main() {
ruang := Suhu(26.5)
fmt.Println(ruang) // String() dipanggil otomatis oleh paket fmt
fmt.Printf("%.1f\n", ruang.Fahrenheit())
}26.5°C
79.7Dua hal menonjol pada program di atas. Pertama, metode Fahrenheit mengonversi s ke float64 terlebih dahulu, karena tipe Suhu dan float64 adalah dua tipe yang berbeda meskipun tipe dasarnya sama. Kedua, metode String tidak dipanggil secara eksplisit pada baris pertama, tetapi paket fmt menemukannya dan memakai hasilnya saat mencetak nilai ruang. Perilaku terakhir ini menunjukkan bahwa metode pada tipe buatan sendiri dapat bekerja sama dengan pustaka standar tanpa pendaftaran apa pun.
Pemisahan tipe semacam ini memberi nilai praktis pada kode nyata. Nilai float64 polos tidak menyatakan apa pun tentang maknanya, sedangkan nilai bertipe Suhu menyatakan bahwa angkanya diukur dalam derajat Celsius, sehingga fungsi yang menerima Suhu tidak dapat menerima jarak atau berat begitu saja. Konversi eksplisit yang dituntut Go menjadi titik pemeriksaan alami ketika dua satuan berbeda bertemu dalam satu perhitungan.
Metode pada tipe kumpulan#
Tipe bernama juga dapat berbasis tipe kumpulan, misalnya slice. Program berikut mendefinisikan DeretSuhu sebagai slice float64 beserta metode RataRata yang menghitung rata-rata pengukuran:
package main
import "fmt"
// DeretSuhu adalah tipe baru yang berbasis slice float64.
type DeretSuhu []float64
// rataRata mengembalikan rata-rata seluruh pengukuran.
func (d DeretSuhu) RataRata() float64 {
if len(d) == 0 {
return 0
}
total := 0.0
for _, s := range d {
total += s
}
return total / float64(len(d))
}
func main() {
pengukuran := DeretSuhu{24.0, 25.5, 28.5}
fmt.Printf("rata-rata %.1f derajat\n", pengukuran.RataRata())
fmt.Printf("rata-rata deret kosong %.1f derajat\n", DeretSuhu{}.RataRata())
}rata-rata 26.0 derajat
rata-rata deret kosong 0.0 derajatMetode RataRata menerima receiver nilai bertipe DeretSuhu. Penyalinan pada receiver nilai hanya menyalin header slice, yaitu struktur yang memuat pointer, panjang, dan kapasitas. Array penyangga yang menampung data slice tidak ikut disalin, sehingga salinan tetap murah. Pemeriksaan len(d) == 0 menjaga metode dari pembagian dengan nol, dan pemanggilan pada deret kosong membuktikannya. Pola semacam ini lazim dipakai untuk perilaku yang memang milik kumpulan data, misalnya perhitungan statistik, penyaringan, dan pengurutan.
Bentuk receiver pada tipe kumpulan mengikuti aturan yang sama dengan struct. Metode yang hanya membaca isi memakai receiver nilai sebagaimana RataRata, sedangkan metode yang menambah elemen melalui append memerlukan receiver pointer, karena hasil append dapat memindahkan array penyangganya ke alamat baru. Apabila pemindahan itu perlu dipertahankan, receiver pointer memastikan variabel slice milik pemanggil ikut diperbarui.
Batasan tipe yang boleh menerima metode#
Deklarasi metode memuat satu syarat yang tegas, yaitu tipe dasar receiver harus didefinisikan di package yang sama dengan metodenya. Akibatnya, metode tidak dapat ditempelkan pada tipe bawaan seperti int atau string, tidak pula pada tipe yang diimpor dari package lain. Potongan berikut gagal dikompilasi dengan pesan cannot define new methods on non-local type int:
// tidak-dijalankan
func (i int) Ganda() int {
return i * 2 // tipe int bukan tipe lokal, sehingga kompilasi gagal
}Batasan ini menjaga jarak antara kode milik sendiri dan kode milik pihak lain, sehingga pustaka standar tidak berubah perilaku tanpa diketahui. Jalan keluarnya adalah mendefinisikan tipe pembungkus (wrapper), misalnya type Angka int, lalu menempelkan metode pada tipe pembungkus itu. Dengan tipe pembungkus, seluruh kemampuan metode tetap diperoleh tanpa menyentuh tipe milik orang lain.
Kesalahan umum#
- Menempelkan metode pada tipe milik pustaka standar atau tipe bawaan. Penyebabnya adalah lupa bahwa receiver harus bertipe lokal, dan akibatnya kompilasi gagal. Perbaikannya, definisikan tipe pembungkus di package sendiri, lalu deklarasikan metode pada tipe tersebut.
- Menugaskan nilai tipe bernama ke variabel tipe dasarnya tanpa konversi. Baris
var f float64 = ruangdenganruangbertipeSuhugagal dikompilasi, karena keduanya tipe yang berbeda. Perbaikannya, tulis konversinya secara eksplisit, yaituvar f float64 = float64(ruang). - Menganggap metode hanya milik struct. Akibatnya, perilaku sederhana seperti konversi satuan atau perhitungan rata-rata ditulis sebagai fungsi biasa padahal perhitungan itu milik satu tipe. Perbaikannya, kenali tipe yang selalu dipanggil bersama, lalu deklarasikan metodenya agar pemanggilan terbaca
ruang.Fahrenheit(), bukanfahrenheit(float64(ruang)). - Mengira metode mengubah perilaku tipe dasarnya secara global. Tipe
Suhutidak mengubah sifatfloat64di seluruh program, melainkan hanya menambah perilaku baru pada tipe barunya. Perbaikannya, perlakukan tipe bernama sebagai tipe yang berdiri sendiri, dan gunakan konversi ke tipe dasar saat berinteraksi dengan kode yang menuntutfloat64.
Ringkasan#
- Metode dapat dideklarasikan pada setiap tipe yang didefinisikan di package sendiri, bukan hanya pada struct.
- Tipe bernama berdiri sendiri dari tipe dasarnya, sehingga konversi eksplisit dipakai saat keduanya bertukar nilai.
- Tipe bawaan dan tipe dari package lain tidak dapat menerima metode, dan tipe pembungkus menjadi jalan keluarnya.
- Metode seperti
String()memungkinkan pustaka standar menampilkan nilai sesuai format yang ditetapkan tipe itu sendiri.
Referensi#
- Effective Go — Go Documentation — Bagian methods yang menyatakan metode dapat dideklarasikan pada tipe bernama apa pun.
- A Tour of Go: Methods — Go Documentation — Tur resmi yang memperkenalkan metode pada tipe berbasis bilangan.
- The Go Programming Language Specification — The Go Project — Aturan resmi deklarasi metode beserta syarat receiver-nya.