解答 01.2 — カプセル化(Restock)¶
このノートブックは 01.2-encapsulation.ipynb の練習問題の解答です。
まず問題の前提コードを再掲し、次に解答、最後にチェックを実行します。
先に自分の力で解いてから、答え合わせに使ってください。
前提コード(問題ノートブックと同じ定義)¶
In [1]:
import (
"errors"
"fmt"
)
In [2]:
var priceOf = map[string]int{"cola": 120, "tea": 100, "water": 80}
type VendingMachine struct {
inserted int
stock map[string]int
}
func NewVendingMachine() *VendingMachine {
return &VendingMachine{
stock: map[string]int{"cola": 5, "tea": 3, "water": 0},
}
}
func (v *VendingMachine) InsertCoin(amount int) error {
if amount <= 0 {
return errors.New("coin must be positive")
}
v.inserted += amount
return nil
}
func (v *VendingMachine) Inserted() int {
return v.inserted
}
func (v *VendingMachine) Stock(item string) int {
return v.stock[item]
}
func (v *VendingMachine) Buy(item string) (string, error) {
price, ok := priceOf[item]
if !ok {
return "", fmt.Errorf("unknown item: %s", item)
}
if v.stock[item] <= 0 {
return "", fmt.Errorf("sold out: %s", item)
}
if v.inserted < price {
return "", fmt.Errorf("insufficient coins: inserted=%d, need=%d", v.inserted, price)
}
v.inserted -= price
v.stock[item]--
return item, nil
}
func (v *VendingMachine) Refund() int {
amount := v.inserted
v.inserted = 0
return amount
}
解答: Restock¶
「商品の存在チェック」「正の数チェック」を先に行い、最後にだけ在庫を増やします。
エラー時は状態を変えない(v.stock[item] += n をエラー後に書かない)ことがポイントです。
In [3]:
func (v *VendingMachine) Restock(item string, n int) error {
if _, ok := priceOf[item]; !ok {
return fmt.Errorf("unknown item: %s", item)
}
if n <= 0 {
return fmt.Errorf("restock amount must be positive: %d", n)
}
v.stock[item] += n
return nil
}
チェック(問題ノートブックと同じ期待値)¶
In [4]:
import "reflect"
import "fmt"
func mustEqual(got, want any, name string) {
if reflect.DeepEqual(got, want) {
fmt.Printf("✅ Passed: %s\n", name)
return
}
panic(fmt.Sprintf("❌ %s\n got = %v (%T)\n want = %v (%T)", name, got, got, want, want))
}
In [5]:
%%
m := NewVendingMachine()
err1 := m.Restock("water", 5)
mustEqual(err1, nil, "Restock(water, 5) はエラーなし")
mustEqual(m.Stock("water"), 5, "water の在庫が 0 → 5")
err2 := m.Restock("pizza", 1)
mustEqual(err2 != nil, true, "存在しない商品はエラー")
err3 := m.Restock("cola", 0)
mustEqual(err3 != nil, true, "0 以下の補充はエラー")
err4 := m.Restock("cola", -3)
mustEqual(err4 != nil, true, "負の補充はエラー")
println("🎉 すべてのチェックが通りました")
✅ Passed: Restock(water, 5) はエラーなし ✅ Passed: water の在庫が 0 → 5 ✅ Passed: 存在しない商品はエラー ✅ Passed: 0 以下の補充はエラー ✅ Passed: 負の補充はエラー
🎉 すべてのチェックが通りました
解説¶
_, ok := priceOf[item]— 商品が存在するか判定します(okが false ならエラー)。priceOfはBuyと同じ辞書を使うので、「存在する商品」の定義が 1 箇所に保たれますn <= 0のガード — 0 本やマイナス本の補充を弾きますv.stock[item] += n— すべてのチェックを通った後にだけ実行します
この「不正は先に弾く → 成功操作は最後に」の順序は、カプセル化されたメソッドの定石です。 エラーを返す分岐の途中で状態を変えてしまうと、失敗なのに状態が変わったというバグになります。