Table of Contents

08 – Interface

Pengertian

Interface adalah kumpulan definisi method yang tidak memiliki isi (hanya definisi saja), yang dibungkus dengan nama tertentu.

Interface merupakan tipe data. Nilai objek bertipe interface zero value-nya adalah nil. Interface mulai bisa digunakan jika sudah ada isinya, yaitu objek konkret yang memiliki definisi method minimal sama dengan yang ada di interface-nya.

Penerapan Interface

Yang pertama perlu dilakukan untuk menerapkan interface adalah menyiapkan interface beserta definisi method nya. Keyword type dan interface digunakan untuk pendefinisian interface.

				
					package main

import "fmt"
import "math"

type hitung interface {
    luas() float64
    keliling() float64
}
				
			

Pada kode di atas, interface hitung memiliki 2 definisi method, luas() dan keliling(). Interface ini nantinya digunakan sebagai tipe data pada variabel, di mana variabel tersebut akan menampung objek bangun datar hasil dari struct yang akan kita buat.

Dengan memanfaatkan interface hitung, perhitungan luas dan keliling bangun datar bisa dilakukan, tanpa perlu tahu jenis bangun datarnya sendiri itu apa.

Siapkan struct bangun datar lingkaran, struct ini memiliki method yang beberapa di antaranya terdefinisi di interface hitung.

				
					type lingkaran struct {
    diameter float64
}

func (l lingkaran) jariJari() float64 {
    return l.diameter / 2
}

func (l lingkaran) luas() float64 {
    return math.Pi * math.Pow(l.jariJari(), 2)
}

func (l lingkaran) keliling() float64 {
    return math.Pi * l.diameter
}
				
			

Struct lingkaran di atas memiliki tiga method, jariJari()luas(), dan keliling().

Selanjutnya, siapkan struct bangun datar persegi.

				
					type persegi struct {
    sisi float64
}

func (p persegi) luas() float64 {
    return math.Pow(p.sisi, 2)
}

func (p persegi) keliling() float64 {
    return p.sisi * 4
}
				
			

Perbedaan struct persegi dengan lingkaran terletak pada method jariJari(). Struct persegi tidak memiliki method tersebut. Tetapi meski demikian, variabel objek hasil cetakan 2 struct ini akan tetap bisa ditampung oleh variabel cetakan interface hitung, karena dua method yang ter-definisi di interface tersebut juga ada pada struct persegi dan lingkaran, yaitu luas() dan keliling().

Buat implementasi perhitungan di main

				
					func main() {
    var bangunDatar hitung

    bangunDatar = persegi{10.0}
    fmt.Println("===== persegi")
    fmt.Println("luas      :", bangunDatar.luas())
    fmt.Println("keliling  :", bangunDatar.keliling())

    bangunDatar = lingkaran{14.0}
    fmt.Println("===== lingkaran")
    fmt.Println("luas      :", bangunDatar.luas())
    fmt.Println("keliling  :", bangunDatar.keliling())
    fmt.Println("jari-jari :", bangunDatar.(lingkaran).jariJari())
}
				
			

Perhatikan kode di atas. Variabel objek bangunDatar bertipe interface hitung. Variabel tersebut digunakan untuk menampung objek konkrit buatan struct lingkaran dan persegi.

Dari variabel tersebut, method luas() dan keliling() diakses. Secara otomatis Golang akan mengarahkan pemanggilan method pada interface ke method asli milik struct yang bersangkutan.

				
					===== persegi
luas      : 100
keliling  : 40
===== lingkaran
luas      : 153.93804002589985
keliling  : 43.982297150257104
jari-jari : 7
				
			

Method jariJari() pada struct lingkaran tidak akan bisa diakses karena tidak terdefinisi dalam interface hitung. Pengaksesannya dengan paksa akan menyebabkan error.

Untuk mengakses method yang tidak ter-definisi di interface, variabel-nya harus di-casting terlebih dahulu ke tipe asli variabel konkritnya (pada kasus ini tipenya lingkaran), setelahnya method akan bisa diakses.

Cara casting objek interface sedikit unik, yaitu dengan menuliskan nama tipe tujuan dalam kurung, ditempatkan setelah nama interface dengan menggunakan notasi titik (seperti cara mengakses property, hanya saja ada tanda kurung nya). Contohnya bisa dilihat di kode berikut. Statement bangunDatar.(lingkaran) adalah contoh casting pada objek interface.

				
					var bangunDatar hitung = lingkaran{14.0}
var bangunLingkaran lingkaran = bangunDatar.(lingkaran)

bangunLingkaran.jariJari()
				
			

Perlu diketahui juga, jika ada interface yang menampung objek konkrit di mana struct-nya tidak memiliki salah satu method yang terdefinisi di interface, error juga akan muncul. Intinya kembali ke aturan awal, variabel interface hanya bisa menampung objek yang minimal memiliki semua method yang terdefinisi di interface-nya.

Interface Kosong (Any)

Interface kosong atau empty interface yang dinotasikan dengan interface{} atau any (per go v1.18), merupakan tipe data yang sangat spesial. Variabel bertipe ini bisa menampung segala jenis data, bahkan array, pointer, apapun. Tipe data dengan konsep ini biasa disebut dengan dynamic typing.

Penggunaan interface{}

interface{} merupakan tipe data, sehingga cara penggunaannya sama seperti pada tipe data lainnya, hanya saja nilai yang diisikan bisa apa saja. Contoh:

				
					package main

import "fmt"

func main() {
    var secret interface{}

    secret = "ethan hunt"
    fmt.Println(secret)

    secret = []string{"apple", "manggo", "banana"}
    fmt.Println(secret)

    secret = 12.4
    fmt.Println(secret)
}
				
			

Keyword interface seperti yang kita tau, digunakan untuk pembuatan interface. Tetapi ketika ditambahkan kurung kurawal ({}) di belakang-nya (menjadi interface{}), maka kegunaannya akan berubah, yaitu sebagai tipe data.

				
					ethan hunt
[apple manggo banana]
12.4
				
			

Agar tidak bingung, coba perhatikan kode berikut.

				
					var data map[string]interface{}

data = map[string]interface{}{
    "name":      "ethan hunt",
    "grade":     2,
    "breakfast": []string{"apple", "manggo", "banana"},
}
				
			

Pada kode di atas, disiapkan variabel data dengan tipe map[string]interface{}, yaitu sebuah koleksi dengan key bertipe string dan nilai bertipe interface kosong interface{}.

Kemudian variabel tersebut di-inisialisasi, ditambahkan lagi kurung kurawal setelah keyword deklarasi untuk kebutuhan pengisian data, map[string]interface{}{ /* data */ }.

Dari situ terlihat bahwa interface{} bukanlah sebuah objek, melainkan tipe data.

Type Alias Any

Pada Go rilis versi v1.18, interface kosong bisa dituliskan dengan menggunakan any. Contohnya:

				
					var data map[string]any

data = map[string]any{
    "name":      "ethan hunt",
    "grade":     2,
    "breakfast": []string{"apple", "manggo", "banana"},
}
				
			
Casting Variabel Interface Kosong

Variabel bertipe interface{} bisa ditampilkan ke layar sebagai string dengan memanfaatkan fungsi print, seperti fmt.Println(). Tapi perlu diketahui bahwa nilai yang dimunculkan tersebut bukanlah nilai asli, melainkan bentuk string dari nilai aslinya.

Hal ini penting diketahui, karena untuk melakukan operasi yang membutuhkan nilai asli pada variabel yang bertipe interface{}, diperlukan casting ke tipe aslinya. Contoh seperti pada kode berikut.

				
					package main

import "fmt"
import "strings"

func main() {
    var secret interface{}

    secret = 2
    var number = secret.(int) * 10
    fmt.Println(secret, "multiplied by 10 is :", number)

    secret = []string{"apple", "manggo", "banana"}
    var gruits = strings.Join(secret.([]string), ", ")
    fmt.Println(gruits, "is my favorite fruits")
}
				
			

Pertama, variabel secret menampung nilai bertipe numerik. Ada kebutuhan untuk mengalikan nilai yang ditampung variabel tersebut dengan angka 10. Maka perlu dilakukan casting ke tipe aslinya, yaitu int, setelahnya barulah nilai bisa dioperasikan, yaitu secret.(int) * 10.

Pada contoh kedua, secret berisikan array string. Kita memerlukan string tersebut untuk digabungkan dengan pemisah tanda koma. Maka perlu di-casting ke []string terlebih dahulu sebelum bisa digunakan di strings.Join(), contohnya pada strings.Join(secret.([]string), ", ").

				
					2 multiplied by 10 is : 20
apple, manggo, banana is my favorite fruits
				
			

Teknik casting pada interface disebut dengan type assertions.

Casting Variabel Interface Kosong Ke Objek Pointer

Variabel interface{} bisa menyimpan data apa saja, termasuk data objek, pointer, ataupun gabungan keduanya. Di bawah ini merupakan contoh penerapan interface untuk menampung data objek pointer.

				
					type person struct {
    name string
    age  int
}

var secret interface{} = &person{name: "wick", age: 27}
var name = secret.(*person).name
fmt.Println(name)
				
			

Variabel secret dideklarasikan bertipe interface{} menampung referensi objek cetakan struct person. Cara casting dari interface{} ke struct pointer adalah dengan menuliskan nama struct-nya dan ditambahkan tanda asterisk (*) di awal, contohnya seperti secret.(*person). Setelah itu barulah nilai asli bisa diakses.

Kombinasi Slice, map, dan interface{}

Tipe []map[string]interface{} adalah salah satu tipe yang paling sering digunakan (menurut saya), karena tipe data tersebut bisa menjadi alternatif tipe slice struct.

Pada contoh berikut, variabel person dideklarasikan berisi data slice map berisikan 2 item dengan key adalah name dan age.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut elit tellus, luctus nec ullamcorper mattis, pulvinar dapibus leo.

				
					var person = []map[string]interface{}{
    {"name": "Wick", "age": 23},
    {"name": "Ethan", "age": 23},
    {"name": "Bourne", "age": 22},
}

for _, each := range person {
    fmt.Println(each["name"], "age is", each["age"])
}
				
			

Dengan memanfaatkan slice dan interface{}, kita bisa membuat data array yang isinya adalah bisa apa saja. Silakan perhatikan contoh berikut.

				
					var fruits = []interface{}{
    map[string]interface{}{"name": "strawberry", "total": 10},
    []string{"manggo", "pineapple", "papaya"},
    "orange",
}

for _, each := range fruits {
    fmt.Println(each)
}