Open22
実用 Go言語 読書メモ

1章 「Goらしさ」に触れる

2章 定義型

3章 構造体

3.3.3 インスタンスからメソッドを取り出して関数型として使う
構造体のインスタンスのメソッドは単体の関数と同じように利用できるが、レシーバーを経由して独自の名前空間にアクセスできる。
main.go
package main
type Struct struct {
additionalField string
}
func (s *Struct) Method(arg string) {
println(arg)
println(s.additionalField)
}
func Func(arg string) {
println(arg)
}
func HigherOrderFunc(f func(string), arg string) {
f(arg)
}
func main() {
var s Struct = Struct{additionalField: "additionalField"}
HigherOrderFunc(s.Method, "method")
HigherOrderFunc(Func, "function")
}
$ go run main.go
method
additionalField
function
s.Method
と Func
は共に同じ型の引数と返り値を持つ関数であるが、s.Method
の方が構造体のフィールドにアクセスできる分より多くの情報を持つ。

3.3.4 クロージャを使ってメソッドを再現する
上と同じことはクロージャを使っても実現できる。
main.go
package main
func Enclosure(additionalField string) func(arg string) {
return func(arg string) {
println(arg)
println(additionalField)
}
}
func Func(arg string) {
println(arg)
}
func HigherOrderFunc(f func(string), arg string) {
f(arg)
}
func main() {
closure := Enclosure("additionalField")
HigherOrderFunc(closure, "method?")
HigherOrderFunc(Func, "function")
}
$ go run main.go
method?
additionalField
function

3.9.2 構造体の埋め込みは継承ではない
Python Pop Quiz 🐍❓
main.py
class Parent:
def m1(self):
self.m2()
def m2(self):
print("Parent")
class Child(Parent):
def m2(self):
print("Child")
c = Child()
c.m1()
c.m2()
答え
Child
は Parent
から c1
を継承するが、Child.c1
実行時、self
引数は Child
インスタンスを参照する。
$ python3 main.py
Child
Child
一方で、埋め込まれた構造体はそれ自身で閉じているため、オーバーライドはできない。
main.go
package main
type Parent struct {
}
func (p Parent) m1() {
p.m2()
}
func (p Parent) m2() {
println("Parent")
}
type Child struct {
Parent
}
func (c Child) m2() {
println("Child")
}
func main() {
c := Child{}
c.m1()
c.m2()
}
$ go run main.go
Parent
Child

3.9.3 テンプレートメソッドパターンではなく、ストラテジーパターン
3.9.2 で見た通り、テンプレートメソッドパターンはそのままでは利用できない。
main.go
package main
type Character struct {
x int
y int
moveImpl func(dx int, dy int)
}
func (c *Character) move(dx int, dy int) {
println("各キャラクターにおける移動前の共通処理")
c.moveImpl(dx, dy)
println("各キャラクターにおける移動後の共通処理")
}
type Warrior struct {
Character
}
func (w *Warrior) moveImpl(dx int, dy int) {
w.x += dx * 2
w.y += dy * 2
}
type Mage struct {
Character
}
func (m *Mage) moveImpl(dx int, dy int) {
m.x += dx
m.y += dy
}
func main() {
w := Warrior{}
m := Mage{}
w.move(1, 1) // panic!
m.move(1, 1) // panic!
}
w.move()
や m.move()
の呼び出しは、(&Warrior).moveImpl
や (&Mage)moveImpl
ではなく、初期化されていない (&Character).moveImpl
を参照するため、パニックを起こす。
$ go run main.go
各キャラクターにおける移動前の共通処理
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x1004de658]
...
構造体の初期化時に関数を与えることによって、振る舞いを部分的にカスタマイズすることができる。
(これはデリゲートパターン??)
main.go
package main
import "fmt"
type Character struct {
x int
y int
moveImpl func(c *Character, dx int, dy int)
}
func (c *Character) move(dx int, dy int) {
fmt.Println("各キャラクターにおける移動前の共通処理")
c.moveImpl(c, dx, dy)
fmt.Println("各キャラクターにおける移動後の共通処理")
}
func warriorMoveImpl(c *Character, dx int, dy int) {
c.x += dx * 2
c.y += dy * 2
}
func mageMoveImpl(c *Character, dx int, dy int) {
c.x += dx
c.y += dy
}
func main() {
w := Character{moveImpl: warriorMoveImpl}
m := Character{moveImpl: mageMoveImpl}
w.move(1, 1)
m.move(1, 1)
println(w.x, w.y)
println(m.x, m.y)
}
$ go run main.go
各キャラクターにおける移動前の共通処理
各キャラクターにおける移動後の共通処理
各キャラクターにおける移動前の共通処理
各キャラクターにおける移動後の共通処理
2 2
1 1

4章 インタフェース

4.5 実装を切り替えるためのさまざまな方法
実感が湧かなくて何を言ってるかわからない部分が多い。実際に動かして考えてみることにする。

4.5.1 基本の分岐
main.go
//go:build !darwin
// +build !darwin
package main
func main() {
println("Hello, World! (not darwin)")
}
main_darwin.go
package main
func main() {
println("Hello, World! (darwin)")
}
go.mod
module test
go 1.23.6
$ go build .
$ ./test
Hello, World! (darwin)

5章 エラーハンドリング

6章 パッケージ、モジュール

7章 Goプログラミングの環境を整備する

8章 さまざまなデータフォーマット

9章 Goとリレーショナルデータベース

10章 HTTPサーバー

11章 HTTPクライアント

12章 ログとオブザーバビリティ

13章 テスト

14章 クラウドとGo

15章 クラウドのストレージ
