Pustaka standar Go menaruh sebagian besar abstraksinya pada interface kecil yang dipenuhi tipe buatan pengembang. Topik ini membahas empat di antaranya yang paling sering ditemui, yaitu fmt.Stringer untuk pencetakan, error untuk pelaporan kegagalan, sort.Interface untuk pengurutan, serta komposisi Reader dan Writer menjadi kontrak yang lebih besar. Setelah membacanya, tipe buatan sendiri dapat menyatu dengan paket fmt, sort, dan io tanpa adaptor apa pun.
fmt.Stringer#
Paket fmt dapat mencetak nilai apa pun, tetapi apabila nilainya memiliki metode String() string, hasil cetaknya diambil dari metode itu. Kontrak tersebut bernama fmt.Stringer dan didefinisikan sebagai berikut.
// tidak-dijalankan
type Stringer interface {
String() string
}Metode String memainkan peran yang sama seperti metode toString pada bahasa berorientasi objek, tetapi hubungannya terbentuk lewat pemenuhan interface dan bukan pewarisan. Program berikut memberi tipe Suhu cara mencetak dirinya sendiri.
package main
import "fmt"
// Suhu adalah pengukuran suhu dalam derajat Celsius.
type Suhu float64
// String memenuhi fmt.Stringer sehingga Suhu tahu cara mencetak dirinya.
func (s Suhu) String() string {
return fmt.Sprintf("%.1f °C", float64(s))
}
func main() {
pengukuran := []Suhu{22.5, 24, 19.75}
for _, p := range pengukuran {
fmt.Println(p)
}
fmt.Printf("rekap: pertama %v, terakhir %v\n", pengukuran[0], pengukuran[2])
}22.5 °C
24.0 °C
19.8 °C
rekap: pertama 22.5 °C, terakhir 19.8 °Cfmt.Println dan verb %v memeriksa fmt.Stringer lebih dahulu, sehingga ketiga pengukuran dicetak melalui metode String. Pemformatan yang tadinya tersebar di tempat pemanggil kini berkumpul di satu tempat, yaitu metode milik tipenya sendiri. Ketika format berubah, cukup metode itu yang disunting. Manfaat yang sama berlaku pada tipe yang menyimpan nilai Suhu, karena pencetakan struct yang memiliki field bertipe Suhu otomatis menampilkan format yang sama. Pustaka standar memanfaatkan mekanisme ini pada beberapa tipenya, misalnya time.Duration yang mencetak durasinya dalam bentuk ringkas.
Program tersebut dapat dijalankan lewat terminal sebagaimana berikut.
# siapkan folder kerja untuk topik ini
mkdir -p /tmp/gointerface/interface-stdlib
cp stringer-suhu.go /tmp/gointerface/interface-stdlib/
cd /tmp/gointerface/interface-stdlib
# periksa format sebelum menjalankan programnya
gofmt -l .
# jalankan programnya
go run stringer-suhu.go22.5 °C
24.0 °C
19.8 °C
rekap: pertama 22.5 °C, terakhir 19.8 °CKeluarannya sama dengan blok sebelumnya karena keduanya menjalankan program yang sama, sedangkan gofmt -l tidak mencetak apa pun ketika format kode sudah sesuai.
error sebagai interface#
Tipe error adalah interface dengan satu metode Error() string, dan setiap tipe yang memiliki metode itu memenuhinya tanpa deklarasi apa pun. Kelebihan desain ini terasa ketika error perlu membawa data, karena struct pembawa data dapat sekaligus menjadi error. Program berikut mendefinisikan tipe error yang menyimpan nilai dan batas pelanggarannya.
package main
import "fmt"
// ErrAmbang adalah tipe error buatan sendiri untuk pelanggaran batas.
type ErrAmbang struct {
Nilai int
Batas int
}
// Error memenuhi interface error bawaan bahasa Go.
func (e ErrAmbang) Error() string {
return fmt.Sprintf("nilai %d melebihi batas %d", e.Nilai, e.Batas)
}
// validasiBatas menolak nilai yang melewati ambang tertinggi.
func validasiBatas(n int) error {
if n > 100 {
return ErrAmbang{Nilai: n, Batas: 100}
}
return nil
}
func main() {
for _, n := range []int{80, 120} {
if err := validasiBatas(n); err != nil {
fmt.Println("ditolak:", err)
} else {
fmt.Println("diterima:", n)
}
}
}diterima: 80
ditolak: nilai 120 melebihi batas 100Pernyataan return ErrAmbang{Nilai: n, Batas: 100} mengembalikan nilai struct, sedangkan fungsi menyatakan kembaliannya bertipe error. Keduanya cocok karena ErrAmbang memenuhi interface error, sehingga pemanggil dapat mencetaknya melalui metode Error. Apabila errornya kelak perlu diperiksa lebih dalam, field seperti Nilai dan Batas tetap tersedia dan dapat dibaca melalui type assertion, yaitu pemeriksaan tipe yang dilakukan saat program berjalan.
sort.Interface#
Paket sort tidak mengenal tipe data yang diurutkannya. Yang diperlukannya hanya interface sort.Interface dengan tiga metode berikut.
// tidak-dijalankan
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}Metode Len melaporkan banyaknya elemen, Less memutuskan urutan dua elemen, dan Swap menukarkan dua elemen. Pemisahan tiga tanggung jawab ini membuat satu algoritme pengurutan melayani tipe data apa pun. Program berikut mengurutkan catatan menurut tebal halamannya.
package main
import (
"fmt"
"sort"
)
// Catatan adalah satu entri buku catatan.
type Catatan struct {
Judul string
Halaman int
}
// DaftarCatatan adalah irisan Catatan yang dapat diurutkan.
type DaftarCatatan []Catatan
// Len, Less, dan Swap memenuhi sort.Interface.
func (d DaftarCatatan) Len() int {
return len(d)
}
func (d DaftarCatatan) Less(i, j int) bool {
return d[i].Halaman < d[j].Halaman
}
func (d DaftarCatatan) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func main() {
daftar := DaftarCatatan{
{Judul: "Rapat mingguan", Halaman: 4},
{Judul: "Ide proyek", Halaman: 2},
{Judul: "Log sensor", Halaman: 7},
}
sort.Sort(daftar)
for _, c := range daftar {
fmt.Printf("%s (%d halaman)\n", c.Judul, c.Halaman)
}
}Ide proyek (2 halaman)
Rapat mingguan (4 halaman)
Log sensor (7 halaman)Tipe DaftarCatatan adalah irisan Catatan yang diberi tiga metode, sehingga sort.Sort dapat mengurutkannya tanpa mengetahui isi elemennya. Untuk pengurutan menurut kunci lain, misalnya judul, cukup mengganti isi Less, sedangkan dua metode lainnya tetap dipakai. Untuk pengurutan sekali pakai, paket slices menyediakan slices.SortFunc yang menerima fungsi perbandingan, sehingga ketiga metode tadi tidak perlu ditulis.
Komposisi interface kecil#
Interface dapat disusun dari interface lain melalui penyematan, yaitu mencantumkan nama interface di dalam deklarasi interface baru. Gabungan io.Reader dan io.Writer muncul begitu sering sehingga pustaka standar menyediakannya sebagai io.ReadWriter.
// tidak-dijalankan
type ReadWriter interface {
Reader
Writer
}Tipe yang memenuhi io.ReadWriter harus memiliki Read sekaligus Write, sedangkan pemenuhan tiap bagiannya mengikuti kontrak kecil masing-masing. bytes.Buffer adalah contoh tipe yang memenuhi keduanya sekaligus. Program berikut memanfaatkan sifat itu untuk membaca dan menulis pada satu buffer yang sama.
package main
import (
"bytes"
"fmt"
"io"
)
// gandakan membaca seluruh isi kanal, lalu menulisnya dua kali ke kanal yang sama.
func gandakan(kanal io.ReadWriter) error {
isi, err := io.ReadAll(kanal)
if err != nil {
return err
}
teks := string(isi)
_, err = io.WriteString(kanal, teks+" "+teks)
return err
}
func main() {
var kanal bytes.Buffer
kanal.WriteString("catatan singkat")
if err := gandakan(&kanal); err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(kanal.String())
}catatan singkat catatan singkatFungsi gandakan menerima io.ReadWriter, sehingga ia dapat dipakai pada bytes.Buffer, koneksi jaringan, maupun bufio.ReadWriter tanpa perubahan kode. Fungsi ini hanya memperhatikan gabungan kontraknya dan bukan tipe konkretnya. Prinsip yang sama berlaku pada varian lain di paket io, seperti io.ReadWriteCloser, yang menyusun tiga kontrak kecil menjadi satu.
Kesalahan umum#
Tiga kesalahan berikut muncul ketika interface pustaka standar mulai diimplementasikan.
- Mendefinisikan
Stringdengan receiver pointer. Pemenuhanfmt.Stringermenempel pada*Catatan, sedangkanfmt.Println(catatan)menerima nilaiCatatandan mencetaknya mentah sebagai{Rapat mingguan 4}. Perbaikannya, gunakan receiver nilai untuk tipe kecil, atau teruskan pointer secara konsisten di seluruh kode. - Mengembalikan nilai error dari tipe dengan metode
Errorbereceiver pointer. Deklarasifunc (e *ErrAmbang) Error() stringmembuat nilaiErrAmbang{}tidak memenuhierror, sehingga pernyataan pengembaliannya ditolak kompilator. Perbaikannya, jaga konsistensi dengan receiver nilai untuk error ringan, atau kembalikan&ErrAmbang{...}bila receiver pointer memang diperlukan. - Menempelkan ketiga metode
sort.Interfacepada tipe yang salah. MetodeLen,Less, danSwapharus menempel pada tipe irisan yang diurutkan, misalnyaDaftarCatatan, dan bukan pada tipe elemennya sepertiCatatan. Apabila metodenya menempel pada elemen, irisannya tidak memenuhi interface dansort.Sortmenolaknya saat kompilasi.
Ringkasan#
fmt.Stringermengubah cara sebuah nilai dicetak oleh paketfmtmelalui metodeString() string.erroradalah interface satu metode, sehingga struct pembawa data dapat sekaligus menjadi error.sort.InterfacemenuntutLen,Less, danSwap, dan satu algoritme mengurutkan tipe apa pun yang memenuhinya.- Interface disusun melalui penyematan, dan
io.ReadWriteradalah gabunganio.Readerdenganio.Writer.
Referensi#
- Package fmt — The Go Authors
— Dokumentasi
fmt.Stringerdan keterlibatannya dalam pencetakan. - Package sort — The Go Authors
— Dokumentasi
sort.Interfacebeserta ketiga metodenya. - Package io — The Go Authors
— Dokumentasi
io.ReadWriterdan varian komposisinya. - Error handling and Go — The Go Blog — Prinsip error sebagai nilai yang diperiksa oleh pemanggil.