# GoF Patterns in Go

23 个 runnable 的最小 implementation，按 Creational 5 / Structural 7 / Behavioral 11 完整覆盖。Go 1.23+，只使用 standard library。

这些例子用于学习 pattern invariant，不是 production framework。除特别说明支持 concurrency 的 Singleton/Flyweight/Observer 外，mutable example 按 single-owner 使用；interface dependency 由 caller 提供有效的 non-nil implementation。

## 01 · Factory Method — creational

把创建 product 的决定交给可替换的 creator。

### Problem

发送流程相同，但不同 deployment 需要 Email 或 SMS。让 workflow 直接 new 具体 sender，会把 product choice 固定在流程中。

### Design

Deliver 只认识 Creator 与 Sender。先通过 NewSender 创建 product，再 call Send；切换 Creator 就切换 product，workflow 不动。经典 GoF 用 subclass override factory method；这里用 implicit interface 和 composition 表达同一 variation point。

### Walkthrough

先读 Creator.NewSender，再读 Deliver 的两次 call，最后比较 EmailCreator 与 SMSCreator。test 把两种 creator 放在同一个 table，证明 caller 只 dependency 共同 contract。

### Boundary

一个 NewX function 并不自动成为 GoF Factory Method。这里真正可替换的是 creation operation；如果 caller 已经知道唯一的 concrete type，直接 constructor 更简单。

### Role map

| GoF role | Go implementation |
|---|---|
| Creator | `Creator` |
| Factory operation | `NewSender` |
| Product | `Sender` |
| Concrete products | `email / sms` |

### Minimal Go

```go
package factorymethod

type Sender interface{ Send(string) string }
type Creator interface{ NewSender() Sender }

// Deliver depends on a creation operation, not a concrete product.
func Deliver(c Creator, message string) string { return c.NewSender().Send(message) }

type EmailCreator struct{}

func (EmailCreator) NewSender() Sender { return email{} }

type SMSCreator struct{}

func (SMSCreator) NewSender() Sender { return sms{} }

type email struct{}

func (email) Send(s string) string { return "email:" + s }

type sms struct{}

func (sms) Send(s string) string { return "sms:" + s }
```

### Test

```go
package factorymethod

import "testing"

func TestProductsAreSubstitutable(t *testing.T) {
	for _, tc := range []struct {
		creator Creator
		want    string
	}{{EmailCreator{}, "email:hi"}, {SMSCreator{}, "sms:hi"}} {
		if got := Deliver(tc.creator, "hi"); got != tc.want {
			t.Fatal(got)
		}
	}
}
```

```sh
go test -v ./factory-method
```

### OSS · Go adaptation

gRPC 的 balancer.Builder.Build 返回 Balancer，baseBuilder.Build 创建 baseBalancer。这是 function/method-level 的 creation extension point。名字叫 Builder，但这里的角色更接近 Factory Method，不是逐步 assembly 的 Builder。

- [grpc/grpc-go / balancer/balancer.go: L221–L228](https://github.com/grpc/grpc-go/blob/298389d61dd6f3071da1fa34d2fdf3f726d4aa36/balancer/balancer.go#L221-L228)
- [grpc/grpc-go / balancer/base/balancer.go: L35–L61](https://github.com/grpc/grpc-go/blob/298389d61dd6f3071da1fa34d2fdf3f726d4aa36/balancer/base/balancer.go#L35-L61)

### Practice

新增 PushCreator，保持 Deliver 不变。然后删掉 Creator interface，改成 func() Sender，比较两种 API。

一个 method 且无需 metadata 时，function injection 更小；creator 还需要 Name、configuration 或多个操作时，interface 更自然。两者都要保留可替换的创建行为。

## 02 · Abstract Factory — creational

一次替换一整组相关 product。

### Problem

Light 与 Dark 主题各需要相容的 Button 和 Checkbox。分别传入两个 constructor，容易不小心混搭。

### Design

Factory 同时提供 Button 与 Checkbox；Screen 从同一个 factory 获取整组 product。Factory Method 关注一次 creation extension point，Abstract Factory 关注多个 product 之间的一致 family。

### Walkthrough

看 Screen 如何只接收一个 Factory。test 一次换掉整组 Light/Dark，而不是分别传两个 style flag。product 的 concrete type 没有进入 Screen。

### Boundary

Go interface 不会自动证明两个 product 属于同一个 family；这是 factory 的 contract。只有一种 product 时，这层 grouping 往往没有意义。

### Role map

| GoF role | Go implementation |
|---|---|
| Abstract factory | `Factory` |
| Product A | `Button` |
| Product B | `Checkbox` |
| Concrete families | `Light / Dark` |

### Minimal Go

```go
package abstractfactory

type Button interface{ Draw() string }
type Checkbox interface{ Check() string }
type Factory interface {
	Button() Button
	Checkbox() Checkbox
}
type button string

func (b button) Draw() string { return string(b) + ":button" }

type checkbox string

func (c checkbox) Check() string { return string(c) + ":checked" }

type Light struct{}

func (Light) Button() Button     { return button("light") }
func (Light) Checkbox() Checkbox { return checkbox("light") }

type Dark struct{}

func (Dark) Button() Button     { return button("dark") }
func (Dark) Checkbox() Checkbox { return checkbox("dark") }

// Screen obtains related products from the same family factory.
func Screen(f Factory) string { return f.Button().Draw() + "/" + f.Checkbox().Check() }
```

### Test

```go
package abstractfactory

import "testing"

func TestFamilyChangesTogether(t *testing.T) {
	for _, tc := range []struct {
		f    Factory
		want string
	}{{Light{}, "light:button/light:checked"}, {Dark{}, "dark:button/dark:checked"}} {
		if got := Screen(tc.f); got != tc.want {
			t.Fatal(got)
		}
	}
}
```

```sh
go test -v ./abstract-factory
```

### OSS · Go adaptation

database/sql/driver.Conn 提供 Prepare→Stmt 与 Begin→Tx。mysqlConn 分别创建关联同一 connection 的 mysqlStmt 与 mysqlTx，可按 family factory 理解；上游没有声称这是 GoF。现代使用应走 database/sql 的 Context-aware API，而不是照抄 legacy Begin。

- [golang/go / src/database/sql/driver/driver.go: L231–L264](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/database/sql/driver/driver.go#L231-L264)
- [go-sql-driver/mysql / connection.go: L145–L164](https://github.com/go-sql-driver/mysql/blob/6a57eb17a8c8f81b4fb5998761cd074d9a7c88b0/connection.go#L145-L164)
- [go-sql-driver/mysql / connection.go: L214–L252](https://github.com/go-sql-driver/mysql/blob/6a57eb17a8c8f81b4fb5998761cd074d9a7c88b0/connection.go#L214-L252)

### Practice

增加 HighContrast family；再写一个故意混合 Light Button 与 Dark Checkbox 的 factory。

新 family 不应修改 Screen。混合 factory 仍可能通过 compilation，所以 family consistency 需要 contract test；不能把 implicit interface 当作语义证明。

## 03 · Builder — creational

分步 assembly，在 boundary 验证并交付 product。

### Problem

一个 Plan 需要 name 和任意多个 step。长 constructor parameter list 难读；暴露半成品则让无效 state 进入 caller。

### Design

Builder 保存 construction state，Named/Add 返回同一个 builder，Build 才验证并 copy slice。caller 得到可独立修改的 Plan，而不是 builder 内部 slice 的 alias。GoF 的 Director 在最小 version 里由 call 顺序承担，无需额外 type。

### Walkthrough

空 Builder 的 Build 必须失败。创建 first 后修改它的 Steps，再继续 build second，test 验证两者及 builder 不 sharing  mutable slice。

### Boundary

fluent syntax 本身不是 Builder。若每个 method 只是立即执行 I/O，它并没有分离 construction 与 execution。这个 mutable builder 也不支持 concurrent use。

### Role map

| GoF role | Go implementation |
|---|---|
| Builder | `Builder` |
| Product | `Plan` |
| Steps | `Named / Add` |
| Finalization | `Build` |

### Minimal Go

```go
package builder

import "errors"

type Plan struct {
	Name  string
	Steps []string
}
type Builder struct {
	name  string
	steps []string
}

func (b *Builder) Named(s string) *Builder { b.name = s; return b }
func (b *Builder) Add(s string) *Builder   { b.steps = append(b.steps, s); return b }
func (b *Builder) Build() (Plan, error) {
	if b.name == "" || len(b.steps) == 0 {
		return Plan{}, errors.New("name and steps required")
	}
	return Plan{Name: b.name, Steps: append([]string(nil), b.steps...)}, nil
}
```

### Test

```go
package builder

import "testing"

func TestValidationAndBuiltValueOwnership(t *testing.T) {
	b := new(Builder)
	if _, err := b.Build(); err == nil {
		t.Fatal("invalid plan accepted")
	}
	first, err := b.Named("import").Add("read").Build()
	if err != nil {
		t.Fatal(err)
	}
	first.Steps[0] = "changed by caller"
	second, err := b.Add("write").Build()
	if err != nil {
		t.Fatal(err)
	}
	if second.Steps[0] != "read" || len(second.Steps) != 2 || len(first.Steps) != 1 {
		t.Fatal(first, second)
	}
}
```

```sh
go test -v ./builder
```

### OSS · Direct structure

Kubernetes rest.Request 的 Verb/Resource/Name/Namespace 分步修改 request state，多个 setter 把 validation error 保存到 r.err；Do 才进入 request execution。它同时是 builder 和最终 request，未像 lab 分离出独立 product。

- [kubernetes/kubernetes / staging/src/k8s.io/client-go/rest/request.go: L214–L255](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/client-go/rest/request.go#L214-L255)
- [kubernetes/kubernetes / staging/src/k8s.io/client-go/rest/request.go: L332–L368](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/client-go/rest/request.go#L332-L368)
- [kubernetes/kubernetes / staging/src/k8s.io/client-go/rest/request.go: L1128–L1147](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/client-go/rest/request.go#L1128-L1147)

### Practice

增加 Build 后重复使用的 test，再决定是否需要 Reset。

Reset 不是 pattern 的必备部分。若加入它，已返回的 Plan 必须不受影响；若 product 含 nested pointers，单层 slice copy 也不再足够。

## 04 · Prototype — creational

从已有 object 克隆，并明确 copy depth。

### Problem

需要从 template Document 派生多个 version，但修改新 version 不能污染原 object。Go struct assignment 不会 deep copy slice/map。

### Design

Clone 先 copy value fields，再对 Tags 和 Labels 各 copy 一层。此例的 element 都是 string，因此满足独立修改 contract；如果 map value 变成 pointer，必须重新定义 copy depth。

### Walkthrough

test 修改 clone 的 slice element 与 map entry，assert 原值不变；还验证 nil collection 保持 nil。

### Boundary

GC 管 lifetime，不管 aliasing。不要 copy 含已使用 mutex、connection 或 goroutine ownership 的 struct；这些 object 通常不是可克隆 value。

### Role map

| GoF role | Go implementation |
|---|---|
| Prototype | `Document` |
| Clone operation | `Clone` |
| Independent state | `Tags / Labels` |

### Minimal Go

```go
package prototype

import (
	"maps"
	"slices"
)

type Document struct {
	Name   string
	Tags   []string
	Labels map[string]string
}

// Clone preserves nil collections and copies each mutable collection here.
func (d Document) Clone() Document {
	d.Tags = slices.Clone(d.Tags)
	d.Labels = maps.Clone(d.Labels)
	return d
}
```

### Test

```go
package prototype

import "testing"

func TestCloneDoesNotAliasCollections(t *testing.T) {
	original := Document{Name: "base", Tags: []string{"go"}, Labels: map[string]string{"owner": "a"}}
	clone := original.Clone()
	clone.Tags[0] = "java"
	clone.Labels["owner"] = "b"
	if original.Tags[0] != "go" || original.Labels["owner"] != "a" {
		t.Fatal("clone aliases original")
	}
	empty := (Document{}).Clone()
	if empty.Tags != nil || empty.Labels != nil {
		t.Fatal("nil semantics changed")
	}
}
```

```sh
go test -v ./prototype
```

### OSS · Direct structure

Kubernetes Pod.DeepCopy 创建新 Pod，DeepCopyInto 先做 value copy，再 delegation ObjectMeta/Spec/Status 的 DeepCopyInto。DeepCopyObject 返回 runtime.Object。这是 generated cloning API，而不是 runtime reflection magic。

- [kubernetes/kubernetes / staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go: L3944–L3970](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/api/core/v1/zz_generated.deepcopy.go#L3944-L3970)

### Practice

把 Labels 改成 map[string][]string，写出原 test 不能捕获的新 aliasing bug。

maps.Clone 只 copy map entry，nested slice 仍 sharing backing array。对每个 value 再 slices.Clone，并增加 nested element mutation test。

## 05 · Singleton — creational

一个明确 scope 内 sharing 同一个 instance。

### Problem

所有 caller 需要同一个 read-only 默认 configuration，并可能同时首次访问。手写 nil check 会产生 initialization race。

### Design

package-level OnceValue closure 只 construction 一次，Default 返回同一个 pointer。公开 API 没有 mutation method；test 同时 call 32 次，验证 shared identity。这个保证只属于 Default access path。

### Walkthrough

注意 OnceValue 必须只创建一次。把 sync.OnceValue(...) 放进 Default 内，会在每次 call 新建一个独立 initialization scope。

### Boundary

这不是 distributed singleton，也不禁止 caller copy Go value 或创建其它 Settings。global dependency 会影响 test isolation；需要多 tenant 或多 configuration 时，显式 DI 往往更合适。

### Role map

| GoF role | Go implementation |
|---|---|
| Access point | `Default` |
| Shared instance | `Settings` |
| One-time initialization | `sync.OnceValue` |

### Minimal Go

```go
package singleton

import "sync"

// Settings exposes no mutation API; all callers share one package default.
type Settings struct{ endpoint string }

func (s *Settings) Endpoint() string { return s.endpoint }

var instance = sync.OnceValue(func() *Settings { return &Settings{endpoint: "localhost:8080"} })

func Default() *Settings { return instance() }
```

### Test

```go
package singleton

import (
	"sync"
	"testing"
)

func TestConcurrentCallsShareIdentity(t *testing.T) {
	const n = 32
	results := make(chan *Settings, n)
	var wg sync.WaitGroup
	for i := 0; i < n; i++ {
		wg.Add(1)
		go func() { defer wg.Done(); results <- Default() }()
	}
	wg.Wait()
	close(results)
	for got := range results {
		if got != Default() || got.Endpoint() != "localhost:8080" {
			t.Fatal("different instance")
		}
	}
}
```

```sh
go test -v ./singleton
```

### OSS · Related mechanism

time.Local 背后有 package-level localLoc 与 localOnce，get 对默认 object 执行一次 initLocal。它是 shared default + lazy initialization；Local 可被替换，也存在其它 Location，所以不能称为严格禁止多 instance 的 Singleton。OnceValue 是 lab 使用的机制。

- [golang/go / src/time/zoneinfo.go: L78–L99](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/time/zoneinfo.go#L78-L99)
- [golang/go / src/sync/oncefunc.go: L43–L75](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/sync/oncefunc.go#L43-L75)

### Practice

把 OnceValue 移入 Default，观察 identity test 失败；再给 Settings 添加 mutable map。

Once 只保护 initialization，不保护之后的读写。mutable shared state 还需要 synchronization 或 immutable snapshot；不要认为有 Once 就不会 data race。

## 06 · Adapter — structural

保持 caller contract，把不兼容 API 转换过来。

### Problem

旧 sensor 提供 Fahrenheit，新的 rule 只接受 Celsius。直接把数值转交，即使通过 compilation，仍会产生语义 error。

### Design

Adapter implementation target 的 Celsius method，持有旧 sensor，在 boundary 做单位转换。caller IsBoiling 不知道 legacy API。

### Walkthrough

test 用 212°F→100°C 和 32°F→0°C 两个边界，验证 adaptation 是语义转换，不只是 method rename。

### Boundary

Adapter 改 contract；Decorator 通常保留 contract。time unit、error meaning、ownership、cancellation 都可能需要转换，不能只让 interface assignment 成功。

### Role map

| GoF role | Go implementation |
|---|---|
| Target | `CelsiusSensor` |
| Adaptee | `FahrenheitSensor` |
| Adapter | `Adapter` |

### Minimal Go

```go
package adapter

type FahrenheitSensor interface{ Fahrenheit() float64 }
type CelsiusSensor interface{ Celsius() float64 }
type Adapter struct{ Sensor FahrenheitSensor }

func (a Adapter) Celsius() float64   { return (a.Sensor.Fahrenheit() - 32) * 5 / 9 }
func IsBoiling(s CelsiusSensor) bool { return s.Celsius() >= 100 }
```

### Test

```go
package adapter

import "testing"

type oldSensor float64

func (s oldSensor) Fahrenheit() float64 { return float64(s) }
func TestUnitsAreAdapted(t *testing.T) {
	if !IsBoiling(Adapter{Sensor: oldSensor(212)}) {
		t.Fatal("boiling lost")
	}
	if got := (Adapter{Sensor: oldSensor(32)}).Celsius(); got != 0 {
		t.Fatal(got)
	}
}
```

```sh
go test -v ./adapter
```

### OSS · Direct structure

net/http.HandlerFunc 是官方注释明确称为 adapter 的例子：function type 增加 ServeHTTP method，method 内 call 原 function。它转换 callable shape；lab 额外展示 unit semantics。

- [golang/go / src/net/http/server.go: L2344–L2353](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/net/http/server.go#L2344-L2353)

### Practice

增加一个 KelvinSensor adapter，但不修改 IsBoiling。

把 conversion 留在 adapter，给零点与沸点写 test。如果三个来源都有 error，就先升级 target error contract，再 adaptation 每个来源。

## 07 · Bridge — structural

分开两个独立变化的 dimension。

### Problem

需要普通与安静 remote，同时支持 TV 和 Radio。为每个 composition 建一个 type，会出现乘法增长。

### Design

Remote 持有 Device interface；QuietRemote 复用 Remote 的 capability，再 composition Volume 与 Power。remote behavior 与 device implementation 分开变化，test 覆盖 2×2 composition。

### Walkthrough

先看 Device 的基础 operation，再看 On/Sleep 如何组织它们。新增 device 不该要求新增一整组 remote type。

### Boundary

任何 struct 持有 interface 都不自动是 Bridge。需要两个有意义的 variation axes。embedding 也没有 Java virtual override 语义；这里使用的是 composition。

### Role map

| GoF role | Go implementation |
|---|---|
| Abstraction | `Remote` |
| Refined abstraction | `QuietRemote` |
| Implementor | `Device` |
| Implementations | `TV / Radio` |

### Minimal Go

```go
package bridge

import "fmt"

type Device interface {
	Power(bool) string
	Volume(int) string
}
type Remote struct{ Device Device }

func (r Remote) On() string { return r.Device.Power(true) }

type QuietRemote struct{ Remote }

func (r QuietRemote) Sleep() string { return r.Device.Volume(0) + "/" + r.Device.Power(false) }

type TV struct{}

func (TV) Power(on bool) string { return fmt.Sprint("tv:power=", on) }
func (TV) Volume(n int) string  { return fmt.Sprint("tv:volume=", n) }

type Radio struct{}

func (Radio) Power(on bool) string { return fmt.Sprint("radio:power=", on) }
func (Radio) Volume(n int) string  { return fmt.Sprint("radio:volume=", n) }
```

### Test

```go
package bridge

import "testing"

func TestTwoIndependentAxes(t *testing.T) {
	for _, tc := range []struct {
		device Device
		name   string
	}{{TV{}, "tv"}, {Radio{}, "radio"}} {
		remote := Remote{Device: tc.device}
		if got := remote.On(); got != tc.name+":power=true" {
			t.Fatal(got)
		}
		if got := (QuietRemote{Remote: remote}).Sleep(); got != tc.name+":volume=0/"+tc.name+":power=false" {
			t.Fatal(got)
		}
	}
}
```

```sh
go test -v ./bridge
```

### OSS · Go adaptation

Zap Logger 保存 Core，Logger 的命名、caller annotation 等高层 behavior 与 Core 的 encoding/output contract 分开；check delegation core.Check。按这两个维度可理解为 Bridge-style separation，不声称作者使用了 GoF taxonomy。

- [uber-go/zap / logger.go: L41–L57](https://github.com/uber-go/zap/blob/bb1a55dd13257cf7cbd06b4146674c67ca614dea/logger.go#L41-L57)
- [uber-go/zap / zapcore/core.go: L23–L45](https://github.com/uber-go/zap/blob/bb1a55dd13257cf7cbd06b4146674c67ca614dea/zapcore/core.go#L23-L45)
- [uber-go/zap / logger.go: L322–L344](https://github.com/uber-go/zap/blob/bb1a55dd13257cf7cbd06b4146674c67ca614dea/logger.go#L322-L344)

### Practice

新增 Speaker Device 和一个 AlarmRemote，统计新增 type 数量。

应各加一个 type，而不是为所有 composition 建新 type。若 Alarm 需要仅部分 device 具有的 capability，重新审查 capability contract，不要默默 type assert 后忽略失败。

## 08 · Composite — structural

让 leaf 与 tree 都提供同一个 operation。

### Problem

计算 directory 总 size，caller 不应自己判断每个 child 是 File 还是嵌套 Directory。

### Design

File 与 Directory 都 implementation Size。Directory 保存 []Node 并递归累加；caller 只 call root.Size。空 directory 的 identity 是 0。

### Walkthrough

test 包含 leaf、两层 nested directory 和空 directory，验证统一 contract 与递归 aggregation。

### Boundary

示例只接受 finite acyclic tree。若允许 cycle、shared child 或重复 node，需明确 double counting、visited set 与 recursion depth，不能把任意 graph 当 tree。

### Role map

| GoF role | Go implementation |
|---|---|
| Component | `Node` |
| Leaf | `File` |
| Composite | `Directory` |

### Minimal Go

```go
package composite

// Node represents a finite, acyclic tree. Cycles are outside this contract.
type Node interface{ Size() int }
type File int

func (f File) Size() int { return int(f) }

type Directory []Node

func (d Directory) Size() int {
	total := 0
	for _, child := range d {
		total += child.Size()
	}
	return total
}
```

### Test

```go
package composite

import "testing"

func TestLeafAndNestedTreeUseSameContract(t *testing.T) {
	var root Node = Directory{File(3), Directory{File(4), Directory{}, File(5)}}
	if got := root.Size(); got != 12 {
		t.Fatal(got)
	}
	if (Directory{}).Size() != 0 {
		t.Fatal("empty tree")
	}
}
```

```sh
go test -v ./composite
```

### OSS · Direct structure

errors.Join 返回的 joinError 自己是 error，又持有 []error；Error 聚合 child message，Unwrap 暴露 child errors。Join 可嵌套，因此 leaf error 和 composite error 都可通过共同 error contract 使用。

- [golang/go / src/errors/join.go: L11–L63](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/errors/join.go#L11-L63)

### Practice

为 Node 增加一个 Count operation，会影响哪些 type？

所有 leaf 与 composite 都要 implementation。若 operation 增长很快而 node kinds 稳定，比较 Visitor；它把新增 operation 的成本转移到 visitor。

## 09 · Decorator — structural

保留 contract，叠加一项 behavior。

### Problem

给任意 Reader 加 byte count，不修改 file、buffer 或 network reader 的 implementation。

### Design

CountingReader 也是 io.Reader。Read 先 delegation Source，再把实际 n 加入 Bytes，并原样返回 n/err。它记录成功读到的 bytes，而不是 caller request 的 buffer length。

### Walkthrough

特别看 n>0 与 io.EOF 同时返回的 test。如果先判断 err 再计数，就会漏掉最后一段数据。

### Boundary

wrapper 可能隐藏 WriterTo 等 optional capability；它也没有自动获得 concurrent safety。Decorator 不应该偷偷关闭由 caller 拥有的 Source。

### Role map

| GoF role | Go implementation |
|---|---|
| Component | `io.Reader` |
| Concrete component | `Source` |
| Decorator | `CountingReader` |

### Minimal Go

```go
package decorator

import "io"

type CountingReader struct {
	Source io.Reader
	Bytes  int
}

func (r *CountingReader) Read(p []byte) (int, error) {
	n, err := r.Source.Read(p)
	r.Bytes += n
	return n, err
}
```

### Test

```go
package decorator

import (
	"io"
	"strings"
	"testing"
)

type finalChunk struct{}

func (finalChunk) Read(p []byte) (int, error) { return copy(p, "end"), io.EOF }
func TestPreservesBytesAndTerminalError(t *testing.T) {
	reader := &CountingReader{Source: strings.NewReader("hello")}
	got, err := io.ReadAll(reader)
	if err != nil || string(got) != "hello" || reader.Bytes != 5 {
		t.Fatal(string(got), err, reader.Bytes)
	}
	reader = &CountingReader{Source: finalChunk{}}
	n, err := reader.Read(make([]byte, 3))
	if n != 3 || err != io.EOF || reader.Bytes != 3 {
		t.Fatal("lost bytes returned with EOF")
	}
}
```

```sh
go test -v ./decorator
```

### OSS · Direct structure

io.LimitedReader 持有 Reader 并 implementation Read，缩小可读范围、扣减 N，同时保留 Reader contract。它添加 limit behavior；lab 添加计数 behavior。

- [golang/go / src/io/io.go: L458–L482](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/io/io.go#L458-L482)

### Practice

把两个 CountingReader 嵌套，验证两层都记录相同 bytes。

每层观察自己的 Read result。若底层发生 short read 或 error，两个计数仍应等于实际已交付的数据；不应按 len(p) 计数。

## 10 · Facade — structural

给多个 subsystem 一个适合 caller 的入口。

### Problem

每个 caller 都重复 Get→Decode，重复 error wrapping，还需要知道两个 subsystem 的 sequencing。

### Design

Service.Load 组织 Store 与 Decoder，返回 caller 关心的 Config。它不要求 subsystem implementation 共同 interface，也不需要假装自己是其中任意一个。

### Walkthrough

test 证明 load failure 后不会 call decoder，同时 errors.Is 仍能识别底层 sentinel error。成功路径验证 orchestration 的结果。

### Boundary

Facade 不应该无限吸收 business rule，成为万能 Manager。它也不自动提供 transaction；多个 subsystem 部分成功时仍需设计 failure policy。

### Role map

| GoF role | Go implementation |
|---|---|
| Facade | `Service.Load` |
| Subsystem A | `Store` |
| Subsystem B | `Decoder` |

### Minimal Go

```go
package facade

import "fmt"

type Config struct{ Address string }
type Store interface{ Get(string) (string, error) }
type Decoder interface{ Decode(string) (Config, error) }
type Service struct {
	Store   Store
	Decoder Decoder
}

func (s Service) Load(key string) (Config, error) {
	raw, err := s.Store.Get(key)
	if err != nil {
		return Config{}, fmt.Errorf("load config: %w", err)
	}
	config, err := s.Decoder.Decode(raw)
	if err != nil {
		return Config{}, fmt.Errorf("decode config: %w", err)
	}
	return config, nil
}
```

### Test

```go
package facade

import (
	"errors"
	"testing"
)

type storeFunc func(string) (string, error)

func (f storeFunc) Get(k string) (string, error) { return f(k) }

type decodeFunc func(string) (Config, error)

func (f decodeFunc) Decode(s string) (Config, error) { return f(s) }
func TestFacadeOrchestratesAndPreservesError(t *testing.T) {
	missing := errors.New("missing")
	decoded := false
	s := Service{Store: storeFunc(func(string) (string, error) { return "", missing }), Decoder: decodeFunc(func(raw string) (Config, error) { decoded = true; return Config{Address: raw}, nil })}
	if _, err := s.Load("key"); !errors.Is(err, missing) || decoded {
		t.Fatal("decode ran after failed load", err)
	}
	s.Store = storeFunc(func(string) (string, error) { return "localhost", nil })
	if got, err := s.Load("key"); err != nil || got.Address != "localhost" {
		t.Fatal(got, err)
	}
}
```

```sh
go test -v ./facade
```

### OSS · Go adaptation

http.Client.Get 将 NewRequest 与 Client.Do composition 成一个 URL-oriented convenience API。这是小型 Facade-style entry point；Context-aware request 仍应显式 NewRequestWithContext 后 Do。

- [golang/go / src/net/http/client.go: L443–L476](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/net/http/client.go#L443-L476)

### Practice

让 Decoder 返回 sentinel error，检查 caller 能否识别它。

用 %w 保留 cause，并返回 decode stage 的 context。不要用 string 比较 error，也不要为了隐藏 subsystem 而丢弃所有 error identity。

## 11 · Flyweight — structural

sharing intrinsic state，把每次使用的 context 留在外面。

### Problem

大量 Glyph 使用同一种 font。如果每个 Glyph 都重复保存昂贵的 style data，会浪费 memory。

### Design

Pool 按 font intern 一个 read-only Style，mutex 保护 lookup/create。Glyph 的 Text/X 是 extrinsic state，不放进 sharing Style。并行 test 验证同 key identity，同时不同 glyph 保留独立 context。

### Walkthrough

先比较同 font 的两个 pointer，再比较不同 Glyph 的 Text/X。 sharing 的是 intrinsic object，不是整个使用场景。

### Boundary

这个 pool 永久强 reference 所有 key；无限 key space 会造成 retention。若允许 Style 被修改，一个 glyph 的操作可能影响所有 consumer。

### Role map

| GoF role | Go implementation |
|---|---|
| Flyweight | `Style` |
| Factory | `Pool.Get` |
| Intrinsic state | `font` |
| Extrinsic state | `Glyph.Text / X` |

### Minimal Go

```go
package flyweight

import "sync"

type Style struct{ font string }

func (s *Style) Font() string { return s.font }

// Pool keeps strong references forever; a real unbounded key space needs a policy.
type Pool struct {
	mu     sync.Mutex
	styles map[string]*Style
}

func (p *Pool) Get(font string) *Style {
	p.mu.Lock()
	defer p.mu.Unlock()
	if p.styles == nil {
		p.styles = make(map[string]*Style)
	}
	if p.styles[font] == nil {
		p.styles[font] = &Style{font: font}
	}
	return p.styles[font]
}

// Extrinsic data stays outside the shared immutable Style.
type Glyph struct {
	Style *Style
	Text  string
	X     int
}
```

### Test

```go
package flyweight

import (
	"sync"
	"testing"
)

func TestIntrinsicSharingKeepsExtrinsicDataSeparate(t *testing.T) {
	var pool Pool
	var wg sync.WaitGroup
	found := make(chan *Style, 16)
	for i := 0; i < 16; i++ {
		wg.Add(1)
		go func() { defer wg.Done(); found <- pool.Get("mono") }()
	}
	wg.Wait()
	close(found)
	for s := range found {
		if s != pool.Get("mono") {
			t.Fatal("not interned")
		}
	}
	a := Glyph{Style: pool.Get("mono"), Text: "A", X: 1}
	b := Glyph{Style: pool.Get("mono"), Text: "B", X: 9}
	if a.Style != b.Style || a.Text == b.Text || a.X == b.X || pool.Get("serif") == a.Style {
		t.Fatal(a, b)
	}
}
```

```sh
go test -v ./flyweight
```

### OSS · Direct structure

unique.Make 为相等的 comparable value 返回相等 Handle，通过按 type 的 canonical map 查找或保存 value。Value 只做 shallow copy；不要由此推断 pointer 指向的数据也 immutable。这里的关 key 是 canonical identity。

- [golang/go / src/unique/handle.go: L15–L53](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/unique/handle.go#L15-L53)

### Practice

给 pool 增加 limit，会不会破坏同 key 永远同 pointer 的承诺？

可能。旧 Style 仍被 caller 持有时，eviction 后可能创建第二个 instance。先定义 identity scope，再决定 cache、interning 或 weak-reference 方案。

## 12 · Proxy — structural

在相同 contract 前控制访问或 forwarding。

### Problem

caller 需要 Store.Read，但受限 key 不得触达 backend。把检查散落到每个 caller 很容易漏掉。

### Design

Guard 与真实 Store 提供同一个 Read contract。先做 Allowed decision，拒绝时直接返回 ErrDenied，允许时才 delegate。test 验证拒绝路径 backend call count 为零。

### Walkthrough

第二个 assertion 让真实 backend 返回 error，确保 proxy 没把允许访问等同于读取成功。

### Boundary

Proxy 与 Decorator 的 shape 可能一样，区别在 intent：access/location/lifecycle control 对比增加 behavior。lab 的 key predicate 只是示例 policy，不是完整 authentication 系统。

### Role map

| GoF role | Go implementation |
|---|---|
| Subject | `Store` |
| Real subject | `Next` |
| Proxy | `Guard` |

### Minimal Go

```go
package proxy

import "errors"

var ErrDenied = errors.New("access denied")

type Store interface{ Read(string) (string, error) }
type Guard struct {
	Next    Store
	Allowed func(string) bool
}

func (g Guard) Read(key string) (string, error) {
	if !g.Allowed(key) {
		return "", ErrDenied
	}
	return g.Next.Read(key)
}
```

### Test

```go
package proxy

import (
	"errors"
	"testing"
)

type storeFunc func(string) (string, error)

func (f storeFunc) Read(k string) (string, error) { return f(k) }
func TestDeniedAccessNeverReachesSubject(t *testing.T) {
	calls := 0
	backendErr := errors.New("offline")
	var store Store = Guard{Allowed: func(k string) bool { return k == "public" }, Next: storeFunc(func(string) (string, error) { calls++; return "", backendErr })}
	if _, err := store.Read("secret"); !errors.Is(err, ErrDenied) || calls != 0 {
		t.Fatal(err, calls)
	}
	if _, err := store.Read("public"); !errors.Is(err, backendErr) || calls != 1 {
		t.Fatal(err, calls)
	}
}
```

```sh
go test -v ./proxy
```

### OSS · Direct structure

httputil.ReverseProxy implementation ServeHTTP，在本地接收 request，再通过 Transport.RoundTrip 转发到 upstream。它是 remote Proxy；lab 是 protection Proxy。两者保留 caller-facing contract，但 policy 不同。

- [golang/go / src/net/http/httputil/reverseproxy.go: L421–L450](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/net/http/httputil/reverseproxy.go#L421-L450)
- [golang/go / src/net/http/httputil/reverseproxy.go: L587–L604](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/net/http/httputil/reverseproxy.go#L587-L604)

### Practice

为 Guard 添加 audit function，区分 denied 和 backend failed。

不要在 denied 路径先访问 backend 再记录拒绝。明确 audit failure 是否影响 business 结果；否则一个看似普通 wrapper 会悄悄改变 availability。

## 13 · Chain of Responsibility — behavioral

沿 chain 寻找能处理 request 的 handler。

### Problem

多个 handler 都可能处理某个 path，caller 不想知道具体负责者，也不希望所有 handler 都执行 side effect。

### Design

每个 Handler 返回 response 与 handled。Dispatch 按顺序询问，第一个 handled=true 就停止；没有接受者时返回 ErrUnhandled。central loop 是 Go 中表达 successor traversal 的简洁方式。

### Walkthrough

test 先 skip，再 accept，再放一个不应执行的 handler。call count=2 是 short-circuit 的可观察证据。

### Boundary

chain order 是 behavior 的一部分。它和总会执行全部 stage 的 pipeline 不同，也和所有 subscriber 都收到通知的 Observer 不同。

### Role map

| GoF role | Go implementation |
|---|---|
| Request | `Request` |
| Handlers | `Handler functions` |
| Chain owner | `Dispatch` |

### Minimal Go

```go
package chainofresponsibility

import "errors"

var ErrUnhandled = errors.New("no handler accepted request")

type Request struct{ Path string }
type Handler func(Request) (response string, handled bool)

func Dispatch(request Request, chain ...Handler) (string, error) {
	for _, handler := range chain {
		if response, ok := handler(request); ok {
			return response, nil
		}
	}
	return "", ErrUnhandled
}
```

### Test

```go
package chainofresponsibility

import (
	"errors"
	"testing"
)

func TestDelegationStopsAfterAcceptance(t *testing.T) {
	calls := 0
	skip := func(Request) (string, bool) { calls++; return "", false }
	accept := func(Request) (string, bool) { calls++; return "ok", true }
	got, err := Dispatch(Request{Path: "/"}, skip, accept, skip)
	if err != nil || got != "ok" || calls != 2 {
		t.Fatal(got, err, calls)
	}
	if _, err := Dispatch(Request{}); !errors.Is(err, ErrUnhandled) {
		t.Fatal(err)
	}
}
```

```sh
go test -v ./chain-of-responsibility
```

### OSS · Go adaptation

Gin Next 推进 handler index，Abort 把 index 设到终止位置，阻止 pending handlers。它允许 middleware before/after 与多 handler，所以不是 lab 的严格 first-handler-wins；尤其 Abort 不会 return 当前 function。

- [gin-gonic/gin / context.go: L195–L219](https://github.com/gin-gonic/gin/blob/dcaa4296d111981ffb31ac3eba90bb63e1eb5ab9/context.go#L195-L219)

### Practice

加一个 fallback handler；交换它和其它 handler 的位置。

永远 handled=true 的 fallback 必须放最后，否则后续 handler 永远不可达。用顺序 test lock 定这个 contract。

## 14 · Command — behavioral

把 action encapsulation 成可传递、可延后执行的 value。

### Problem

UI、CLI 或 job queue 想先组织 action，稍后由统一 invoker 执行，而不直接修改 Document。

### Design

Replace 保存 receiver 与 parameter，Execute 才改 state。Run 只认识 Command，在 error 时停止后续 command。construction 与 execution 在时间上分开。

### Walkthrough

test 在 construction queue 后检查 Document 仍是 old，再 Run 变成 new；第二条路径验证前一个 command 失败后后续 action 不执行。

### Boundary

Command 不自带 undo、retry safety 或 durable queue。保存 pointer 也不是 snapshot：执行时会看到 receiver 的当前 state。需要 serializable job 时定义独立 payload。

### Role map

| GoF role | Go implementation |
|---|---|
| Command | `Command` |
| Concrete command | `Replace` |
| Receiver | `Document` |
| Invoker | `Run` |

### Minimal Go

```go
package command

import "fmt"

type Command interface{ Execute() error }
type Document struct{ Text string }
type Replace struct {
	Target *Document
	Text   string
}

func (c Replace) Execute() error { c.Target.Text = c.Text; return nil }
func Run(queue []Command) error {
	for i, c := range queue {
		if err := c.Execute(); err != nil {
			return fmt.Errorf("command %d: %w", i, err)
		}
	}
	return nil
}
```

### Test

```go
package command

import (
	"errors"
	"testing"
)

type commandFunc func() error

func (f commandFunc) Execute() error { return f() }
func TestDeferredExecutionAndFailureBoundary(t *testing.T) {
	doc := &Document{Text: "old"}
	queue := []Command{Replace{Target: doc, Text: "new"}}
	if doc.Text != "old" {
		t.Fatal("construction executed command")
	}
	if err := Run(queue); err != nil || doc.Text != "new" {
		t.Fatal(doc, err)
	}
	stop := errors.New("stop")
	queue = []Command{commandFunc(func() error { return stop }), Replace{Target: doc, Text: "wrong"}}
	if err := Run(queue); !errors.Is(err, stop) || doc.Text != "new" {
		t.Fatal(doc, err)
	}
}
```

```sh
go test -v ./command
```

### OSS · Direct structure

Cobra Command 保存 Run/RunE action，execute 在 parsing 与 validation 后 call 它们。这里 Command 的 action-encapsulation 很清晰；RunE failure 会跳过后续 public post hooks，并不会自动 rollback action。

- [spf13/cobra / command.go: L1014–L1044](https://github.com/spf13/cobra/blob/adbc8813901bba65827259daa8e22ff94ec1f30e/command.go#L1014-L1044)

### Practice

让 Replace 支持 undo；比较保存旧 text 与保存 Memento。

Command 表示要做什么，Memento 表示之前是什么。undo history、重复 Execute、失败后的部分效果需要额外 contract，不能只添加一个 Undo method。

## 15 · Interpreter — behavioral

用 expression tree 表达并求值一门小语言。

### Problem

authorization rule 需要 composition  variable、AND 和 NOT，而不是每个 rules 都新增一段硬编码 if。

### Design

Expr.Eval 接受 Env；terminal 返回 variable 或 literal，nonterminal 递归解释 child。And 保留 short-circuit，unknown variable 返回 error。本例从 AST 开始，不包含 parser。

### Walkthrough

rule=admin AND NOT blocked。test 同时覆盖 true、missing variable 和 false AND missing 的 short-circuit。

### Boundary

不要把任意 expression language 当 sandbox。真实 DSL 还需要 grammar、type checking、depth/time budget；一次固定 rule 用普通 function 更容易维护。

### Role map

| GoF role | Go implementation |
|---|---|
| Expression | `Expr` |
| Terminals | `Var / Literal` |
| Nonterminals | `And / Not` |
| Context | `Env` |

### Minimal Go

```go
package interpreter

import "fmt"

type Env map[string]bool
type Expr interface{ Eval(Env) (bool, error) }
type Var string

func (v Var) Eval(env Env) (bool, error) {
	value, ok := env[string(v)]
	if !ok {
		return false, fmt.Errorf("unknown variable %s", v)
	}
	return value, nil
}

type Literal bool

func (l Literal) Eval(Env) (bool, error) { return bool(l), nil }

type And struct{ Left, Right Expr }

func (a And) Eval(env Env) (bool, error) {
	left, err := a.Left.Eval(env)
	if err != nil || !left {
		return left, err
	}
	return a.Right.Eval(env)
}

type Not struct{ Inner Expr }

func (n Not) Eval(env Env) (bool, error) {
	v, err := n.Inner.Eval(env)
	if err != nil {
		return false, err
	}
	return !v, nil
}
```

### Test

```go
package interpreter

import "testing"

func TestExpressionSemanticsAndShortCircuit(t *testing.T) {
	rule := And{Left: Var("admin"), Right: Not{Inner: Var("blocked")}}
	if got, err := rule.Eval(Env{"admin": true, "blocked": false}); err != nil || !got {
		t.Fatal(got, err)
	}
	if got, err := (And{Left: Literal(false), Right: Var("missing")}).Eval(nil); err != nil || got {
		t.Fatal("not short circuited")
	}
	if _, err := Var("missing").Eval(nil); err == nil {
		t.Fatal("unknown silently accepted")
	}
}
```

```sh
go test -v ./interpreter
```

### OSS · Go adaptation

text/template 的 parse.Node 表达 syntax 结构，exec.state.walk 按 node kind 解释 action/list/if/text。它用 central type switch，而不是每个 node 自带 Interpret method；这是 Go-style tree interpretation，不是经典 class hierarchy 的逐字翻译。

- [golang/go / src/text/template/exec.go: L260–L295](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/text/template/exec.go#L260-L295)
- [golang/go / src/text/template/parse/node.go: L17–L33](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/text/template/parse/node.go#L17-L33)

### Practice

增加 Or，并验证 true OR missing 不求值右侧。

short-circuit 属于 language semantics。不要把两侧先求值再做 boolean 运算，否则 error 和 side effect behavior 都会变。

## 16 · Iterator — behavioral

隐藏 collection representation，提供可停止的 traversal。

### Problem

caller 只想按顺序 traversal，不应知道数据内部用 slice、tree 还是分页 storage。

### Design

All 返回 iter.Seq。producer 每次 call yield，false 时立即结束。New copy input slice，因此外部修改不会改变本例的 traversal。每次 All 都可重新 traversal。

### Walkthrough

test 让 yield 第一次就返回 false，assert 只有一次 call；再收集完整结果，证明一次早停没有耗尽后续 traversal。

### Boundary

Go 1.23+ 的 range-over-function 是 push iterator。不要在 yield 返回 false 后继续 call 它。真实 DB cursor 还需要 Close/error/lifetime contract，不能只套一个 Seq 隐藏 resource。

### Role map

| GoF role | Go implementation |
|---|---|
| Aggregate | `Collection` |
| Iterator | `iter.Seq[string]` |
| Consumer | `yield / range` |

### Minimal Go

```go
package iterator

import (
	"iter"
	"slices"
)

type Collection struct{ values []string }

func New(values []string) Collection { return Collection{values: slices.Clone(values)} }
func (c Collection) All() iter.Seq[string] {
	return func(yield func(string) bool) {
		for _, value := range c.values {
			if !yield(value) {
				return
			}
		}
	}
}
```

### Test

```go
package iterator

import (
	"slices"
	"testing"
)

func TestEarlyStopAndIndependentTraversals(t *testing.T) {
	source := []string{"a", "b", "c"}
	c := New(source)
	source[0] = "mutated"
	calls := 0
	c.All()(func(s string) bool {
		calls++
		if s != "a" {
			t.Fatal(s)
		}
		return false
	})
	if calls != 1 {
		t.Fatal("ignored stop")
	}
	if got := slices.Collect(c.All()); !slices.Equal(got, []string{"a", "b", "c"}) {
		t.Fatal(got)
	}
	for range c.All() {
		break
	}
}
```

```sh
go test -v ./iterator
```

### OSS · Go adaptation

google/btree Ascend 接收 callback，并在 callback 返回 false 时停止；Go ast.Preorder 则返回 iter.Seq，把 traversal 与 consumption 分离。google/btree 在本次 snapshot 已 archived，只作 source code 案例，不作为维护 state 良好的选型推荐。

- [google/btree / btree.go: L747–L781](https://github.com/google/btree/blob/aeba20f7a1e1315badec4eca4fdc9f754f5f880a/btree.go#L747-L781)
- [golang/go / src/go/ast/walk.go: L380–L397](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/go/ast/walk.go#L380-L397)

### Practice

增加一个 Filter Seq，保证下游 break 能传回最初 producer。

当 predicate 匹配时才 yield；只要下游返回 false，就立即结束上游 traversal。 test producer 的 call 次数，而不只是最终结果。

## 17 · Mediator — behavioral

把 colleague 之间的交互 rules 放到一个 coordinator。

### Problem

多个 Member 互发消息，如果每个 object 都保存其它 object 的 reference，关系与 routing policy 会散落在各处。

### Design

Member 只知道 Room。Send 把意图交给 route，Room 决定 recipient 与 delivery。Join 检查 duplicate identity；Messages 返回 copy，避免暴露 mutable inbox。

### Walkthrough

a 不持有 b 的 pointer，仍能通过 mediator 给 b 发消息。test 验证 unknown recipient、duplicate identity 与 inbox ownership。

### Boundary

Mediator 不等于 Observer broadcast，也不等于任意 event bus。它应拥有交互 rules，而不只是 pass-through。这个最小 Room 是 single-owner，不支持 concurrent calls。

### Role map

| GoF role | Go implementation |
|---|---|
| Mediator | `Room` |
| Colleagues | `Member` |
| Coordination | `route` |

### Minimal Go

```go
package mediator

import (
	"fmt"
	"slices"
)

// Room and Members have a single owner; concurrent use needs a separate policy.
type Room struct{ members map[string]*Member }
type Member struct {
	room  *Room
	name  string
	inbox []string
}

func (r *Room) Join(name string) (*Member, error) {
	if r.members == nil {
		r.members = make(map[string]*Member)
	}
	if _, exists := r.members[name]; exists {
		return nil, fmt.Errorf("duplicate member %s", name)
	}
	m := &Member{room: r, name: name}
	r.members[name] = m
	return m, nil
}
func (m *Member) Send(to, text string) error { return m.room.route(m.name, to, text) }
func (m *Member) Messages() []string         { return slices.Clone(m.inbox) }
func (r *Room) route(from, to, text string) error {
	recipient, ok := r.members[to]
	if !ok {
		return fmt.Errorf("unknown recipient %s", to)
	}
	recipient.inbox = append(recipient.inbox, from+":"+text)
	return nil
}
```

### Test

```go
package mediator

import "testing"

func TestColleaguesCommunicateThroughMediator(t *testing.T) {
	var room Room
	a, err := room.Join("a")
	if err != nil {
		t.Fatal(err)
	}
	b, err := room.Join("b")
	if err != nil {
		t.Fatal(err)
	}
	if err := a.Send("b", "hi"); err != nil {
		t.Fatal(err)
	}
	got := b.Messages()
	if len(got) != 1 || got[0] != "a:hi" || len(a.Messages()) != 0 {
		t.Fatal(got)
	}
	got[0] = "mutated"
	if b.Messages()[0] != "a:hi" {
		t.Fatal("inbox leaked")
	}
	if err := a.Send("absent", "hi"); err == nil {
		t.Fatal("unknown recipient accepted")
	}
	if _, err := room.Join("a"); err == nil {
		t.Fatal("duplicate accepted")
	}
}
```

```sh
go test -v ./mediator
```

### OSS · Related mechanism

fzf EventBox 集中存放 event，Set 按 event type 更新并唤醒 waiter，Wait 将 event batch 交给 callback。它体现 centralized coordination，但没有本例的 colleague routing protocol，因此只标 related mechanism。不能据此声称 fzf 完整 implementation 经典 Mediator。

- [junegunn/fzf / src/util/eventbox.go: L8–L54](https://github.com/junegunn/fzf/blob/52f4319a72c17e123396cc3a2e6abf2e96e9d753/src/util/eventbox.go#L8-L54)

### Practice

添加 rules：某个 Member 只能向同组成员发送。

group routing policy 应在 mediator，而不是 copy 到每个 Member。若 Room 开始处理 billing、storage 和 UI，说明 coordinator boundary 过大。

## 18 · Memento — behavioral

保存可恢复的 state，而不暴露内部 representation。

### Problem

editor 需要 undo，但 history manager 不应该知道或修改 editor 内部 field。

### Design

Save 返回 opaque Snapshot， field 不 exported；Restore 由 Editor 自己执行，并拒绝其它 Editor 的 snapshot。caretaker 只保存 token，不解释内容。

### Walkthrough

test 保存 before、改成 after、再恢复，并检查 snapshot 可重复使用及跨 owner restore 被拒绝。

### Boundary

Memento 不是 durable transaction，也不等于 deep copy 一切。large state 可以用 journal、copy-on-write 或 revision token。snapshot 会延长它 reference 的 object lifetime。

### Role map

| GoF role | Go implementation |
|---|---|
| Originator | `Editor` |
| Memento | `Snapshot` |
| Caretaker | `caller / history` |

### Minimal Go

```go
package memento

import "errors"

type Editor struct{ text string }
type Snapshot struct {
	owner *Editor
	text  string
}

func (e *Editor) Set(s string)   { e.text = s }
func (e *Editor) Text() string   { return e.text }
func (e *Editor) Save() Snapshot { return Snapshot{owner: e, text: e.text} }
func (e *Editor) Restore(s Snapshot) error {
	if s.owner != e {
		return errors.New("snapshot belongs to another editor")
	}
	e.text = s.text
	return nil
}
```

### Test

```go
package memento

import "testing"

func TestOpaqueSnapshotAndOriginatorBoundary(t *testing.T) {
	var editor Editor
	editor.Set("before")
	saved := editor.Save()
	editor.Set("after")
	if err := editor.Restore(saved); err != nil || editor.Text() != "before" {
		t.Fatal(editor.Text(), err)
	}
	var other Editor
	if err := other.Restore(saved); err == nil {
		t.Fatal("foreign snapshot accepted")
	}
	editor.Set("again")
	if err := editor.Restore(saved); err != nil || editor.Text() != "before" {
		t.Fatal("snapshot mutated")
	}
	if err := editor.Restore(Snapshot{}); err == nil {
		t.Fatal("invalid snapshot accepted")
	}
}
```

```sh
go test -v ./memento
```

### OSS · Go adaptation

go-ethereum StateDB.Snapshot 返回 revision ID；journal 保存 revision 对应的 entry index，RevertToSnapshot 查找它并倒放 journal。它是 incremental restore 的 Go adaptation：token 不携带完整 state，revert 会使相应 revision 失效，和 lab 可重复 snapshot 不同。

- [ethereum/go-ethereum / core/state/statedb.go: L761–L769](https://github.com/ethereum/go-ethereum/blob/d799b1a3f20fe9d983bfff6cc53fb246bcab1298/core/state/statedb.go#L761-L769)
- [ethereum/go-ethereum / core/state/journal.go: L213–L235](https://github.com/ethereum/go-ethereum/blob/d799b1a3f20fe9d983bfff6cc53fb246bcab1298/core/state/journal.go#L213-L235)

### Practice

加入两层 undo history，再决定恢复旧 snapshot 后 newer history 是否保留。

这属于 history semantics，必须明确选择 branch、truncate 或 redo stack。不要把 lab 的 immutable token 语义误读成所有 journal system 的行为。

## 19 · Observer — behavioral

一处 event，通知当前的一组 subscriber。

### Problem

多个独立 consumer 需要知道 state change，但 subject 不应逐个写死 consumer type。

### Design

Subscribe 保存 callback 并返回 unsubscribe。Publish 在 lock 内 copy listener set，然后 unlock 后 call，允许 callback 在内部 unsubscribe，避免 reentrant deadlock。

### Walkthrough

self-unsubscribe test 连续 Publish 两次，只收到第一次。parallel Publish test 用 atomic counter，因为 callback 本身可能 concurrent 执行。

### Boundary

snapshot semantics 表示 unsubscribe 无法撤回已进入 snapshot 的 callback。没有 delivery order 保证，slow observer 会 blocking synchronous Publish，callback panic 会影响 publisher。

### Role map

| GoF role | Go implementation |
|---|---|
| Subject | `Topic` |
| Observers | `callback functions` |
| Registration | `Subscribe` |
| Notification | `Publish` |

### Minimal Go

```go
package observer

import "sync"

type Topic struct {
	mu        sync.Mutex
	next      int
	listeners map[int]func(string)
}

func (t *Topic) Subscribe(fn func(string)) func() {
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.listeners == nil {
		t.listeners = make(map[int]func(string))
	}
	id := t.next
	t.next++
	t.listeners[id] = fn
	return func() { t.mu.Lock(); defer t.mu.Unlock(); delete(t.listeners, id) }
}

// Delivery is synchronous, unordered, and uses a listener snapshot.
// Unsubscribe cannot revoke a callback already included in a snapshot.
func (t *Topic) Publish(value string) {
	t.mu.Lock()
	snapshot := make([]func(string), 0, len(t.listeners))
	for _, fn := range t.listeners {
		snapshot = append(snapshot, fn)
	}
	t.mu.Unlock()
	for _, fn := range snapshot {
		fn(value)
	}
}
```

### Test

```go
package observer

import (
	"sync"
	"sync/atomic"
	"testing"
)

func TestListenerCanUnsubscribeInsideCallback(t *testing.T) {
	var topic Topic
	calls := 0
	var unsubscribe func()
	unsubscribe = topic.Subscribe(func(string) { calls++; unsubscribe() })
	topic.Publish("one")
	topic.Publish("two")
	unsubscribe()
	if calls != 1 {
		t.Fatal(calls)
	}
}
func TestConcurrentPublication(t *testing.T) {
	var topic Topic
	var calls atomic.Int64
	stop := topic.Subscribe(func(string) { calls.Add(1) })
	defer stop()
	var wg sync.WaitGroup
	for i := 0; i < 16; i++ {
		wg.Add(1)
		go func() { defer wg.Done(); topic.Publish("event") }()
	}
	wg.Wait()
	if calls.Load() != 16 {
		t.Fatal(calls.Load())
	}
}
```

```sh
go test -v ./observer
```

### OSS · Direct structure

Kubernetes sharedProcessor register processorListener，并在 distribute 中向符合条件的 listener.add 投递；sync event 与普通 event 的受众不同。实际 informer 有独立 listener lifecycle/queue，不能把 lab 的synchronous callback latency contract 套过去。

- [kubernetes/kubernetes / staging/src/k8s.io/client-go/tools/cache/shared_informer.go: L1190–L1208](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/client-go/tools/cache/shared_informer.go#L1190-L1208)
- [kubernetes/kubernetes / staging/src/k8s.io/client-go/tools/cache/shared_informer.go: L1236–L1259](https://github.com/kubernetes/kubernetes/blob/b2ec8b6fefac451a2dedafc4dd71f2f16c7a6abe/staging/src/k8s.io/client-go/tools/cache/shared_informer.go#L1236-L1259)

### Practice

让一个 observer 在通知中register另一个 observer，定义新 observer 是否收到当前 event。

此 implementation 的 snapshot 已确定，新 observer 从下一次 Publish 开始。为这个边界写 test；若需要严格 ordering，需改变 data structure 和 concurrency contract。

## 20 · State — behavioral

同一个 action，随内部 state 改变 behavior。

### Problem

Gate 在 locked 时拒绝 Pass，在 unlocked 时允许并重新 lock 定。把每种 action 都写成巨大 switch 会重复 transition logic。

### Design

Gate 把 Coin/Pass delegate 给 current mode。具体 state 决定 result 和 next state；locked.Coin 进入 unlocked，unlocked.Pass 再进入 locked。client 不直接选择 algorithm。

### Walkthrough

test 依次检查拒绝、投入 coin、允许、再次拒绝。第二个 coin 不累计多次通行许可，这是例子的明确 policy。

### Boundary

State 与 Strategy 的 shape 可能接近。Strategy 常由 caller 选择，State 常由内部 transition 推进。简单 enum switch 可能比多个 state type 更清楚；不要为了 taxonomy 拆碎两行逻辑。

### Role map

| GoF role | Go implementation |
|---|---|
| Context | `Gate` |
| State interface | `mode` |
| Concrete states | `locked / unlocked` |

### Minimal Go

```go
package state

import "errors"

var ErrLocked = errors.New("gate is locked")

type mode interface {
	coin(*Gate)
	pass(*Gate) error
}
type Gate struct{ current mode }

func New() *Gate            { return &Gate{current: locked{}} }
func (g *Gate) Coin()       { g.current.coin(g) }
func (g *Gate) Pass() error { return g.current.pass(g) }

type locked struct{}

func (locked) coin(g *Gate)     { g.current = unlocked{} }
func (locked) pass(*Gate) error { return ErrLocked }

type unlocked struct{}

func (unlocked) coin(*Gate)         {}
func (unlocked) pass(g *Gate) error { g.current = locked{}; return nil }
```

### Test

```go
package state

import (
	"errors"
	"testing"
)

func TestBehaviorChangesWithInternalState(t *testing.T) {
	g := New()
	if err := g.Pass(); !errors.Is(err, ErrLocked) {
		t.Fatal(err)
	}
	g.Coin()
	g.Coin()
	if err := g.Pass(); err != nil {
		t.Fatal(err)
	}
	if err := g.Pass(); !errors.Is(err, ErrLocked) {
		t.Fatal("did not return to locked state")
	}
}
```

```sh
go test -v ./state
```

### OSS · Go adaptation

text/template lexer 定义 stateFn func(*lexer) stateFn；nextItem 执行 state(l)，lexText 返回下一个 state function，例如 lexLeftDelim。它把 state behavior 放进 function，而不是 lab 的 state object，是自然的 Go adaptation。

- [golang/go / src/text/template/parse/lex.go: L109–L126](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/text/template/parse/lex.go#L109-L126)
- [golang/go / src/text/template/parse/lex.go: L224–L238](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/text/template/parse/lex.go#L224-L238)
- [golang/go / src/text/template/parse/lex.go: L270–L298](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/text/template/parse/lex.go#L270-L298)

### Practice

增加 maintenance state，拒绝 Coin 和 Pass，并明确谁能进入/离开。

transition authorization 属于 Context 的 public API；不要把 current state 暴露给任意 caller 修改。若多 goroutine 访问，需在 Context 层序列化 action。

## 21 · Strategy — behavioral

保持 workflow，替换其中一个 algorithm decision。

### Problem

同一组数需要 ascending 或 descending。 copy 两套 sorting implementation 会制造重复 code。

### Design

Sorted 接受 Less function，并把它 adaptation 为三路 comparator。function 就是 strategy；不必为两个 lambda 各建一个 struct。它 copy input，避免排序污染 caller slice。

### Walkthrough

test 使用同一 Sorted、两种 comparator，并检查 duplicate values 与 input ownership。变化的是决策，稳定的是 sorting flow。

### Boundary

Less 必须满足 strict weak ordering；随 call count 改变结果的 comparator 会破坏 algorithm contract。strategy 有 mutable state 时还需定义复用与 concurrency。

### Role map

| GoF role | Go implementation |
|---|---|
| Context | `Sorted` |
| Strategy | `Less` |
| Concrete strategies | `ascending / descending functions` |

### Minimal Go

```go
package strategy

import "slices"

type Less func(a, b int) bool

// Sorted accepts a strict weak ordering and leaves the input unchanged.
func Sorted(input []int, less Less) []int {
	result := slices.Clone(input)
	slices.SortFunc(result, func(a, b int) int {
		if less(a, b) {
			return -1
		}
		if less(b, a) {
			return 1
		}
		return 0
	})
	return result
}
```

### Test

```go
package strategy

import (
	"slices"
	"testing"
)

func TestReplaceOrderingWithoutChangingAlgorithm(t *testing.T) {
	source := []int{3, 1, 2, 2}
	up := Sorted(source, func(a, b int) bool { return a < b })
	down := Sorted(source, func(a, b int) bool { return a > b })
	if !slices.Equal(up, []int{1, 2, 2, 3}) || !slices.Equal(down, []int{3, 2, 2, 1}) || source[0] != 3 {
		t.Fatal(source, up, down)
	}
}
```

```sh
go test -v ./strategy
```

### OSS · Direct structure

BackOff interface 提供 NextBackOff/Reset；Retry call 该 policy 决定等待时间，Zero/Stop/Constant 各 implementation 不同 strategy。这个 v7 branch snapshot 的 API 固定在 source link，不应与其它 major version 混用。

- [cenkalti/backoff / backoff.go: L19–L70](https://github.com/cenkalti/backoff/blob/ffcfd8ab39e2910a1180ba0b7a02a52f0485adc9/backoff.go#L19-L70)
- [cenkalti/backoff / retry.go: L153–L172](https://github.com/cenkalti/backoff/blob/ffcfd8ab39e2910a1180ba0b7a02a52f0485adc9/retry.go#L153-L172)

### Practice

按绝对值排序，并为绝对值相同的数定义 tie-breaker。

先比较 magnitude，再比较原值，保持一致 ordering。生产 int 的最小负值不能直接安全取负，需要定义 overflow-safe magnitude 或限制 input domain。

## 22 · Template Method — behavioral

固定 algorithm skeleton，只开放指定 hooks。

### Problem

多种 import workflow 都遵循 Load→Transform→Save，但各步 implementation 不同，顺序和 failure handling 必须一致。

### Design

Run 拥有 sequencing 与 error wrapping，Steps 只填入 operation。任何阶段 error 都短路后续 stage；已有 cancellation 在入口及 Save 前检查。Go 用 composition 替代 abstract base class 的 overridable methods。

### Walkthrough

test 验证完整 trace、Transform failure 不 Save，以及已 cancellation 时连 Load 都不 call。Context 仍要求各 I/O hook 合作；Transform 本例不接受 Context。

### Boundary

与 Strategy 的区别在控制范围：Template 固定多步 flow，Strategy 替换某个决策或 algorithm。Go 没有 inheritance override，embedding 后 shadow method 不会让已有 base method 自动 dispatch 到外层。

### Role map

| GoF role | Go implementation |
|---|---|
| Template | `Run` |
| Primitive operations | `Steps` |
| Hooks | `Load / Transform / Save` |

### Minimal Go

```go
package templatemethod

import (
	"context"
	"fmt"
)

type Steps interface {
	Load(context.Context) ([]byte, error)
	Transform([]byte) ([]byte, error)
	Save(context.Context, []byte) error
}

// Run owns ordering and failure policy; injected Steps only fill the hooks.
func Run(ctx context.Context, s Steps) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	raw, err := s.Load(ctx)
	if err != nil {
		return fmt.Errorf("load: %w", err)
	}
	value, err := s.Transform(raw)
	if err != nil {
		return fmt.Errorf("transform: %w", err)
	}
	if err := ctx.Err(); err != nil {
		return err
	}
	if err := s.Save(ctx, value); err != nil {
		return fmt.Errorf("save: %w", err)
	}
	return nil
}
```

### Test

```go
package templatemethod

import (
	"context"
	"errors"
	"slices"
	"testing"
)

type hooks struct {
	trace []string
	fail  error
	saved string
}

func (h *hooks) Load(context.Context) ([]byte, error) {
	h.trace = append(h.trace, "load")
	return []byte("raw"), nil
}
func (h *hooks) Transform(b []byte) ([]byte, error) {
	h.trace = append(h.trace, "transform")
	return append(b, '!'), h.fail
}
func (h *hooks) Save(_ context.Context, b []byte) error {
	h.trace = append(h.trace, "save")
	h.saved = string(b)
	return nil
}
func TestSkeletonOwnsOrderAndShortCircuit(t *testing.T) {
	h := new(hooks)
	if err := Run(context.Background(), h); err != nil || h.saved != "raw!" || !slices.Equal(h.trace, []string{"load", "transform", "save"}) {
		t.Fatal(h, err)
	}
	stop := errors.New("stop")
	h = &hooks{fail: stop}
	if err := Run(context.Background(), h); !errors.Is(err, stop) || !slices.Equal(h.trace, []string{"load", "transform"}) {
		t.Fatal(h, err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	cancel()
	h = new(hooks)
	if err := Run(ctx, h); !errors.Is(err, context.Canceled) || len(h.trace) != 0 {
		t.Fatal(h, err)
	}
}
```

```sh
go test -v ./template-method
```

### OSS · Go adaptation

Backoff Retry 固定 operation→error classification→budget→delay→wait 的 skeleton，operation 与 BackOff 作为 extension points。它体现 template-style control inversion，同时也使用 Strategy；一个 implementation 可以包含多个 pattern。

- [cenkalti/backoff / retry.go: L108–L188](https://github.com/cenkalti/backoff/blob/ffcfd8ab39e2910a1180ba0b7a02a52f0485adc9/retry.go#L108-L188)

### Practice

要求 Transform 也可 cancellation，并避免 Save 在 cancellation 后执行。

把 Context 加到 Transform contract，由 hook 合作观察；保留 stage boundary 的检查。检查之后仍可能发生 cancellation，真正 I/O 必须继续使用同一个 Context。

## 23 · Visitor — behavioral

node kinds 稳定时，把新增 operation 放在 visitor。

### Problem

同一个 AST 既要 Eval，也要 Count。持续向每个 node 增加 method，会让数据结构承担越来越多 operation。

### Design

Accept 先根据 node 的具体 type call Visitor.Number/Add；visitor 再决定如何处理。这是显式 double dispatch。Eval 与 Count 是两组可替换 operation，node 不需要知道具体 visitor。

### Walkthrough

同一 tree 的 Eval=6、Count=5；再次 Eval 不变。比较 Interpreter：那里 Eval 是 expression 本身的 operation，这里把 operation 移到 visitor。

### Boundary

新增 visitor 容易，新增 node kind 则要修改 Visitor interface 和所有 visitor。这是 extension axis 的选择，不是无条件的 open/closed 完美解。

### Role map

| GoF role | Go implementation |
|---|---|
| Element | `Node` |
| Concrete elements | `Number / Add` |
| Visitor | `Visitor` |
| Operations | `Eval / Count` |

### Minimal Go

```go
package visitor

type Node interface{ Accept(Visitor) int }
type Visitor interface {
	Number(Number) int
	Add(Add) int
}
type Number int

func (n Number) Accept(v Visitor) int { return v.Number(n) }

type Add struct{ Left, Right Node }

func (a Add) Accept(v Visitor) int { return v.Add(a) }

type Eval struct{}

func (Eval) Number(n Number) int { return int(n) }
func (v Eval) Add(a Add) int     { return a.Left.Accept(v) + a.Right.Accept(v) }

type Count struct{}

func (Count) Number(Number) int { return 1 }
func (v Count) Add(a Add) int   { return 1 + a.Left.Accept(v) + a.Right.Accept(v) }
```

### Test

```go
package visitor

import "testing"

func TestAddOperationsWithoutChangingNodes(t *testing.T) {
	var tree Node = Add{Left: Number(1), Right: Add{Left: Number(2), Right: Number(3)}}
	if got := tree.Accept(Eval{}); got != 6 {
		t.Fatal(got)
	}
	if got := tree.Accept(Count{}); got != 5 {
		t.Fatal(got)
	}
	if got := tree.Accept(Eval{}); got != 6 {
		t.Fatal("visitor mutated tree")
	}
}
```

```sh
go test -v ./visitor
```

### OSS · Go adaptation

go/ast 明确提供 Visitor 与 Walk。它只有 Visit(Node)，以 type switch 组织 child traversal，返回 nil 可剪枝，Visit(nil) 表示离开。它不同于 lab 每种 node 一个 typed method 的经典 double dispatch，属于 Go adaptation。

- [golang/go / src/go/ast/walk.go: L12–L41](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/go/ast/walk.go#L12-L41)
- [golang/go / src/go/ast/walk.go: L350–L377](https://github.com/golang/go/blob/c5941983810b68ba93c30f0ef22c91ad63fb3e5c/src/go/ast/walk.go#L350-L377)

### Practice

增加一个打印 expression 的 operation；再增加 Multiply node，比较修改范围。

打印可以另加 visitor，但当前 Visitor 返回 int，需为新 result type 重设 contract，或让 visitor 保存输出 state。Multiply 必须扩展 node 与所有 visitor；这暴露了 Visitor 的真实成本。
