Generics dan interface sama-sama menghindari duplikasi kode, tetapi keduanya menyelesaikan persoalan yang berbeda. Topik ini menimbang kapan generics membantu, kapan interface biasa tetap lebih tepat, dan biaya apa yang dibayar untuk masing-masing. Setelah membacanya, pemilihan antara parameter tipe dan interface dapat dilandasi kriteria yang jelas pada saat mendesain fungsi dan tipe.
Kapan generics membantu#
Generics membantu ketika logikanya identik untuk banyak tipe dan tipe konkretnya perlu diketahui di dalam kode. Tanda-tanda yang paling sering muncul adalah fungsi yang mengembalikan tipe sejenis dengan masukannya, operasi perbandingan atau aritmetika atas bilangan, serta kontainer yang menyimpan satu jenis nilai. Pada ketiganya, pendekatan interface memaksa asersi tipe atau salinan kode, sedangkan parameter tipe mempertahankan informasi tipe tanpa biaya tambahan saat program berjalan. Generics juga menghindari penyimpanan nilai ke dalam interface pada beberapa kasus, sehingga alokasi tambahan dapat dikurangi.
package main
import (
"cmp"
"fmt"
)
// terbesar mengembalikan elemen terbesar dari slice yang tidak kosong.
func terbesar[T cmp.Ordered](x []T) T {
m := x[0]
for _, v := range x[1:] {
if v > m {
m = v
}
}
return m
}
func main() {
skor := []int{78, 91, 66, 84}
mataUji := []string{"aljabar", "statistika", "fisika"}
fmt.Println(terbesar(skor))
fmt.Println(terbesar(mataUji))
}91
statistikaFungsi terbesar menerima constraint cmp.Ordered dari paket cmp, yaitu himpunan tipe yang mendukung perbandingan urutan seperti bilangan dan string. Satu definisi melayani []int dan []string, dan nilai kembaliannya bertipe sama dengan elemen masukannya. Bila fungsi yang sama ditulis dengan any, hasilnya harus berasersi kembali dan kesalahan tipe baru terlihat saat program berjalan. Kasus yang tidak cocok sama pentingnya untuk dikenali. Bila badan fungsi memerlukan perilaku khusus per tipe, parameter tipe hanya memindahkan percabangan ke constraint yang dipaksakan, dan kodenya menjadi lebih rumit daripada dua fungsi konkret.
Kapan interface lebih tepat#
Interface lebih tepat ketika yang disatukan adalah perilaku, bukan logika yang sama. Kumpulan data yang isinya bercampur tipe, batas antar paket yang bergantung pada perilaku seminimal mungkin, dan fungsi yang menerima banyak implementasi berbeda merupakan wilayah yang cocok untuk interface. Parameter tipe justru memaksa seluruh item bertipe sama pada satu pemanggilan, sehingga polimorfisme yang dikehendaki hilang. Pustaka standar menggunakan pola ini secara luas, seperti io.Reader yang melayani berkas, koneksi jaringan, dan penyangga memori dengan satu deklarasi. Ukuran yang berguna untuk menimbang keduanya adalah frekuensi perubahan, karena kontrak interface yang kecil jarang berubah dan mudah ditambah implementasi, sedangkan constraint yang terus tumbuh menandai abstraksi yang dipaksakan.
package main
import "fmt"
// Penyapa menggambarkan perilaku menyapa tanpa memandang tipenya.
type Penyapa interface {
Sapa() string
}
// Siswa adalah satu peserta kelas.
type Siswa struct {
Nama string
}
// Sapa menyapa dengan gaya ringan.
func (s Siswa) Sapa() string {
return "Halo, " + s.Nama
}
// Guru adalah satu pengajar kelas.
type Guru struct {
Nama string
}
// Sapa menyapa dengan gaya formal.
func (g Guru) Sapa() string {
return "Selamat pagi, " + g.Nama
}
func main() {
ruangan := []Penyapa{
Siswa{Nama: "Budi"},
Guru{Nama: "Citra"},
Siswa{Nama: "Dewi"},
}
for _, p := range ruangan {
fmt.Println(p.Sapa())
}
}Halo, Budi
Selamat pagi, Citra
Halo, DewiSlice ruangan bertipe []Penyapa menampung Siswa dan Guru sekaligus karena yang dituntut hanyalah metode Sapa. Setiap pemanggilan p.Sapa() menjalankan perilaku tipe konkretnya masing-masing saat program berjalan. Generics tidak dapat menggantikan pola ini, karena satu pemanggilan fungsi generik hanya melayani satu tipe.
Tabel berikut merangkum perbedaan keduanya.
| Aspek | Generics | Interface |
|---|---|---|
| Pemeriksaan tipe | Saat kompilasi | Tipe dinamis saat program berjalan |
| Keseragaman data | Satu tipe per instansiasi | Banyak tipe dalam satu nilai interface |
| Yang disatukan | Logika sama atas tipe berbeda | Perilaku berbeda atas kontrak yang sama |
| Biaya penulisan | Deklarasi parameter tipe dan constraint | Implementasi metode pada setiap tipe |
| Contoh yang cocok | Pencarian, pengurutan, kontainer | io.Reader, fmt.Stringer, batas antar paket |
Panduan pemilihan#
Panduan resmi Go dalam artikel When To Use Generics menempatkan generics sebagai alat untuk kode yang logikanya identik antar tipe, dan bukan sebagai pengganti interface. Urutan praktisnya dimulai dari tipe konkret, karena tipe konkret memberi kesempatan melihat kebutuhan yang nyata sebelum abstraksi dibuat. Bila perilakunya beragam, gunakan interface kecil. Generics masuk ketika salinan kode untuk tipe kedua benar-benar muncul dan isinya sama persis.
Artikel tersebut juga menyarankan agar generics tidak dipakai hanya karena kodenya terlihat lebih rapi, karena abstraksi yang tidak dibutuhkan tetap dibaca oleh setiap pengguna API-nya. Pertimbangan yang sama berlaku untuk tipe generik, yaitu bila penampungnya hanya dipakai dengan satu tipe, deklarasi konkret sudah cukup. Kriteria itu juga menjaga API tetap ramping, karena setiap parameter tipe menambah hal yang harus dipahami pembaca tanda tangan fungsinya.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul saat memilih antara generics dan interface. Ketiganya berpangkal pada keinginan menyatukan kode sebelum kebutuhannya benar-benar terlihat.
- Mengganti interface dengan generics pada batas yang menuntut banyak implementasi. Fungsi generik hanya menerima satu tipe per pemanggilan, sehingga pengirim banyak implementasi sekaligus tidak terlayani. Perbaikannya, gunakan interface untuk batas perilaku dan simpan generics untuk logika yang seragam. Pertanyaan pemandunya sederhana, yaitu apakah tipe konkretnya perlu diketahui di dalam logika, ataukah yang dibutuhkan hanyalah perilakunya.
- Menulis fungsi generik padahal parameternya hanya dipanggil dengan satu tipe. Deklarasi
[T any]untuk fungsi yang selalu menerima string hanya menambah bunyi tanpa manfaat. Perbaikannya, tulis tipe konkretnya dan tunggu kebutuhan tipe kedua sebelum menggeneralisasi. - Memaksakan generics untuk logika yang sebenarnya berbeda antar tipe. Bila badan fungsi bercabang per tipe melalui type switch, keunggulan generics hilang. Perbaikannya, pisahkan logikanya per tipe atau satukan lewat metode pada interface.
Ringkasan#
- Generics membantu untuk logika identik antar tipe yang membutuhkan tipe konkret, misalnya pencarian, pengurutan, dan kontainer.
- Interface lebih tepat untuk menyatukan perilaku berbeda atas kontrak yang sama, termasuk kumpulan data yang bercampur tipe.
cmp.Orderedadalah constraint pustaka standar untuk tipe yang mendukung perbandingan urutan.- Urutan praktisnya mulai dari tipe konkret, lalu interface kecil untuk perilaku, dan generics ketika salinan untuk tipe kedua muncul.
Referensi#
- When To Use Generics — The Go Blog — Panduan resmi kapan generics dipilih dan kapan tidak.
- An Introduction To Generics — The Go Blog — Konteks dan tujuan desain generics pada Go.
- Package cmp — The Go Authors — Constraint Ordered untuk tipe yang mendukung perbandingan urutan.
- Frequently Asked Questions (FAQ) — The Go Project — Jawaban resmi pertanyaan umum tentang desain Go, termasuk generics.