Go tidak punya kata kunci constructor seperti __construct pada PHP atau method khusus __init__ pada Python. Zero value struct yang kita bahas sebelumnya memang sering cukup, tetapi domain tertentu menuntut aturan: akun tanpa nama atau saldo negatif tidak boleh tercipta. Untuk kebutuhan itu, komunitas Go memakai constructor pattern — fungsi biasa bernama NewX yang membangun nilai struct sekaligus memvalidasi masukannya. Pada chapter ini kita bangun constructor NewAkun lengkap dengan validasi dan penanganan error-nya.

Pola NewX #

Constructor Go hanyalah fungsi dengan konvensi nama: diawali New diikuti nama tipe. Bentuk yang paling umum mengembalikan pasangan (Akun, error) — constructor mengembalikan nilai zero value Akun{} bersama nil saat berhasil, atau Akun{} bersama error saat validasi gagal. Karena berupa fungsi biasa, constructor juga bisa menerima parameter apa pun, memanggil fungsi lain, dan diberi documentation comment seperti fungsi lainnya.

Validasi diletakkan di baris-baris awal sebelum pembentukan nilai. Strategi ini disebut fail fast: kesalahan terdeteksi di pintu masuk, bukan menyebar sebagai struct cacat ke seluruh program. Pemanggil wajib memeriksa error sebelum memakai struct hasilnya — pola pengecekan if err != nil yang diperdalam pada section Error Handling .

Program Utuh #

Berkas constructor-demo.go memuat struct Akun dengan field privat beserta constructor-nya:

go
package main

import (
	"errors"
	"fmt"
	"strings"
)

type Akun struct {
	nama  string
	saldo int
	aktif bool
}

func NewAkun(nama string, saldoAwal int) (Akun, error) {
	if strings.TrimSpace(nama) == "" {
		return Akun{}, errors.New("nama tidak boleh kosong")
	}
	if saldoAwal < 0 {
		return Akun{}, fmt.Errorf("saldo awal %d tidak boleh negatif", saldoAwal)
	}
	return Akun{nama: nama, saldo: saldoAwal, aktif: true}, nil
}

func (a Akun) Ringkasan() string {
	return fmt.Sprintf("Akun %q (aktif: %v) saldo %d", a.nama, a.aktif, a.saldo)
}

func main() {
	a, err := NewAkun("Budi", 500000)
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Println(a.Ringkasan())

	_, err = NewAkun("   ", 100)
	fmt.Println("galat:", err)

	_, err = NewAkun("Siti", -1)
	fmt.Println("galat:", err)
}

Jalankan program dari direktori chapter:

bash
go run constructor-demo.go

Output yang Dihasilkan:

text
Akun "Budi" (aktif: true) saldo 500000
galat: nama tidak boleh kosong
galat: saldo awal -1 tidak boleh negatif

Mari kita telaah baris demi baris program di atas:

  1. Baris 9–13 (type Akun struct): mendeklarasikan struct dengan field kecil semuanya (nama, saldo, aktif) — huruf kecil membuat field tidak bisa diisi dari package lain.
  2. Baris 15–23 (func NewAkun): constructor dengan tanda tangan (Akun, error); seluruh validasi selesai sebelum baris pengembalian nilai sukses.
  3. Baris 16–18 (validasi nama): menolak nama kosong dengan errors.New — cukup untuk pesan statis; pembuatan error dibahas pada chapter Membuat Error .
  4. Baris 19–21 (validasi saldo): menolak saldo negatif dengan fmt.Errorf karena pesannya memuat nilai saldoAwal lewat verb %d.
  5. Baris 22 (nilai sukses): membangun Akun dengan field privat terisi penuh dan mengembalikan nil sebagai error — status aktif otomatis true hanya lewat jalur ini.
  6. Baris 30–35 (pemanggilan sukses): memanggil NewAkun untuk Budi, memeriksa err lebih dulu, lalu mencetak ringkasan akun.
  7. Baris 37–41 (pemanggilan gagal): memanggil constructor dengan masukan salah; identifier kosong _ membuang struct yang tidak terpakai dan error-nya dicetak sebagai bukti validasi bekerja.

Field Privat sebagai Penjaga Aturan #

Field kecil membuat struct hanya bisa dibangun lewat jalur yang kita sediakan — dari package lain, Akun{nama: "Budi"} tidak dikompilasi karena nama tidak terlihat. Constructor berada di package yang sama dengan struct sehingga berhak mengisi field privat. Contoh kita memakai satu berkas package main, jadi aturan visibilitas belum terasa; begitu struct dipindah ke package terpisah, constructor menjadi satu-satunya pintu masuk yang sah. Pola Ringkasan di baris 25–27 memperkuat hal yang sama: pembacaan field tetap tersedia lewat method, penulisan hanya lewat constructor.

Alternatif: Zero Value yang Siap Pakai #

Constructor bukan satu-satunya cara memulai struct. Standard library kerap merancang struct yang zero value-nya langsung berguna lalu membiarkan pengguna mengonfigurasi field publik yang perlu:

go
srv := &http.Server{Addr: ":8080"}

http.Server tanpa konfigurasi tetap sah dengan nilai bawaan; field Addr diisi lewat struct literal karena memang dimaksudkan publik. Pilih pendekatan sesuai kebutuhan: zero value saat nilai bawaan sudah benar, constructor saat aturan validasi harus ditegakkan. Bentuk keduanya dirangkum tabel berikut:

BentukKapan dipakai
NewT() Ttanpa kemungkinan gagal
NewT() (T, error)ada validasi atau kegagalan eksternal
NewT() *Thasil dibagikan sebagai satu pointer
zero value + field publiknilai bawaan sudah benar

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

constructor-demo.go

Unduh berkas
go
package main

import (
	"errors"
	"fmt"
	"strings"
)

type Akun struct {
	nama  string
	saldo int
	aktif bool
}

func NewAkun(nama string, saldoAwal int) (Akun, error) {
	if strings.TrimSpace(nama) == "" {
		return Akun{}, errors.New("nama tidak boleh kosong")
	}
	if saldoAwal < 0 {
		return Akun{}, fmt.Errorf("saldo awal %d tidak boleh negatif", saldoAwal)
	}
	return Akun{nama: nama, saldo: saldoAwal, aktif: true}, nil
}

func (a Akun) Ringkasan() string {
	return fmt.Sprintf("Akun %q (aktif: %v) saldo %d", a.nama, a.aktif, a.saldo)
}

func main() {
	a, err := NewAkun("Budi", 500000)
	if err != nil {
		fmt.Println("galat:", err)
		return
	}
	fmt.Println(a.Ringkasan())

	_, err = NewAkun("   ", 100)
	fmt.Println("galat:", err)

	_, err = NewAkun("Siti", -1)
	fmt.Println("galat:", err)
}