Interface on the Producer Side
1. Definisi Producer vs Consumer Side
Producer side: Interface didefinisikan di package yang sama dengan implementasi konkret
Consumer side: Interface didefinisikan di package eksternal dimana interface tersebut digunakan
2. Kesalahan Umum
Developer sering membuat interface di producer side (kebiasaan dari Java/C#)
Ini memaksakan abstraksi yang mungkin tidak diperlukan oleh client
3. Prinsip yang Benar
"Abstractions should be discovered, not created"
Biarkan client yang menentukan kebutuhan abstraksinya
Producer hanya menyediakan implementasi konkret
4. Keuntungan Interface di Consumer Side
Client bisa membuat interface yang spesifik sesuai kebutuhannya
Mengikuti Interface Segregation Principle (SOLID)
Interface bisa dibuat unexported jika hanya digunakan internal
Tidak ada circular dependency
5. Pengecualian
Standard library seperti
encoding
package memang mendefinisikan interface di producer sideHanya lakukan ini jika benar-benar yakin abstraksi akan berguna untuk banyak client
6. Returning Interfaces
Hindari mengembalikan interface dari fungsi
Kembalikan struct konkret, terima interface sebagai parameter
Pengecualian:
error
interface dan beberapa case di standard library
Contoh Kode
Salah - Interface di Producer Side
// package store (producer)
package store
type Customer struct {
ID string
Name string
}
// Interface dipaksakan di producer side
type CustomerStorage interface {
StoreCustomer(customer Customer) error
GetCustomer(id string) (Customer, error)
UpdateCustomer(customer Customer) error
GetAllCustomers() ([]Customer, error)
GetCustomersWithoutContract() ([]Customer, error)
GetCustomersWithNegativeBalance() ([]Customer, error)
}
// Implementasi konkret
type InMemoryStore struct {
customers map[string]Customer
}
func (s *InMemoryStore) StoreCustomer(customer Customer) error {
s.customers[customer.ID] = customer
return nil
}
func (s *InMemoryStore) GetCustomer(id string) (Customer, error) {
return s.customers[id], nil
}
// ... method lainnya
// Fungsi factory yang buruk - mengembalikan interface
func NewInMemoryStore() CustomerStorage {
return &InMemoryStore{
customers: make(map[string]Customer),
}
}
Benar - Interface di Consumer Side
// package store (producer) - Hanya提供 implementasi konkret
package store
type Customer struct {
ID string
Name string
}
// Hanya提供 struct konkret tanpa interface
type InMemoryStore struct {
customers map[string]Customer
}
func (s *InMemoryStore) StoreCustomer(customer Customer) error {
s.customers[customer.ID] = customer
return nil
}
func (s *InMemoryStore) GetCustomer(id string) (Customer, error) {
return s.customers[id], nil
}
func (s *InMemoryStore) GetAllCustomers() ([]Customer, error) {
// implementasi
}
// Fungsi factory yang baik - mengembalikan struct konkret
func NewInMemoryStore() *InMemoryStore {
return &InMemoryStore{
customers: make(map[string]Customer),
}
}
Consumer Mendefinisikan Interface Sesuai Kebutuhan
// package client (consumer)
package client
import "project/store"
// Client hanya butuh method GetAllCustomers
type customersGetter interface {
GetAllCustomers() ([]store.Customer, error)
}
// Client lain butuh method yang berbeda
type customerWriter interface {
StoreCustomer(customer store.Customer) error
UpdateCustomer(customer store.Customer) error
}
func ProcessCustomers(getter customersGetter) {
customers, _ := getter.GetAllCustomers()
// process customers
}
func main() {
// Gunakan concrete implementation
store := store.NewInMemoryStore()
// Client bisa menggunakan concrete implementation langsung
// atau membuat abstraction sesuai kebutuhan
ProcessCustomers(store) // works because InMemoryStore implements the interface implicitly
}
Penerapan Postel's Law dalam Go
// Conservative in what you do: return structs
func NewDatabaseStore() *DatabaseStore { // return concrete struct
return &DatabaseStore{}
}
// Liberal in what you accept: accept interfaces
func ProcessData(reader io.Reader) error { // accept interface
data, _ := io.ReadAll(reader)
// process data
return nil
}
Best Practices
Start with concrete implementations - Jangan mulai dengan interface
Let clients discover interfaces - Biarkan client yang menentukan abstraksi
Keep interfaces small - Interface seharusnya spesifik dan minimalis
Return structs, accept interfaces - Pola umum yang baik
Use unexported interfaces - Untuk kebutuhan internal client
Pengecualian yang Valid
// Standard library case where interface on producer side makes sense
package encoding
type BinaryMarshaler interface {
MarshalBinary() (data []byte, err error)
}
type BinaryUnmarshaler interface {
UnmarshalBinary(data []byte) error
}
Kesimpulan: Jangan paksakan interface di producer side kecuali Anda benar-benar yakin abstraksi tersebut akan berguna untuk banyak client dan sudah terbukti kebutuhan nyatanya.
Last updated