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 Gomaxdi 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 dengansyntax 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. Hasilappendhilang ketika metode berakhir, dan stack tetap kosong. - Menamai field-nya
Items. Field itu lalu diekspor, danmainbisa 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.