builder
任意の項目を指定して構造体を初期化するパターン
各項目の関数では自身を返し、メソッドチェインで構造体を初期化していく。
package main
type email struct {
from, to, subject, body string
}
type EmailBuilder struct {
email email
}
func (b *EmailBuilder) From(from string) *EmailBuilder {
b.email.from = from
return b
}
func (b *EmailBuilder) To(to string) *EmailBuilder {
b.email.to = to
return b
}
func (b *EmailBuilder) Subject(subject string) *EmailBuilder {
b.email.subject = subject
return b
}
func (b *EmailBuilder) Body(body string) *EmailBuilder {
b.email.body = body
return b
}
func sendMail(email *email) {
// 実際の処理
}
type build func(*EmailBuilder)
func SendEmail(action build) {
builder := EmailBuilder{}
action(&builder)
sendMail(&builder.email)
}
func main() {
SendEmail(func(b *EmailBuilder) {
b.From("hoge@example.com").
To("huga@example.com").
Subject("Sample").
Body("HOGE")
})
}
functional builder
任意の項目を指定して構造体を初期化するパターン。
上述のbuilderパターンの関数バージョン。
package main
type email struct {
from, to, subject, body string
}
type EmailBuilder struct {
email email
}
type Option func(b *EmailBuilder)
func From(from string) Option {
return func(b *EmailBuilder) {
b.email.from = from
}
}
func To(to string) Option {
return func(b *EmailBuilder) {
b.email.to = to
}
}
func Subject(subject string) Option {
return func(b *EmailBuilder) {
b.email.subject = subject
}
}
func Body(body string) Option {
return func(b *EmailBuilder) {
b.email.body = body
}
}
func sendMail(email *email) {
// 実際の処理
}
func SendEmail(ops ...Option) {
builder := EmailBuilder{}
for _, option := range ops {
option(&builder)
}
sendMail(&builder.email)
}
func main() {
SendEmail(From("hoge@example.com"),
To("huga@example.com"),
Subject("Sample"),
Body("HOGE"))
}
functional builder2
任意の項目を指定して構造体を初期化するパターンその2。
ネストした構造体を初期化する。
package main
import "fmt"
type User struct {
id int64
skill *Skill
item *Item
}
func (u *User) Fetch(userId int64) func(u *User) {
return func(u *User) {
// データを取ってくる感じの処理
// user := getUser(userId)
u.id = userId
}
}
type Skill struct {
id int64
detail *SkillDetail
}
func (s *Skill) Fetch() func(u *User) {
return func(u *User) {
// データを取ってくる感じの処理
// skill := getSkill(u.id)
s.id = 1
}
}
type Item struct {
id int64
}
func (i *Item) Fetch() func(u *User) {
return func(u *User) {
// データを取ってくる感じの処理
// item := getItem(u.id)
i.id = 1
}
}
type SkillDetail struct {
atk int64
}
func (sd *SkillDetail) Fetch() func(u *User) {
return func(u *User) {
// データを取ってくる感じの処理
// sd := getSkillDetail(u.skill.id)
sd.atk = 1000
}
}
func (u *User) Build(fetchLists ...func(u *User)) {
for _, fetch := range fetchLists {
fetch(u)
}
}
func New(user *User) {
user.Build(
user.Fetch(1),
user.skill.Fetch(),
user.skill.detail.Fetch(),
user.item.Fetch(),
)
fmt.Printf("%+v", user)
}
func main() {
New(&User{
skill: &Skill{
detail: &SkillDetail{},
},
item: &Item{},
})
}
factory
構造体の初期化をfactoryに移譲するパターン。
各factoryで初期化される構造体は別物だが、同じような振る舞いを持つ構造体であれば、下記のようにinterfaceを利用し、createメソッドを抽象化すれば、疎結合にできる。
package main
import "fmt"
type Employee struct {
Name, Position string
}
func (e *Employee) setName(name string) {
e.Name = name
}
type NewEmployeeFactory interface {
create() *Employee
}
type DeveloperFactory struct {}
func (DeveloperFactory) create() *Employee {
return &Employee{"", "Developer"}
}
type ManagerFactory struct {}
func (ManagerFactory) create() *Employee {
return &Employee{"", "Manager"}
}
func NewEmployee(factory NewEmployeeFactory) *Employee {
return factory.create()
}
func main() {
developerFactory := DeveloperFactory{}
dev := NewEmployee(developerFactory)
dev.setName("Sam")
managerFactory := ManagerFactory{}
manager := NewEmployee(managerFactory)
manager.setName("Bob")
fmt.Println(dev)
fmt.Println(manager)
}
decorator
構造体にいろんな機能をデコレーションするかのように追加できるパターン。
構造体の中にinterfaceがあるのがポイント。interfaceを満たしていれば、デコレートし続けることができる
package main
import "fmt"
type Shape interface {
Render() string
}
type Circle struct {
Radius float32
}
func (c *Circle) Render() string {
return fmt.Sprintf("Circle of radius %f",
c.Radius)
}
type ColoredShape struct {
Shape Shape
Color string
}
func (c *ColoredShape) Render() string {
return fmt.Sprintf("%s has the color %s",
c.Shape.Render(), c.Color)
}
type TransparentShape struct {
Shape Shape
Transparency float32
}
func (t *TransparentShape) Render() string {
return fmt.Sprintf("%s has %f%% transparency",
t.Shape.Render(), t.Transparency * 100.0)
}
func main() {
circle := Circle{2}
fmt.Println(circle.Render())
redCircle := ColoredShape{&circle, "Red"}
fmt.Println(redCircle.Render())
rhsCircle := TransparentShape{&redCircle, 0.5}
fmt.Println(rhsCircle.Render())
}
state
状態を構造体として表すパターン。
- On/Offの状態を構造体で持つ。On構造体はOffメソッド、Off構造体はOnメソッドを実装している。
- Offの状態の時のOffメソッド、Onの状態の時のOnメソッドの実行は、BaseState構造体のOn/Offメソッドでカバー。
- 状態を構造体で表し、状態遷移をメソッドで表すことで、if文を使うことなく、stateを扱えている。
package main
import "fmt"
type Switch struct {
State State
}
func NewSwitch() *Switch {
return &Switch{NewOffState()}
}
func (s *Switch) On() {
s.State.On(s)
}
func (s *Switch) Off() {
s.State.Off(s)
}
type State interface {
On(sw *Switch)
Off(sw *Switch)
}
type BaseState struct {}
func (s *BaseState) On(sw *Switch) {
fmt.Println("Light is already on")
}
func (s *BaseState) Off(sw *Switch) {
fmt.Println("Light is already off")
}
type OnState struct {
BaseState
}
func NewOnState() *OnState {
fmt.Println("Light turned on")
return &OnState{BaseState{}}
}
func (o *OnState) Off(sw *Switch) {
fmt.Println("Turning light off...")
sw.State = NewOffState()
}
type OffState struct {
BaseState
}
func NewOffState() *OffState {
fmt.Println("Light turned off")
return &OffState{BaseState{}}
}
func (o *OffState) On(sw *Switch) {
fmt.Println("Turning light on...")
sw.State = NewOnState()
}
func main() {
sw := NewSwitch()
sw.On()
sw.Off()
sw.Off()
}
strategy
機能の切り替えを容易にするパターン。
似たような機能が複数あったとして、その各機能のinterfaceを揃えてあげれば、具象的な処理を切り替えるだけでストラテジパターンが作れる。
- MarkdownListStrategy、HtmlListStrategyから生えてるメソッドでmarkdown、htmlの出力がされる。
- MarkdownListStrategy、HtmlListStrategyはListStrategyのinterfaceを満たしている。
- MarkdownListStrategy、HtmlListStrategyのどちらを使うかはSetOutputFormatで切り替えれる。
- AppendListメソッドでbufferに出力内容をためていく。二つのStrategyがListStrategyを満たしているため、ここの処理は共通化できる。
package main
import (
"fmt"
"strings"
)
type OutputFormat int
const (
Markdown OutputFormat = iota
Html
)
type ListStrategy interface {
Start(builder *strings.Builder)
End(builder *strings.Builder)
AddListItem(builder *strings.Builder, item string)
}
type MarkdownListStrategy struct {}
func (m *MarkdownListStrategy) Start(builder *strings.Builder) {}
func (m *MarkdownListStrategy) End(builder *strings.Builder) {}
func (m *MarkdownListStrategy) AddListItem(
builder *strings.Builder, item string) {
builder.WriteString(" * " + item + "\n")
}
type HtmlListStrategy struct {}
func (h *HtmlListStrategy) Start(builder *strings.Builder) {
builder.WriteString("<ul>\n")
}
func (h *HtmlListStrategy) End(builder *strings.Builder) {
builder.WriteString("</ul>\n")
}
func (h *HtmlListStrategy) AddListItem(builder *strings.Builder, item string) {
builder.WriteString(" <li>" + item + "</li>\n")
}
type TextProcessor struct {
builder strings.Builder
listStrategy ListStrategy
}
func NewTextProcessor(listStrategy ListStrategy) *TextProcessor {
return &TextProcessor{strings.Builder{}, listStrategy}
}
func (t *TextProcessor) SetOutputFormat(fmt OutputFormat) {
switch fmt {
case Markdown:
t.listStrategy = &MarkdownListStrategy{}
case Html:
t.listStrategy = &HtmlListStrategy{}
}
}
func (t *TextProcessor) AppendList(items []string) {
t.listStrategy.Start(&t.builder)
for _, item := range items {
t.listStrategy.AddListItem(&t.builder, item)
}
t.listStrategy.End(&t.builder)
}
func (t *TextProcessor) Reset() {
t.builder.Reset()
}
func (t *TextProcessor) String() string {
return t.builder.String()
}
func main() {
tp := NewTextProcessor(&MarkdownListStrategy{})
tp.AppendList([]string{ "foo", "bar", "baz" })
fmt.Println(tp)
tp.Reset()
tp.SetOutputFormat(Html)
tp.AppendList([]string{ "foo", "bar", "baz" })
fmt.Println(tp)
}
proxy
処理の前にある処理を挟みたい場合のパターン。interfaceの差異の吸収だったり、バリデーションだったり。
実際に実行したいメソッド名とプロキシのメソッド名は合わせる。利用者側にはプロキシが挟まっていることを意識させない。
package proxy
import "fmt"
type Driven interface {
Drive()
}
type Car struct {}
func (c *Car) Drive() {
fmt.Println("Car being driven")
}
type Driver struct {
Age int
}
type CarProxy struct {
car Car
driver *Driver
}
func (c *CarProxy) Drive() {
if c.driver.Age >= 18 {
c.car.Drive()
} else {
fmt.Println("Driver too young")
}
}
func NewCarProxy(driver *Driver) *CarProxy {
return &CarProxy{Car{}, driver}
}
func main() {
car := NewCarProxy(&Driver{12})
car.Drive()
}
再帰的な構造体
構造体の中にinterfaceを定義することで、再帰的な構造体が作れる。
- AdditionalExpressionとDoubleExpressionはleft/rightにExpressionのinterfaceを持つ。
- AdditionalExpressionとDoubleExpression自体がExpressionのinterfaceを満たしているので、left/rightを再帰的にすることができる
package main
import (
"fmt"
"strings"
)
type Expression interface {
Print(sb *strings.Builder)
}
type DoubleExpression struct {
value float64
}
func (d *DoubleExpression) Print(sb *strings.Builder) {
sb.WriteString(fmt.Sprintf("%g", d.value))
}
type AdditionExpression struct {
left, right Expression
}
func (a *AdditionExpression) Print(sb *strings.Builder) {
sb.WriteString("(")
a.left.Print(sb)
sb.WriteString("+")
a.right.Print(sb)
sb.WriteString(")")
}
func main() {
e := AdditionExpression{
&DoubleExpression{1},
&AdditionExpression{
left: &DoubleExpression{2},
right: &DoubleExpression{3},
},
}
sb := strings.Builder{}
e.Print(&sb)
fmt.Println(sb.String()) // 1+(2+3)
}