30. Merancang tipe kecil — Solusi PR

File solusi .go ada di exercises/30/homework/solutions/. Tantangannya berupa modul, jadi masuk ke foldernya untuk menjalankannya:

cd exercises/30/homework/solutions/04-stack
go run .

Soal 1 — Penghitung

Soal. counter dengan increment, get, dan reset.

Cara berpikir. Dia mengetahui sebuah hitungan. Dia bisa menambah, melaporkan, dan mengatur ulang. Aturannya adalah hitungan hanya berubah lewat metode-metode itu. Dua di antaranya mengubah hitungan, jadi ketiganya memakai receiver pointer. Nilai nolnya sudah dimulai dari 0, jadi counter{} siap dipakai, dan newCounter tidak diperlukan. File ini ada di package main, jadi Go tidak menegakkan aturannya. Hanya package terpisah yang bisa.

Solusi yang dikerjakan.

package main

import "fmt"

type counter struct {
    count int
}

func (c *counter) increment() {
    c.count = c.count + 1
}

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

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

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

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

Soal 2 — Kesehatan yang dibatasi

Soal. Kesehatan yang tetap di antara 0 dan maxHealth.

Cara berpikir. Tegakkan aturannya di dalam heal dan damage, supaya tidak ada pemanggil yang bisa mendorong kesehatan keluar dari rentang. n yang negatif butuh pemeriksaan sendiri. Tanpa itu, heal(-1000) lolos dari pemeriksaan batas atas dan mendorong kesehatan di bawah 0. maxHealth yang buruk ditolak di newHealth, jadi tidak ada nilai health yang dimulai dalam keadaan salah. Fungsi kecil check menghemat pengulangan if yang sama untuk setiap pemanggilan di main.

Solusi yang dikerjakan.

package main

import "fmt"

type health struct {
    current   int
    maxHealth int
}

func newHealth(maxHealth int) (*health, error) {
    if maxHealth <= 0 {
        return nil, fmt.Errorf("the maximum %d is not above 0", maxHealth)
    }
    return &health{current: maxHealth, maxHealth: maxHealth}, nil
}

func (h *health) heal(n int) error {
    if n < 0 {
        return fmt.Errorf("cannot heal by %d", n)
    }
    h.current = h.current + n
    if h.current > h.maxHealth {
        h.current = h.maxHealth
    }
    return nil
}

func (h *health) damage(n int) error {
    if n < 0 {
        return fmt.Errorf("cannot damage by %d", n)
    }
    h.current = h.current - n
    if h.current < 0 {
        h.current = 0
    }
    return nil
}

func (h *health) get() int {
    return h.current
}

// check prints an error, if there is one.
func check(err error) {
    if err != nil {
        fmt.Println("Error:", err)
    }
}

func main() {
    _, err := newHealth(0)
    fmt.Println(err) // the maximum 0 is not above 0

    h, err := newHealth(100)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }

    check(h.damage(30))
    fmt.Println(h.get()) // 70

    check(h.heal(1000))
    fmt.Println(h.get()) // 100

    check(h.damage(9999))
    fmt.Println(h.get()) // 0

    check(h.heal(-5))    // Error: cannot heal by -5
    fmt.Println(h.get()) // 0
}

Kesalahan umum.

  • Menamai parameter max. Kode ini bisa dikompilasi, tetapi menutupi fungsi bawaan Go max di dalam fungsi itu. Nama yang lebih panjang lebih jelas.

Soal 3 — Saklar lampu

Soal. Saklar dengan toggle dan isOn.

Cara berpikir. Dia mengetahui satu bool. Nilai nol bool adalah false, jadi lightSwitch{} dimulai dalam keadaan mati tanpa kerja tambahan.

Solusi yang dikerjakan.

package main

import "fmt"

type lightSwitch struct {
    on bool
}

func (s *lightSwitch) toggle() {
    s.on = !s.on
}

func (s *lightSwitch) isOn() bool {
    return s.on
}

func main() {
    s := lightSwitch{}
    fmt.Println(s.isOn()) // false

    s.toggle()
    fmt.Println(s.isOn()) // true

    s.toggle()
    fmt.Println(s.isOn()) // false
}

s.on = !s.on membalik bool pada setiap pemanggilan, dan itulah seluruh saklar dalam satu baris.

Kesalahan umum.

  • Menulis type switch struct. Go berhenti dengan syntax error: unexpected switch, expected name.
  • Memakai receiver nilai pada toggle. Yang dibalik adalah salinan, dan saklarnya tetap mati.

Tantangan — Stack dengan penjaga

Soal. Pop() pada stack kosong mengembalikan error, bukan crash.

Cara berpikir. Tanpa penjaga, membaca puncak slice kosong membuat crash, seperti yang kamu lihat di Bab 26. Penjaganya memeriksa panjang lebih dulu, dan itulah aturan yang dilindungi metode ini. items berhuruf kecil, jadi main tidak bisa menyentuh slice-nya. Pop punya item atau masalah untuk dilaporkan, jadi metode ini mengembalikan (string, error), dan "" mengisi tempat item saat gagal.

Solusi yang dikerjakan. stack/stack.go:

package stack

import "errors"

// Stack is a stack of strings. Make one with New.
type Stack struct {
    items []string
}

// New returns an empty stack.
func New() *Stack {
    return &Stack{}
}

// Push adds v on top.
func (s *Stack) Push(v string) {
    s.items = append(s.items, v)
}

// Pop removes and returns the top item, or returns an error if the stack is
// empty.
func (s *Stack) Pop() (string, error) {
    if len(s.items) == 0 {
        return "", errors.New("the stack is empty")
    }
    last := len(s.items) - 1
    top := s.items[last]
    s.items = s.items[:last]
    return top, nil
}

// Size returns the number of items.
func (s *Stack) Size() int {
    return len(s.items)
}

main.go:

package main

import (
    "fmt"

    "stackmodule/stack"
)

func main() {
    s := stack.New()
    s.Push("a")
    s.Push("b")
    fmt.Println(s.Size())

    for i := 0; i < 3; i++ {
        top, err := s.Pop()
        if err != nil {
            fmt.Println("Error:", err)
            continue
        }
        fmt.Println(top)
    }

    fmt.Println(s.Size())
}

Output:

2
b
a
Error: the stack is empty
0

Kesalahan umum.

  • Memakai receiver nilai pada Push. Hasil append hilang ketika metode berakhir, dan stack tetap kosong.
  • Menamai field-nya Items. Field itu lalu diekspor, dan main bisa mengubah slice-nya langsung, melewati penjaganya.

Selesai?

Itulah akhir bab-bab Bagian 6. Kamu bisa membuat struct, membuatnya banyak, membagi kode dengan embedding, memperlakukan tipe yang berbeda dengan cara yang sama lewat interface, dan merancang permukaan publik yang aman. Bagian 6 punya dua mini-proyek, Inventaris dan Pertempuran Monster, keduanya dibangun dari nilai-nilai yang bekerja sama.