author Ihsan Arif | 6 minutes

Mengenal Struct dan Interface Pada Golang

Meskipun mungkin bagi kita untuk menulis program hanya menggunakan tipe data bawaan Go, pada titik tertentu itu akan menjadi sangat membosankan. Pertimbangkan program yang berinteraksi dengan bentuk seperti kode dibawah ini.

package main

import (
	"fmt"
	"math"
)

func distance(x1, y1, x2, y2 float64) float64 {
	a := x2 - x1
	b := y2 - y1
	return math.Sqrt(a*a + b*b)
}

func rectangleArea(x1, y1, x2, y2 float64) float64 {
	l := distance(x1, y1, x1, y2)
	w := distance(x1, y1, x2, y1)
	return l * w
}

func circleArea(x, y, r float64) float64 {
	return math.Pi * r * r
}

func main() {
	var rx1, ry1 float64 = 0, 0
	var rx2, ry2 float64 = 10, 10
	var cx, cy, cr float64 = 0, 0, 5
	fmt.Println(rectangleArea(rx1, ry1, rx2, ry2))
	fmt.Println(circleArea(cx, cy, cr))
}

Melacak semua koordinat membuat sulit untuk melihat apa yang sedang dilakukan program dan kemungkinan akan menyebabkan kesalahan.

Structs

Cara mudah untuk membuat program ini agar lebih baik adalah dengan menggunakan struct. Sebuah struct adalah tipe yang berisi bidang bernama. Misalnya kita bisa mewakili lingkaran seperti ini:

type Circle struct {
  x float64
  y float64
  r float64 
}

Kata kunci type memperkenalkan tipe baru. Diikuti dengan nama tipe (Lingkaran), kata kunci struct untuk menunjukkan bahwa kita mendefinisikan tipe struct dan daftar bidang di dalam kurung kurawal. Setiap bidang memiliki nama dan jenis. Seperti halnya fungsi, kita dapat menutup bidang yang memiliki tipe yang sama:

type Circle struct {
     x, y, r float64
}

Inisialiasi

Kita dapat membuat turunan dari tipe Lingkaran baru kita dalam berbagai cara:

  var c Circle

Seperti tipe data lainnya, ini akan membuat variabel Cirlce lokal yang secara default diset ke nol. Untuk struct nol berarti masing-masing bidang diatur ke nilai nol yang sesuai (0 untuk int, 0,0 untuk float, "" untuk string, nil untuk pointer, …) Kita juga dapat menggunakan fungsi new:

  c := new(Circle)

Ini mengalokasikan memori untuk semua bidang, menetapkan masing-masing ke nilai nol dan mengembalikan pointer. (*Circle) Lebih sering kita gunakan untuk memberi nilai pada setiap field. Kita bisa melakukan ini dengan dua cara. Seperti ini:

  c := Circle{x: 0, y: 0, r: 5}

Atau kita dapat mengabaikan nama field jika kita mengetahui urutannya:

  c := Circle{0, 0, 5}

Fields

Kita dapat mengakses field menggunakan . operator:

  fmt.Println(c.x, c.y, c.r)
  c.x = 10
  c.y = 5

Mari kita ubah fungsi circleArea sehingga menggunakan Circle:

func circleArea(c Circle) float64 {
     return math.Pi * c.r*c.r
}

lalu di dalam fungsi main kita berikan:

  c := Circle{0, 0, 5}
  fmt.Println(circleArea(c))

Satu hal yang perlu diingat adalah bahwa argumen selalu disalin di Go. Jika kita mencoba untuk mengubah salah satu field di dalam fungsi circleArea, itu tidak akan mengubah variabel asli. Karena itu, kita biasanya menulis fungsi seperti ini:

func circleArea(c *Circle) float64 {
     return math.Pi * c.r*c.r
}

Dan ubah pada fungsi main menjadi:

  c := Circle{0, 0, 5}
  fmt.Println(circleArea(&c))

Methods

Meskipun ini lebih baik daripada versi pertama dari kode ini, kita dapat mengubahnya dengan menggunakan jenis fungsi khusus yang dikenal sebagai method:

func (c *Circle) area() float64 {
     return math.Pi * c.r*c.r
}

Di antara kata kunci func dan nama fungsi, kita telah menambahkan receiver. receiver seperti parameter ia memiliki nama dan tipe tetapi dengan membuat fungsi dengan cara ini, kita dapat memanggil fungsi menggunakan . operator:

  fmt.Println(c.area())

Ini jauh lebih mudah dibaca, kita tidak lagi membutuhkan operator & (Go secara otomatis tahu untuk melewatkan pointer ke lingkaran untuk metode ini) dan karena fungsi ini hanya dapat digunakan dengan circle, kita dapat mengganti nama fungsi menjadi area saja.

Mari kita lakukan hal yang sama untuk persegi panjang:

type Rectangle struct {
     x1, y1, x2, y2 float64
}
func (r *Rectangle) area() float64 {
     l := distance(r.x1, r.y1, r.x1, r.y2)
     w := distance(r.x1, r.y1, r.x2, r.y1)
     return l * w
}

dan pada main kita memiliki:

r := Rectangle{0, 0, 10, 10}
fmt.Println(r.area())

Embedded Types

Field struct biasanya mewakili saling keterikatan. Misalnya Circle memiliki radius. Misalkan kita memiliki orang struct:

type Person struct {
    Name string
}
func (p *Person) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}

Dan kita ingin membuat struct Android baru. Kita bisa melakukan ini:

type Android struct {
    Person Person
    Model string
}

Go mendukung keterikatan struct seperti ini dengan menggunakan tipe yang disematkan. Juga dikenal sebagai bidang anonim, jenis yang disematkan terlihat seperti ini:

type Android struct {
    Person
    Model string
}

Kita menggunakan tipe (Person) dan tidak memberinya nama. Ketika didefinisikan dengan cara ini, struct Person dapat diakses menggunakan nama tipe:

  a := new(Android)
  a.Person.Talk()

Tetapi kita juga dapat memanggil metode Person apa pun secara langsung di Android:

a := new(Android)
a.Talk()

Interfaces

Baik dalam kehidupan nyata maupun dalam pemrograman, hubungan seperti ini adalah hal yang lumrah. Go memiliki cara untuk membuat kesamaan yang tidak disengaja ini menjadi eksplisit melalui tipe yang dikenal sebagai Interface. Berikut adalah contoh interface Shape:

type Shape interface {
  area() float64
}

Seperti sebuah struct, interface dibuat menggunakan type, diikuti dengan nama dan interface. Tapi untuk mendefinisikan Shape, kita membutuhkan "method set".Kumpulan method adalah daftar method yang harus dimiliki suatu tipe untuk “mengimplementasikan” interface.

func totalArea(shapes ...Shape) float64 {
	var area float64
	for _, s := range shapes {
		area += s.area()
	}
	return area
}

kita akan memanggil fungsi ini seperti ini:

  fmt.Println(totalArea(&c, &r))

Interface juga dapat digunakan sebagai fields:

type MultiShape struct {
     shapes []Shape
}

Kita bahkan dapat mengubah MultiShape menjadi Shape dengan memberinya metode area:

func (m *MultiShape) area() float64 {
	var area float64
	for _, s := range m.shapes {
		area += s.area()
	}
	return area
}

Sekarang MultiShape dapat berisi Circles, Rectangles atau bahkan MultiShapes lainnya.

Berikut ini program lebih lengkapnya

package main

import (
	"fmt"
	"math"
)

type Circle struct {
	x, y, r float64
}

func (c *Circle) area() float64 {
	return math.Pi * c.r * c.r
}

type Rectangle struct {
	l, w float64
}

func (r *Rectangle) area() float64 {
	return r.l * r.w
}

type Shape interface {
	area() float64
}

type MultiShape struct {
	shapes []Shape
}

func (m *MultiShape) area() float64 {
	var area float64
	for _, s := range m.shapes {
		area += s.area()
	}
	return area
}

func totalArea(shapes ...Shape) float64 {
	var area float64
	for _, s := range shapes {
		area += s.area()
	}
	return area
}

func main() {
	c := Circle{0, 0, 5}
	r := Rectangle{3, 4}
	fmt.Println(totalArea(&c, &r))

	m := MultiShape{}
	m.shapes = append(m.shapes, &c, &r)
	fmt.Println(m.area())
}
comments powered by Disqus