Interface adalah kumpulan method yang mendefinisikan perilaku yang harus diimplementasikan oleh sebuah tipe. Dalam bahasa Go, interface sangat berguna untuk mendukung prinsip pemrograman berbasis kontrak dan memudahkan pengujian serta pemisahan tanggung jawab.
Masalah Umum: Fat Interface dan Interface Pollution
Salah satu masalah yang sering muncul adalah “interface pollution” atau “fat interface”, yaitu ketika sebuah interface memiliki terlalu banyak method. Hal ini menyebabkan setiap tipe yang mengimplementasikan interface tersebut harus mengimplementasikan semua method, meskipun tidak semuanya dibutuhkan. Akibatnya, interface menjadi sulit dipelihara dan tidak fleksibel.
Contoh kasus pada repository:
type UserRepository interface {
FindAll() (*[]User, error)
FindByID(ID string) (*User, error)
FindByUsername(username string) (*User, error)
FindByEmail(email string) (*User, error)
AddUser(username string, email string) (*User, error)
}
Jika ada kebutuhan baru, misalnya menambah method DeleteUser
, maka interface akan semakin membesar. Hal ini tidak sesuai dengan prinsip Interface Segregation Principle (ISP) yang menyarankan agar interface tetap kecil dan spesifik.
Solusi: Memecah Interface dan Embedding Interface
Untuk mengatasi masalah ini, kita dapat memecah interface menjadi beberapa bagian kecil yang lebih spesifik, lalu menggabungkannya menggunakan teknik embedding interface. Dengan cara ini, tipe hanya perlu mengimplementasikan interface yang benar-benar dibutuhkan.
type UserRetriever interface {
FindAll() (*[]User, error)
FindByID(ID string) (*User, error)
FindByUsername(username string) (*User, error)
FindByEmail(email string) (*User, error)
}
type UserAdder interface {
AddUser(username string, email string) (*User, error)
}
type UserRepository interface {
UserRetriever
UserAdder
}
Dengan pemisahan ini, jika ada tipe yang hanya perlu mengambil data user, cukup mengimplementasikan UserRetriever
saja.
Masalah Lain: Bloated Interface
Masalah lain yang sering terjadi adalah “bloated interface”, yaitu ketika sebuah interface memaksa tipe untuk mengimplementasikan method yang tidak relevan. Contoh klasiknya adalah pada perangkat printer:
Misalkan ada dua jenis printer:
- Epson: Bisa print dan scan.
- HP: Hanya bisa print.
Jika kita membuat interface seperti berikut:
type PrinterScanner interface {
Print()
Scan()
}
Maka kedua tipe harus mengimplementasikan kedua method, meskipun HP tidak bisa scan. Ini menyalahi prinsip ISP.
Implementasi yang Salah
type Epson struct{}
func (e Epson) Print() {
fmt.Println("Mencetak menggunakan Epson..")
}
func (e Epson) Scan() {
fmt.Println("Memindai menggunakan Epson..")
}
type HP struct{}
func (h HP) Print() {
fmt.Println("Mencetak menggunakan HP..")
}
func (h HP) Scan() {
// Tidak relevan, tapi tetap harus diimplementasikan
fmt.Println("HP tidak mendukung scan.")
}
Pada contoh di atas, HP terpaksa mengimplementasikan method Scan()
walaupun tidak mendukung fitur tersebut.
Solusi: Interface Segregation
Solusi yang tepat adalah memisahkan interface menjadi lebih kecil dan spesifik:
type Printer interface {
Print()
}
type Scanner interface {
Scan()
}
Jika ada perangkat yang mendukung kedua fitur, kita bisa membuat interface gabungan:
type PrinterScanner interface {
Printer
Scanner
}
Implementasi yang Benar
type Epson struct{}
func (e Epson) Print() {
fmt.Println("Mencetak menggunakan Epson..")
}
func (e Epson) Scan() {
fmt.Println("Memindai menggunakan Epson..")
}
func NewEpson() PrinterScanner {
return &Epson{}
}
type HP struct{}
func (h HP) Print() {
fmt.Println("Mencetak menggunakan HP..")
}
func NewHP() Printer {
return &HP{}
}
Contoh Penggunaan
package main
func main() {
e := NewEpson()
e.Print()
e.Scan()
h := NewHP()
h.Print()
// h.Scan() // Tidak tersedia, sesuai dengan fitur HP
}
Output:
Mencetak menggunakan Epson..
Memindai menggunakan Epson..
Mencetak menggunakan HP..
Dengan pemisahan interface seperti ini, tipe hanya perlu mengimplementasikan method yang relevan dengan kemampuannya. Ini membuat kode lebih bersih, mudah dipelihara, dan mengikuti prinsip desain yang baik.
Kesimpulan
- Hindari membuat interface yang terlalu besar (fat/bloated interface).
- Pecah interface menjadi bagian-bagian kecil yang spesifik.
- Gunakan embedding interface untuk menggabungkan beberapa interface kecil jika diperlukan.
- Terapkan prinsip Interface Segregation Principle agar kode lebih fleksibel dan mudah diuji.
Dengan memahami dan menerapkan prinsip-prinsip ini, penggunaan interface dalam Go akan menjadi lebih efektif dan efisien.