Kita sudah bisa memanggil p.Tambah() pada variabel p maupun pointer &p — kompiler menyisipkan & secara otomatis. Fleksibilitas pemanggilan ini menimbulkan pertanyaan berikutnya: method apa saja sebenarnya yang dimiliki tipe T dibanding pointer *T? Jawabannya dirumuskan sebagai method set — himpunan method yang melekat pada setiap bentuk tipe. Aturan ini terasa teknis, tetapi menentukan nilai mana yang boleh dimasukkan ke interface, jadi penguasaannya menjadi prasyarat sebelum melangkah ke section Interface
.
Method Set dari T dan *T #
Spesifikasi Go merumuskan dua aturan pendek. Method set dari tipe T hanya berisi method yang receiver-nya T; method set dari pointer *T berisi method yang receiver-nya T maupun *T:
| Bentuk tipe | Method set berisi |
|---|---|
Kalkulator (nilai) | method dengan receiver nilai |
*Kalkulator (pointer) | method dengan receiver nilai dan pointer |
Asal-usul aturan ini masuk akal: pointer selalu bisa mengakses nilai yang ditunjuk, sedangkan nilai biasa tidak menjanjikan alamat tetap — nilai yang disalin ke interface tidak punya alamat yang bisa dipertahankan. Method pointer receiver kemudian tidak diberikan pada method set T.
Interface sebagai Kontrak #
Untuk melihat dampaknya kita perlu interface — tipe yang hanya memuat daftar method. Interface menyimpan nilai konkret beserta method yang dimilikinya; detailnya dibedah pada chapter Konsep Interface . Dua interface kecil berikut dipakai sebagai alat uji:
type Pembaca interface {
Nilai() int
}
type Pengubah interface {
Tambah(n int)
}Method Nilai milik Kalkulator memakai receiver nilai, sedangkan Tambah memakai receiver pointer. Dengan kedua interface di atas, method set kedua tipe teruji langsung: nilai Kalkulator hanya memenuhi Pembaca, sementara *Kalkulator memenuhi Pembaca sekaligus Pengubah.
Program Utuh #
Berkas method-set-demo.go membandingkan penyimpanan nilai dan pointer ke dalam interface:
package main
import "fmt"
type Kalkulator struct {
Total int
}
func (k Kalkulator) Nilai() int {
return k.Total
}
func (k *Kalkulator) Tambah(n int) {
k.Total += n
}
type Pembaca interface {
Nilai() int
}
type Pengubah interface {
Tambah(n int)
}
func main() {
k := Kalkulator{}
k.Tambah(7)
fmt.Println("setelah Tambah:", k.Nilai())
p := &k
var pembacaDariNilai Pembaca = k
var pembacaDariPointer Pembaca = p
var pengubah Pengubah = p
pengubah.Tambah(3)
fmt.Println("pembacaDariNilai:", pembacaDariNilai.Nilai())
fmt.Println("pembacaDariPointer:", pembacaDariPointer.Nilai())
}Jalankan program dari direktori chapter:
go run method-set-demo.goOutput yang Dihasilkan:
setelah Tambah: 7
pembacaDariNilai: 7
pembacaDariPointer: 10Mari kita telaah baris demi baris program di atas:
- Baris 9–11 (
func (k Kalkulator) Nilai): method receiver nilai — anggota method setKalkulatordan*Kalkulator. - Baris 13–15 (
func (k *Kalkulator) Tambah): method receiver pointer — hanya anggota method set*Kalkulator. - Baris 17–23 (dua
type ... interface): mendeklarasikan kontrakPembacadanPengubahsebagai alat uji method set. - Baris 27 (
k.Tambah(7)): memanggil method pointer receiver pada variabel — sah karenakaddressable dan kompiler menyisipkan&k. - Baris 31–33 (tiga assignment): memasukkan nilai
kkePembaca(sah), pointerpkePembaca(sah), dan pointerpkePengubah(sah); nilaikkePengubahakan gagal. - Baris 35–37 (
pengubah.Tambah(3)): mengubahklewat interface, lalu membaca lewat dua interface —pembacaDariNilaimenyimpan salinanksaat assignment (Total masih 7), sedangkanpembacaDariPointerberbagi nilai denganksehingga membaca 10.
Saat Nilai T Gagal Memenuhi Interface #
Assignment nilai k langsung ke interface Pengubah ditolak kompiler dengan pesan:
cannot use k (variable of struct type Kalkulator) as Pengubah value in variable declaration: Kalkulator does not implement Pengubah (method Tambah has pointer receiver)Pemanggilan langsung k.Tambah(3) tetap sah, sehingga kegagalan ini sering terasa aneh. Bedanya: pemanggilan langsung menyisipkan &k sekali saat itu juga, sedangkan interface harus menyimpan nilai dalam waktu lama — nilai yang disalin ke dalam interface tidak dijamin tetap addressable, jadi Go memilih aman dan hanya menerima pointer.
Pointer Bisa Di-assign, Nilai Non-Addressable Tidak #
Ketika nilai dimiliki variabel biasa, kita selalu bisa mengambil &k untuk mendapatkan method set lengkap. Batasan muncul kembali pada nilai non-addressable: elemen map m["buku"] tidak bisa diambil alamatnya, jadi elemen tersebut tidak bisa dipanggil method pointer-nya maupun dimasukkan ke interface yang menuntut method pointer. Kombinasi method set dan addressable inilah alasan idiom map[string]*Kalkulator begitu umum: elemen pointer selalu punya method set penuh. Aturan ini juga menjadi alasan komunitas Go menyukai interface bermethod value receiver — kontrak seperti itu dipenuhi nilai maupun pointer; perannya dibedah pada chapter Kecilnya Interface
.
Referensi #
- Spesifikasi Go: Method sets
— rumusan resmi method set
Tdan*T. - Effective Go: Interfaces — hubungan method set dengan interface.
- A Tour of Go: Methods — latihan interaktif receiver dan interface.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
method-set-demo.go
Unduh berkaspackage main
import "fmt"
type Kalkulator struct {
Total int
}
func (k Kalkulator) Nilai() int {
return k.Total
}
func (k *Kalkulator) Tambah(n int) {
k.Total += n
}
type Pembaca interface {
Nilai() int
}
type Pengubah interface {
Tambah(n int)
}
func main() {
k := Kalkulator{}
k.Tambah(7)
fmt.Println("setelah Tambah:", k.Nilai())
p := &k
var pembacaDariNilai Pembaca = k
var pembacaDariPointer Pembaca = p
var pengubah Pengubah = p
pengubah.Tambah(3)
fmt.Println("pembacaDariNilai:", pembacaDariNilai.Nilai())
fmt.Println("pembacaDariPointer:", pembacaDariPointer.Nilai())
}