Menu
CoddyTech

Aturan Pemenuhan Interface

Termasuk bagian Pemrograman Berorientasi Objek dalam perjalanan GO di Coddy. Pelajaran 43 dari 107.

Meskipun Go menggunakan duck typing untuk pemenuhan interface secara implisit, ada aturan khusus yang menentukan apakah suatu tipe benar-benar memenuhi interface. Memahami aturan ini membantumu menghindari bug yang sulit diketahui.

Aturan terpenting berkaitan dengan pointer receiver. Jika sebuah method didefinisikan dengan pointer receiver, hanya pointer ke tipe tersebut yang memenuhi interface, bukan nilainya sendiri:

type Saver interface {
    Save() string
}

type Document struct{ Name string }

func (d *Document) Save() string {  // receiver pointer
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Karena Save() memiliki receiver pointer, hanya *Document yang memenuhi Saver:

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // berfungsi, pointer memenuhi interface
    // Process(doc)  // error saat compile, nilai tidak memenuhi interface
}

Namun, kebalikannya lebih fleksibel. Jika sebuah method memiliki penerima nilai, tipe nilai dan pointer keduanya memenuhi interface. Go secara otomatis melakukan dereferensi pointer saat memanggil method dengan penerima nilai:

func (d Document) Info() string {  // penerima nilai
    return d.Name
}

// Document dan *Document sama-sama memenuhi interface yang mensyaratkan Info()

Asimetri ini terjadi karena Go selalu bisa mendapatkan nilai dari pointer (dengan melakukan dereferensi), tetapi Go tidak selalu bisa mendapatkan pointer dari suatu nilai (nilai tersebut mungkin tidak dapat diambil alamatnya). Dengan mengingat aturan ini, kamu bisa mencegah error compiler yang membingungkan saat bekerja dengan interface.

challenge icon

Tantangan

Mudah

Mari kita buat sistem konfigurasi yang menunjukkan pengaruh receiver pointer dan receiver nilai terhadap pemenuhan interface. Kamu akan membuat tipe yang pilihan receivernya menentukan apakah nilai, pointer, atau keduanya dapat digunakan dengan sebuah interface.

Kamu akan menyusun kodemu dalam tiga file:

  • config.go: Definisikan interface Configurable yang memerlukan dua method: GetValue() string dan SetValue(string). Lalu buat dua tipe konfigurasi:
    • ReadOnlyConfig dengan field Value: gunakan receiver nilai untuk GetValue() (mengembalikan Value) dan receiver pointer untuk SetValue() (memperbarui Value)
    • Setting dengan field Data: gunakan receiver nilai untuk kedua method (GetValue mengembalikan Data, SetValue hanya mencetak "Cannot modify" tanpa mengubah apa pun)
  • processor.go: Buat fungsi bernama ProcessConfig yang menerima Configurable dan string nilai baru. Fungsi ini harus mencetak nilai saat ini menggunakan GetValue(), memanggil SetValue() dengan nilai baru, lalu mencetak nilai itu lagi untuk menunjukkan perubahan yang terjadi.
  • main.go: Baca detail konfigurasi dari input dan tunjukkan aturan pemenuhan interface:
    1. Buat ReadOnlyConfig dan berikan pointer ke ProcessConfig (diperlukan karena SetValue memiliki receiver pointer)
    2. Buat Setting dan berikan nilainya secara langsung ke ProcessConfig (berfungsi karena kedua method memiliki receiver nilai)

Input berikut akan diberikan:

  • Baris 1: Nilai awal untuk ReadOnlyConfig
  • Baris 2: Nilai baru untuk ReadOnlyConfig
  • Baris 3: Nilai awal untuk Setting
  • Baris 4: Nilai baru yang akan dicoba untuk Setting

Fungsi ProcessConfig harus mencetak dalam format ini:

Current: [value]
Current: [value after SetValue]

Contohnya, jika diberikan debug, production, localhost, dan remote, outputmu seharusnya:

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Perhatikan bahwa ReadOnlyConfig benar-benar mengubah nilainya (karena kita memberikan pointer), sedangkan Setting tidak berubah (method SetValue-nya dengan receiver nilai tidak dapat mengubah nilai aslinya). Inti pentingnya adalah nilai ReadOnlyConfig saja tidak memenuhi Configurable, hanya pointer yang memenuhinya, sedangkan nilai Setting dapat digunakan secara langsung karena semua method-nya menggunakan receiver nilai.

Coba sendiri

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Baca input
	scanner.Scan()
	rocInitial := scanner.Text()
	scanner.Scan()
	rocNew := scanner.Text()
	scanner.Scan()
	settingInitial := scanner.Text()
	scanner.Scan()
	settingNew := scanner.Text()
	
	// TODO: Buat ReadOnlyConfig dengan nilai rocInitial
	// TODO: Teruskan POINTER ke ProcessConfig (diperlukan karena SetValue memiliki pointer receiver)
	
	// TODO: Buat Setting dengan nilai settingInitial
	// TODO: Teruskan VALUE langsung ke ProcessConfig (bisa karena kedua method memiliki value receiver)
	
	// Gunakan variabel-variabel ini agar tidak muncul error variabel yang tidak digunakan
	_ = rocInitial
	_ = rocNew
	_ = settingInitial
	_ = settingNew
}
quiz iconUji dirimu

Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.

Semua pelajaran di Pemrograman Berorientasi Objek

Latihan sendiri: Compiler Go online