Interface adalah kumpulan method yang harus diimplementasikan oleh tipe (struct) yang mengadopsinya. Tujuan utama penggunaan interface adalah menciptakan sistem yang modular dan fleksibel. Modular berarti membagi sistem kompleks menjadi bagian-bagian kecil yang disebut modul, sedangkan fleksibel mengacu pada kemampuan sistem untuk beradaptasi terhadap perubahan tanpa perlu melakukan perubahan besar pada kode yang sudah ada.
Permasalahan Tanpa Interface
Pada aplikasi yang tidak menggunakan interface, penambahan atau penggantian data source seringkali menyebabkan perubahan besar pada kode, terutama jika tidak menggunakan ORM (Object Relational Mapping). Misalnya, jika aplikasi memiliki dua tipe data source seperti Redis dan In Memory, masing-masing memiliki perilaku yang sama, namun implementasinya terpisah.
Contoh Implementasi Tanpa Interface
redis.go
:
type Redis struct{}
var usersFromRedis = []map[string]any{
{"id": 1, "name": "Anakin Skywalker", "age": 46},
{"id": 2, "name": "Han Solo", "age": 66},
}
func (r Redis) GetAll() []map[string]any {
return usersFromRedis
}
func (r Redis) Find(id int) map[string]any {
for _, user := range usersFromRedis {
if user["id"] == id {
return user
}
}
return nil
}
inmemory.go
:
type InMemory struct{}
var usersFromInMemory = []map[string]any{
{"id": 1, "name": "Luke Skywalker", "age": 53},
{"id": 2, "name": "Yoda", "age": 900},
}
func (r InMemory) GetAll() []map[string]any {
return usersFromInMemory
}
func (r InMemory) Find(id int) map[string]any {
for _, user := range usersFromInMemory {
if user["id"] == id {
return user
}
}
return nil
}
main.go
:
func main() {
redis := Redis{}
inmemory := InMemory{}
storageType := "redis" // atau "inmemory"
var data []map[string]any
var user map[string]any
switch storageType {
case "redis":
data = redis.GetAll()
user = redis.Find(1)
case "inmemory":
data = inmemory.GetAll()
user = inmemory.Find(1)
default:
fmt.Println("Invalid storage type")
return
}
fmt.Println(data)
fmt.Println(user)
}
Output
Jika menggunakan redis
:
$ go run *.go
[map[age:46 id:1 name:Anakin Skywalker] map[age:66 id:2 name:Han Solo]]
map[age:46 id:1 name:Anakin Skywalker]
Jika menggunakan inmemory
:
$ go run *.go
[map[age:53 id:1 name:Luke Skywalker] map[age:900 id:2 name:Yoda]]
map[age:53 id:1 name:Luke Skywalker]
Untuk mengganti data source, cukup ubah nilai storageType
. Namun, jika ingin menambah data source baru, seperti MongoDB, perlu menambah kode baru di setiap file terkait.
Penambahan Data Source Baru
Misal ingin menambah MongoDB sebagai data source baru:
mongodb.go
:
type MongoDB struct{}
var usersFromMongoDB = []map[string]any{
{"id": 1, "name": "Mace Windu", "age": 53},
{"id": 2, "name": "Jabba The Hut", "age": 650},
}
func (m MongoDB) GetAll() []map[string]any {
return usersFromMongoDB
}
func (m MongoDB) Find(id int) map[string]any {
for _, user := range usersFromMongoDB {
if user["id"] == id {
return user
}
}
return nil
}
Kemudian, di main.go
harus menambah pengkondisian baru:
func main() {
redis := Redis{}
inmemory := InMemory{}
mongodb := MongoDB{}
storageType := "mongodb"
var data []map[string]any
var user map[string]any
switch storageType {
case "redis":
data = redis.GetAll()
user = redis.Find(1)
case "inmemory":
data = inmemory.GetAll()
user = inmemory.Find(1)
case "mongodb":
data = mongodb.GetAll()
user = mongodb.Find(1)
default:
fmt.Println("Invalid storage type")
return
}
fmt.Println(data)
fmt.Println(user)
}
Output
$ go run *.go
[map[age:53 id:1 name:Mace Windu] map[age:650 id:2 name:Jabba The Hut]]
map[age:53 id:1 name:Mace Windu]
Permasalahan:
Setiap kali menambah data source baru, kode pada main.go
akan semakin panjang dan tidak efisien. Hal ini menyebabkan redundansi dan membuat kode sulit dipelihara.
Solusi: Menggunakan Interface
Untuk mengatasi masalah tersebut, gunakan interface agar kode lebih modular dan fleksibel.
Definisikan Interface
Buat file storage.go
:
package main
type Storage interface {
GetAll() []map[string]any
Find(id int) map[string]any
}
Implementasi Constructor pada Setiap Data Source
Setiap data source membuat fungsi constructor yang mengembalikan tipe Storage
:
redis.go
:
func NewRedis() Storage {
return Redis{}
}
inmemory.go
:
func NewInMemory() Storage {
return InMemory{}
}
mongodb.go
:
func NewMongoDB() Storage {
return MongoDB{}
}
Penggunaan di main.go
Sekarang, cukup gunakan constructor sesuai kebutuhan:
func main() {
// storage := NewRedis()
// storage := NewInMemory()
storage := NewMongoDB()
fmt.Println(storage.GetAll())
fmt.Println(storage.Find(1))
}
Output
$ go run *.go
[map[age:53 id:1 name:Mace Windu] map[age:650 id:2 name:Jabba The Hut]]
map[age:53 id:1 name:Mace Windu]
Menambah Data Source Baru (Contoh: PostgreSQL)
Jika ingin menambah PostgreSQL:
postgresql.go
:
type PostgreSQL struct{}
var usersFromPostgreSQL = []map[string]any{
{"id": 1, "name": "Leia Organa", "age": 54},
{"id": 2, "name": "Obi-Wan Kenobi", "age": 57},
}
func (p PostgreSQL) GetAll() []map[string]any {
return usersFromPostgreSQL
}
func (p PostgreSQL) Find(id int) map[string]any {
for _, user := range usersFromPostgreSQL {
if user["id"] == id {
return user
}
}
return nil
}
func NewPostgreSQL() Storage {
return PostgreSQL{}
}
main.go
:
func main() {
// storage := NewRedis()
// storage := NewInMemory()
// storage := NewMongoDB()
storage := NewPostgreSQL()
fmt.Println(storage.GetAll())
fmt.Println(storage.Find(1))
}
Keuntungan Menggunakan Interface
- Modular: Setiap data source terpisah dan mudah dikembangkan.
- Fleksibel: Penambahan data source baru tidak memerlukan perubahan pada kode utama.
- Mudah Dipelihara: Kode lebih ringkas dan mudah dibaca.
Referensi: