Setiap program besar pada akhirnya menghadapi pertanyaan yang sama: bagaimana modul satu menyebut kemampuan modul lain tanpa mengikat diri pada satu implementasi tertentu. Go menjawabnya dengan interface, tipe yang hanya mendeklarasikan apa yang bisa dilakukan dan sama sekali tidak mendefinisikan bagaimana hal itu dilakukan. Berbeda dengan Java atau C#, Go tidak memiliki kata kunci implements, sehingga hubungan antara tipe dan interface dibangun secara implisit. Chapter ini membahas kontrak perilaku, model penyimpanan nilai interface, serta dua contoh nyata dari standard library: io.Writer dan fmt.Stringer.
Interface Adalah Method Set #
Interface di Go adalah method set: kumpulan signature method yang menjadi kontrak minimal bagi tipe lain. Deklarasi interface memuat daftar nama method beserta parameter dan tipe hasilnya, tanpa badan fungsi sama sekali. Sebuah tipe dikatakan mengimplementasikan interface ketika tipe tersebut memiliki semua method yang tercantum di dalam kontrak tersebut. Interface tidak bisa menyimpan field atau data; interface murni bicara tentang perilaku.
Bentuk deklarasinya ringkas:
type Speaker interface {
Speak() string
}Deklarasi di atas menyatakan: “siapa pun yang punya method Speak dengan hasil string adalah Speaker”. Method signature Speak() string terdiri dari nama method, daftar parameter (kosong di sini), dan tipe hasil. Detail aturan method mana yang dihitung masuk method set dibahas tuntas pada chapter Method Set
.
Nilai Interface: Pasangan Dua Kata #
Hal yang paling sering disalahpahami pemula adalah cara interface menyimpan nilai. Variabel interface tidak menyimpan nilai secara langsung; variabel interface menyimpan pasangan dua kata: dynamic type (tipe konkret yang sedang menempel) dan dynamic value (salinan nilai dari tipe tersebut). Model dua kata ini adalah fondasi untuk memahami seluruh perilaku interface, termasuk jebakan nil interface yang akan kita bedah di chapter Interface nil .
Tabel berikut merangkum keadaan pasangan tersebut:
Keadaan variabel s bertipe interface | Dynamic type | Dynamic value | s == nil |
|---|---|---|---|
var s Speaker (belum diisi) | kosong | kosong | true |
s = Kucing{Nama: "Oyen"} | main.Kucing | salinan struct | false |
s = Robot{ID: 7} (penugasan ulang) | main.Robot | salinan struct | false |
Ketika program memanggil s.Speak(), runtime Go membaca dynamic type dari pasangan tersebut, mencari method Speak milik tipe itu, lalu menjalankannya dengan dynamic value sebagai receiver. Proses pencarian method saat pemanggilan ini disebut dynamic dispatch. Verb %T dari package fmt melaporkan dynamic type yang sedang menempel.
Contoh Nyata: io.Writer dan fmt.Stringer #
Dua interface kecil di standard library menjadi bukti bahwa kontrak satu-dua method sudah cukup untuk menopang seluruh ekosistem Go. Interface pertama adalah io.Writer, tulang punggung semua operasi output di Go:
type Writer interface {
Write(p []byte) (n int, err error)
}Tipe *os.File, *bytes.Buffer, dan *strings.Builder semuanya mengimplementasikannya, sehingga satu fungsi pencetak bisa menerima ketiganya tanpa peduli tujuan akhir datanya. Interface kedua adalah fmt.Stringer:
type Stringer interface {
String() string
}Keluarga fungsi fmt.Print memanggil method String() secara otomatis ketika dynamic type sebuah argumen mengimplementasinya, sehingga setiap tipe bisa mengatur bentuk tampilannya sendiri.
Program Demo #
Berkas interface-demo.go menggabungkan seluruh konsep di atas dalam satu program yang dapat dijalankan langsung dari direktori chapter:
package main
import "fmt"
// Speaker adalah kontrak perilaku: satu method set tanpa implementasi.
type Speaker interface {
Speak() string
}
// Kucing mengimplementasikan Speaker lewat method Speak-nya.
type Kucing struct {
Nama string
}
func (k Kucing) Speak() string {
return k.Nama + ": meong"
}
// Robot juga mengimplementasikan Speaker dengan perilaku berbeda.
type Robot struct {
ID int
}
func (r Robot) Speak() string {
return fmt.Sprintf("robot-%d: bleep bloop", r.ID)
}
// perkenalkan menerima interface, bukan tipe konkret.
func perkenalkan(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
var s Speaker // nilai interface: pasangan (dynamic type, dynamic value)
s = Kucing{Nama: "Oyen"}
fmt.Printf("dynamic type: %T\n", s)
perkenalkan(s)
s = Robot{ID: 7}
fmt.Printf("dynamic type: %T\n", s)
perkenalkan(s)
}Output yang Dihasilkan:
dynamic type: main.Kucing
Oyen: meong
dynamic type: main.Robot
robot-7: bleep bloopMari kita telaah baris demi baris program di atas:
- Baris 6–8 (
type Speaker interface): mendeklarasikan interface dengan satu method set berisi signatureSpeak() string, tanpa badan method dan tanpa field. - Baris 15–17 (
func (k Kucing) Speak): mendefinisikan methodSpeakdengan receiverKucing; sejak baris iniKucingresmi memenuhi kontrakSpeaker. - Baris 24–26 (
func (r Robot) Speak):Robotmemenuhi kontrak yang sama dengan implementasi berbeda, membuktikan interface tidak peduli bagaimana perilaku itu diwujudkan. - Baris 29–31 (
func perkenalkan): fungsi ini menerima parameter bertipeSpeaker, sehingga bisa dipanggil dengan tipe konkret apa pun yang memenuhi kontrak. - Baris 34 (
var s Speaker): mendeklarasikan variabel interface dengan zero value; pasangan (dynamic type, dynamic value) masih kosong dans == nilbernilaitrue. - Baris 36–38 (
s = Kucing{...}): penugasan mengisi pasangan dengan dynamic typemain.Kucingdan salinan struct sebagai dynamic value. - Baris 40–42 (
s = Robot{...}): penugasan ulang mengganti seluruh pasangan, bukan hanya nilainya, sehingga pemanggilans.Speak()kini berpindah ke method milikRobot.
Model pasangan (dynamic type, dynamic value) menjelaskan mengapa satu variabel interface bisa berganti-ganti tipe konkret. Pertanyaan berikutnya: bagaimana Go menentukan bahwa Kucing dan Robot memenuhi kontrak Speaker padahal kedua deklarasi itu tidak pernah menyebut nama interface? Jawabannya kita bahas pada chapter Implementasi Interface Implisit
.
Referensi #
- Spesifikasi Go: Interface types — aturan resmi deklarasi interface dan method set.
- Effective Go: Interfaces — filosofi pemakaian interface di Go.
- Dokumentasi fmt.Stringer — salah satu interface paling sering diimplementasikan di Go.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
interface-demo.go
Unduh berkaspackage main
import "fmt"
// Speaker adalah kontrak perilaku: satu method set tanpa implementasi.
type Speaker interface {
Speak() string
}
// Kucing mengimplementasikan Speaker lewat method Speak-nya.
type Kucing struct {
Nama string
}
func (k Kucing) Speak() string {
return k.Nama + ": meong"
}
// Robot juga mengimplementasikan Speaker dengan perilaku berbeda.
type Robot struct {
ID int
}
func (r Robot) Speak() string {
return fmt.Sprintf("robot-%d: bleep bloop", r.ID)
}
// perkenalkan menerima interface, bukan tipe konkret.
func perkenalkan(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
var s Speaker // nilai interface: pasangan (dynamic type, dynamic value)
s = Kucing{Nama: "Oyen"}
fmt.Printf("dynamic type: %T\n", s)
perkenalkan(s)
s = Robot{ID: 7}
fmt.Printf("dynamic type: %T\n", s)
perkenalkan(s)
}