26. Struct dan metode — Solusi PR

File solusi .go ada di exercises/26/homework/solutions/.

Soal 1 — Penghitung

Soal. Struct counter dengan metode inc, get, dan reset.

Cara berpikir. Struct menyimpan keadaannya di count. Receiver nilai mendapat salinan, jadi inc dan reset mengubah salinannya lalu mengembalikannya. get hanya membaca, jadi metode ini mengembalikan int.

Solusi yang dikerjakan.

package main

import "fmt"

type counter struct {
    count int
}

func (c counter) inc() counter {
    c.count = c.count + 1
    return c
}

func (c counter) get() int {
    return c.count
}

func (c counter) reset() counter {
    c.count = 0
    return c
}

func main() {
    c := counter{}
    fmt.Println(c.get()) // 0

    c = c.inc()
    fmt.Println(c.get()) // 1

    c = c.inc()
    c = c.inc()
    fmt.Println(c.get()) // 3

    c = c.reset()
    fmt.Println(c.get()) // 0
}

Kesalahan umum.

  • Menulis c.inc() di satu baris tersendiri. Kode ini bisa dikompilasi, dan c tidak berubah. Tugaskan hasilnya: c = c.inc().
  • Tidak menulis receiver. Go lalu berhenti dengan c.inc undefined (type counter has no field or method inc).
  • Menulis c.get tanpa tanda kurung di dalam fmt.Println. Kode ini bisa dikompilasi, dan go vet memberi peringatan, tetapi yang tercetak adalah sebuah alamat, bukan hitungannya.

Soal 2 — Anjing yang menggonggong

Soal. Dua nilai dog dengan namanya sendiri, masing-masing memanggil bark()-nya.

Cara berpikir. Setiap literal membuat nilai terpisah dengan name-nya sendiri. Kode metode yang sama berjalan untuk keduanya, dan receiver-nya anjing yang berbeda setiap kali. bark tidak mengubah apa pun, jadi metode ini tidak mengembalikan apa-apa.

Solusi yang dikerjakan.

package main

import "fmt"

type dog struct {
    name string
}

func (d dog) bark() {
    fmt.Printf("%s: Woof!\n", d.name)
}

func main() {
    rex := dog{name: "Rex"}
    lassie := dog{name: "Lassie"}

    rex.bark()    // Rex: Woof!
    lassie.bark() // Lassie: Woof!
}

Kesalahan umum.

  • Memakai variabel name di luar fungsi, bukan d.name. Kedua anjing lalu mencetak nama yang sama.

Soal 3 — Kalkulator

Soal. Struct calc dengan metode aritmetika yang memperbarui sebuah nilai.

Cara berpikir. Ketiga metode aritmetika punya bentuk yang sama: ubah salinannya, lalu kembalikan. Nama total membuat variabelnya tidak tampak seperti tipenya.

Solusi yang dikerjakan.

package main

import "fmt"

type calc struct {
    value int
}

func (c calc) add(n int) calc {
    c.value = c.value + n
    return c
}

func (c calc) sub(n int) calc {
    c.value = c.value - n
    return c
}

func (c calc) mul(n int) calc {
    c.value = c.value * n
    return c
}

func (c calc) show() {
    fmt.Printf("Value: %d\n", c.value)
}

func main() {
    total := calc{}

    total = total.add(10)
    total.show() // Value: 10

    total = total.mul(3)
    total.show() // Value: 30

    total = total.sub(5)
    total.show() // Value: 25
}

Setiap metode mengembalikan calc, jadi pemanggilannya juga bisa berurutan dalam satu baris: total = total.add(10).mul(3) memberikan 30.

Kesalahan umum.

  • Lupa menulis return c pada metode yang menjanjikan calc. Go berhenti dengan missing return.
  • Menamai variabelnya calc juga. Variabel itu lalu menutupi tipenya sampai akhir fungsi, jadi calc{} berikutnya berhenti dengan calc (variable of type calc) is not a type.

Tantangan — Stack

Soal. Stack berisi string dengan push, pop, peek, dan size.

Cara berpikir. Puncaknya adalah ujung slice. append menambah di sana, dan s.items[:last] membuangnya. pop punya dua hal untuk dikembalikan, stack yang lebih pendek dan item-nya, jadi metode ini mengembalikan dua nilai, seperti yang ditunjukkan Bab 21.

Solusi yang dikerjakan.

package main

import "fmt"

type stack struct {
    items []string
}

func (s stack) push(v string) stack {
    s.items = append(s.items, v)
    return s
}

func (s stack) pop() (stack, string) {
    last := len(s.items) - 1
    top := s.items[last]
    s.items = s.items[:last]
    return s, top
}

func (s stack) peek() (string, bool) {
    if len(s.items) == 0 {
        return "", false
    }
    return s.items[len(s.items)-1], true
}

func (s stack) size() int {
    return len(s.items)
}

func main() {
    s := stack{}
    s = s.push("a")
    s = s.push("b")
    s = s.push("c")

    fmt.Println(s.size()) // 3

    top, ok := s.peek()
    fmt.Println(top, ok) // c true

    s, top = s.pop()
    fmt.Println(top) // c

    s, top = s.pop()
    fmt.Println(top) // b

    fmt.Println(s.size()) // 1
}

Output:

3
c true
c
b
1

Kesalahan umum.

  • Tidak menjaga peek. Pada stack kosong, s.items[len(s.items)-1] crash dengan panic: runtime error: index out of range [-1].
  • Memanggil pop ini pada stack kosong, yang crash dengan cara yang sama. Bab 30 merancang stack yang menolak dengan aman.
  • Mengambil item dari depan dengan s.items[0]. Itu membuat antrean, yaitu item pertama yang masuk menjadi yang pertama keluar.

Selesai?

Kamu bisa mengelompokkan data dalam struct dan memberinya metode. Tetapi menugaskan kembali setelah setiap perubahan itu merepotkan. Bab berikutnya, Pointer, memungkinkan metode mengubah aslinya.