├── 1-solid-principles
├── README.md
├── dependency-inversion
│ └── main.go
├── interface-segregation
│ └── main.go
├── liscov-substitution
│ └── main.go
├── open-closed
│ └── main.go
└── single-responsibility
│ └── main.go
├── 2-creational
├── builder
│ ├── advanced
│ │ └── main.go
│ ├── basic
│ │ └── main.go
│ ├── functional
│ │ └── main.go
│ └── parameter
│ │ └── main.go
├── factories
│ ├── function
│ │ └── main.go
│ ├── generator
│ │ └── main.go
│ └── prototype
│ │ └── main.go
├── prototype
│ ├── copy-method
│ │ └── main.go
│ ├── deep-copy
│ │ └── main.go
│ ├── factory
│ │ └── main.go
│ └── serialization
│ │ └── main.go
└── singleton
│ ├── capitals.txt
│ └── main.go
├── 3-structural
├── adapter
│ └── main.go
├── bridge
│ └── main.go
├── composite
│ ├── drawing-example
│ │ └── main.go
│ ├── main.go
│ └── neural-network-example
│ │ └── main.go
├── decorator
│ └── main.go
├── facade
│ └── main.go
├── flyweight
│ ├── main.go
│ ├── text-formatting-example
│ │ └── main.go
│ └── usernames-example
│ │ └── main.go
└── proxy
│ ├── main.go
│ ├── protection-proxy
│ └── main.go
│ └── virtual-proxy
│ └── main.go
├── 4-behavioural
├── chain-of-responsibility
│ ├── broker-chain
│ │ └── main.go
│ ├── main.go
│ └── method-chain
│ │ └── main.go
├── command
│ ├── composite-command
│ │ └── main.go
│ ├── functional
│ │ └── main.go
│ └── main.go
├── interpreter
│ └── main.go
├── iterator
│ ├── main.go
│ └── tree-transversal
│ │ └── main.go
├── mediator
│ └── main.go
├── memento
│ ├── main.go
│ └── undo-redo
│ │ └── main.go
├── observer
│ ├── main.go
│ ├── property-changes
│ │ └── main.go
│ └── property-dependencies
│ │ └── main.go
├── state
│ ├── handmade-state-machine
│ │ └── main.go
│ ├── main.go
│ └── switch-based-state-machine
│ │ └── main.go
├── strategy
│ └── main.go
├── template-method
│ ├── functional-approach
│ │ └── main.go
│ └── main.go
└── visitor
│ ├── classic
│ └── main.go
│ ├── intrusive
│ └── main.go
│ ├── main.go
│ └── reflective
│ └── main.go
└── README.md
/1-solid-principles/README.md:
--------------------------------------------------------------------------------
1 | # SOLID Design Principles
2 |
3 | Five design principles introduced by Uncle Bob :)
4 |
5 | ## 1. Single Responsibility Principle
6 |
7 | A type/class or whatever should have a single primary responsibility.
8 | You can create another type to handle another responsibility.
9 | Maybe break into packages as well -> separation of concerns
10 |
11 | ## 2. Open-Closed Principle
12 |
13 | Types should be open for extension, but close for modification.
14 | It's well illustrated using Specification pattern.
15 |
16 | ## 3. Liskov Substitution Principle
17 |
18 | Stands that if there is any inheritance or extension of a class
19 | It should continue working and behaving correctly
20 | Based on the principles and assumptions it relies on.
21 | It's not truly applicable in Go, but we made a case that applies
22 |
23 | ## 4. Interface Segregation Principle
24 |
25 | You shouldn't put too much in an interface
26 | Makes sense to break into several interfaces
27 | So we won't end up having to implement and leaving alone methods that are not neccessary
28 |
29 | ## 5. Dependency Inversion Principle
30 |
31 | Dependency inversion != Dependency injection
32 | High level modules should not depend on low level modules
33 | Both should depend on abstractions
34 |
--------------------------------------------------------------------------------
/1-solid-principles/dependency-inversion/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // Dependency Inversion Principle
6 | // high level modules should not depend on low level modules
7 | // Both should depend on abstractions
8 |
9 | type Relationship int
10 |
11 | const (
12 | Parent Relationship = iota
13 | Child
14 | Sibling
15 | )
16 |
17 | type Person struct {
18 | name string
19 | // other useful stuff here
20 | }
21 |
22 | type Info struct {
23 | from *Person
24 | relationship Relationship
25 | to *Person
26 | }
27 |
28 | type RelationshipBrowser interface {
29 | FindAllChildrenOf(name string) []*Person
30 | }
31 |
32 | type Relationships struct {
33 | relations []Info
34 | }
35 |
36 | func (rs *Relationships) FindAllChildrenOf(name string) []*Person {
37 | result := make([]*Person, 0)
38 |
39 | for i, v := range rs.relations {
40 | if v.relationship == Parent &&
41 | v.from.name == name {
42 | result = append(result, rs.relations[i].to)
43 | }
44 | }
45 |
46 | return result
47 | }
48 |
49 | func (rs *Relationships) AddParentAndChild(parent, child *Person) {
50 | rs.relations = append(rs.relations,
51 | Info{parent, Parent, child})
52 | rs.relations = append(rs.relations,
53 | Info{child, Child, parent})
54 | }
55 |
56 | type Research struct {
57 | // relationships Relationships
58 | // if we have that attribute we are relying in a low-level module
59 | browser RelationshipBrowser // low-level
60 | }
61 |
62 | func (r *Research) Investigate() {
63 | // if there is any change in the way the relation has stored the relations
64 | // it breaks everything in a high-level module
65 | //relations := r.relationships.relations
66 | //for _, rel := range relations {
67 | // if rel.from.name == "John" &&
68 | // rel.relationship == Parent {
69 | // fmt.Println("John has a child called", rel.to.name)
70 | // }
71 | //}
72 |
73 | for _, p := range r.browser.FindAllChildrenOf("John") {
74 | fmt.Println("John has a child called", p.name)
75 | }
76 | }
77 |
78 | func main() {
79 | parent := Person{"John"}
80 | child1 := Person{"Chris"}
81 | child2 := Person{"Matt"}
82 |
83 | // low-level module
84 | relationships := Relationships{}
85 | relationships.AddParentAndChild(&parent, &child1)
86 | relationships.AddParentAndChild(&parent, &child2)
87 |
88 | research := Research{&relationships}
89 | research.Investigate()
90 | }
91 |
--------------------------------------------------------------------------------
/1-solid-principles/interface-segregation/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | type Document struct {
4 | }
5 |
6 | // this interface has three methods and force everyone that uses it to implement them
7 | type Machine interface {
8 | Print(d Document)
9 | Fax(d Document)
10 | Scan(d Document)
11 | }
12 |
13 | // ok if you need a multifunction device
14 | type MultiFunctionPrinter struct {
15 | // ...
16 | }
17 |
18 | func (m MultiFunctionPrinter) Print(d Document) {
19 |
20 | }
21 |
22 | func (m MultiFunctionPrinter) Fax(d Document) {
23 |
24 | }
25 |
26 | func (m MultiFunctionPrinter) Scan(d Document) {
27 |
28 | }
29 |
30 | type OldFashionedPrinter struct {
31 | // ...
32 | }
33 |
34 | func (o OldFashionedPrinter) Print(d Document) {
35 | // ok
36 | }
37 |
38 | func (o OldFashionedPrinter) Fax(d Document) {
39 | panic("operation not supported")
40 | }
41 |
42 | // Deprecated: ...
43 | func (o OldFashionedPrinter) Scan(d Document) {
44 | panic("operation not supported")
45 | }
46 |
47 | // better approach: split into several interfaces
48 | type Printer interface {
49 | Print(d Document)
50 | }
51 |
52 | type Scanner interface {
53 | Scan(d Document)
54 | }
55 |
56 | // printer only
57 | type MyPrinter struct {
58 | // ...
59 | }
60 |
61 | func (m MyPrinter) Print(d Document) {
62 | // ...
63 | }
64 |
65 | // combine interfaces
66 | type Photocopier struct{}
67 |
68 | func (p Photocopier) Scan(d Document) {
69 | //
70 | }
71 |
72 | func (p Photocopier) Print(d Document) {
73 | //
74 | }
75 |
76 | type MultiFunctionDevice interface {
77 | Printer
78 | Scanner
79 | }
80 |
81 | // interface combination + decorator
82 | type MultiFunctionMachine struct {
83 | printer Printer
84 | scanner Scanner
85 | }
86 |
87 | func (m MultiFunctionMachine) Print(d Document) {
88 | m.printer.Print(d)
89 | }
90 |
91 | func (m MultiFunctionMachine) Scan(d Document) {
92 | m.scanner.Scan(d)
93 | }
94 |
95 | func main() {
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/1-solid-principles/liscov-substitution/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Sized interface {
6 | GetWidth() int
7 | SetWidth(width int)
8 | GetHeight() int
9 | SetHeight(height int)
10 | }
11 |
12 | type Rectangle struct {
13 | width, height int
14 | }
15 |
16 | // vvv !! POINTER
17 | func (r *Rectangle) GetWidth() int {
18 | return r.width
19 | }
20 |
21 | func (r *Rectangle) SetWidth(width int) {
22 | r.width = width
23 | }
24 |
25 | func (r *Rectangle) GetHeight() int {
26 | return r.height
27 | }
28 |
29 | func (r *Rectangle) SetHeight(height int) {
30 | r.height = height
31 | }
32 |
33 | // modified LSP
34 | // If a function takes an interface and
35 | // works with a type T that implements this
36 | // interface, any structure that aggregates T
37 | // should also be usable in that function.
38 | type Square struct {
39 | Rectangle
40 | }
41 |
42 | func NewSquare(size int) *Square {
43 | sq := Square{}
44 | sq.width = size
45 | sq.height = size
46 | return &sq
47 | }
48 |
49 | func (s *Square) SetWidth(width int) {
50 | s.width = width
51 | s.height = width
52 | }
53 |
54 | func (s *Square) SetHeight(height int) {
55 | s.width = height
56 | s.height = height
57 | }
58 |
59 | // good extension
60 | type Square2 struct {
61 | size int
62 | }
63 |
64 | // this is pretty much a correct extension, cause rectangle will continue working
65 | func (s *Square2) Rectangle() Rectangle {
66 | return Rectangle{s.size, s.size}
67 | }
68 |
69 | func UseIt(sized Sized) {
70 | width := sized.GetWidth()
71 | sized.SetHeight(10)
72 | // this works for the rectangle
73 | // but it does not work for the square cause it does not know that the square will set the width again
74 | // then the width value that we have here is no longer the width value
75 | // So, it breaks the Liscov Substitution Principle
76 | expectedArea := 10 * width
77 | actualArea := sized.GetWidth() * sized.GetHeight()
78 | fmt.Print("Expected an area of ", expectedArea,
79 | ", but got ", actualArea, "\n")
80 | }
81 |
82 | func main() {
83 | rc := &Rectangle{2, 3}
84 | UseIt(rc)
85 |
86 | sq := NewSquare(5)
87 | UseIt(sq)
88 | }
89 |
--------------------------------------------------------------------------------
/1-solid-principles/open-closed/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Color int
6 |
7 | const (
8 | red Color = iota
9 | green
10 | blue
11 | )
12 |
13 | type Size int
14 |
15 | const (
16 | small Size = iota
17 | medium
18 | large
19 | )
20 |
21 | type Product struct {
22 | name string
23 | color Color
24 | size Size
25 | }
26 |
27 | // let's say we have a store and want to filter our products
28 | type Filter struct {
29 | }
30 |
31 | // now we can filter by color
32 | func (f *Filter) filterByColor(
33 | products []Product, color Color) []*Product {
34 | result := make([]*Product, 0)
35 |
36 | for i, v := range products {
37 | if v.color == color {
38 | result = append(result, &products[i])
39 | }
40 | }
41 |
42 | return result
43 | }
44 |
45 | // then make another to filter by size
46 | func (f *Filter) filterBySize(
47 | products []Product, size Size) []*Product {
48 | result := make([]*Product, 0)
49 |
50 | for i, v := range products {
51 | if v.size == size {
52 | result = append(result, &products[i])
53 | }
54 | }
55 |
56 | return result
57 | }
58 |
59 | // then make another to filter by size and color... but this seems weird
60 | // we should be rewriting what we already did
61 | // we should extend that
62 | func (f *Filter) filterBySizeAndColor(
63 | products []Product, size Size,
64 | color Color) []*Product {
65 | result := make([]*Product, 0)
66 |
67 | for i, v := range products {
68 | if v.size == size && v.color == color {
69 | result = append(result, &products[i])
70 | }
71 | }
72 |
73 | return result
74 | }
75 |
76 | // let's use the Specification pattern
77 | type Specification interface {
78 | IsSatisfied(p *Product) bool
79 | }
80 |
81 | type ColorSpecification struct {
82 | color Color
83 | }
84 |
85 | func (spec ColorSpecification) IsSatisfied(p *Product) bool {
86 | return p.color == spec.color
87 | }
88 |
89 | type SizeSpecification struct {
90 | size Size
91 | }
92 |
93 | func (spec SizeSpecification) IsSatisfied(p *Product) bool {
94 | return p.size == spec.size
95 | }
96 |
97 | type AndSpecification struct {
98 | first, second Specification
99 | }
100 |
101 | func (spec AndSpecification) IsSatisfied(p *Product) bool {
102 | return spec.first.IsSatisfied(p) &&
103 | spec.second.IsSatisfied(p)
104 | }
105 |
106 | type BetterFilter struct{}
107 |
108 | // and a better filter that just need to receive the specification which
109 | // implements the IsSatisfied method
110 | func (f *BetterFilter) Filter(
111 | products []Product, spec Specification) []*Product {
112 | result := make([]*Product, 0)
113 | for i, v := range products {
114 | if spec.IsSatisfied(&v) {
115 | result = append(result, &products[i])
116 | }
117 | }
118 | return result
119 | }
120 |
121 | func main_() {
122 | apple := Product{"Apple", green, small}
123 | tree := Product{"Tree", green, large}
124 | house := Product{"House", blue, large}
125 |
126 | products := []Product{apple, tree, house}
127 |
128 | fmt.Print("Green products (old):\n")
129 | f := Filter{}
130 | for _, v := range f.filterByColor(products, green) {
131 | fmt.Printf(" - %s is green\n", v.name)
132 | }
133 | // ^^^ BEFORE
134 |
135 | // vvv AFTER
136 | fmt.Print("Green products (new):\n")
137 | greenSpec := ColorSpecification{green}
138 | bf := BetterFilter{}
139 | for _, v := range bf.Filter(products, greenSpec) {
140 | fmt.Printf(" - %s is green\n", v.name)
141 | }
142 |
143 | largeSpec := SizeSpecification{large}
144 |
145 | largeGreenSpec := AndSpecification{largeSpec, greenSpec}
146 | fmt.Print("Large blue items:\n")
147 | for _, v := range bf.Filter(products, largeGreenSpec) {
148 | fmt.Printf(" - %s is large and green\n", v.name)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/1-solid-principles/single-responsibility/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/url"
7 | "strings"
8 | )
9 |
10 | var entryCount = 0
11 |
12 | type Journal struct {
13 | entries []string
14 | }
15 |
16 | // string method to be able to print this entity well :)
17 | func (j *Journal) String() string {
18 | return strings.Join(j.entries, "\n")
19 | }
20 |
21 | func (j *Journal) AddEntry(text string) int {
22 | entryCount++
23 | entry := fmt.Sprintf("%d: %s",
24 | entryCount,
25 | text)
26 | j.entries = append(j.entries, entry)
27 | return entryCount
28 | }
29 |
30 | func (j *Journal) RemoveEntry(index int) {
31 | // ...
32 | }
33 |
34 | // those three methods breaks the single responsibility design pattern
35 | // cause the responsiblity for Journal should not be to save or load info
36 | // but just to keep that
37 | func (j *Journal) Save(filename string) {
38 | _ = ioutil.WriteFile(filename,
39 | []byte(j.String()), 0644)
40 | }
41 |
42 | func (j *Journal) Load(filename string) {
43 |
44 | }
45 |
46 | func (j *Journal) LoadFromWeb(url *url.URL) {
47 |
48 | }
49 |
50 | var lineSeparator = "\n"
51 |
52 | // can work... but let's make this better
53 | func SaveToFile(j *Journal, filename string) {
54 | _ = ioutil.WriteFile(filename,
55 | []byte(strings.Join(j.entries, lineSeparator)), 0644)
56 | }
57 |
58 | // there is another entity in charge of the saving :D
59 | type Persistence struct {
60 | lineSeparator string
61 | }
62 |
63 | func (p *Persistence) saveToFile(j *Journal, filename string) {
64 | _ = ioutil.WriteFile(filename,
65 | []byte(strings.Join(j.entries, p.lineSeparator)), 0644)
66 | }
67 |
68 | func main_() {
69 | j := Journal{}
70 | j.AddEntry("I cried today.")
71 | j.AddEntry("I ate a bug")
72 | fmt.Println(strings.Join(j.entries, "\n"))
73 |
74 | // separate function
75 | SaveToFile(&j, "journal.txt")
76 |
77 | // using another entity
78 | p := Persistence{"\n"}
79 | p.saveToFile(&j, "journal.txt")
80 | }
81 |
--------------------------------------------------------------------------------
/2-creational/builder/advanced/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Person struct {
6 | StreetAddress, Postcode, City string
7 | CompanyName, Position string
8 | AnnualIncome int
9 | }
10 |
11 | type PersonBuilder struct {
12 | person *Person // needs to be inited
13 | }
14 |
15 | func NewPersonBuilder() *PersonBuilder {
16 | return &PersonBuilder{&Person{}} // initialize the person
17 | }
18 |
19 | // for building the entire person
20 | func (it *PersonBuilder) Build() *Person {
21 | return it.person
22 | }
23 |
24 | type PersonJobBuilder struct {
25 | // aggregates the person builder
26 | // so it has access to person too
27 | PersonBuilder
28 | }
29 |
30 | // returns the job builder
31 | func (it *PersonBuilder) Works() *PersonJobBuilder {
32 | return &PersonJobBuilder{*it}
33 | }
34 |
35 | // methods to set up the job
36 | func (pjb *PersonJobBuilder) At(
37 | companyName string) *PersonJobBuilder {
38 | pjb.person.CompanyName = companyName
39 | return pjb
40 | }
41 |
42 | func (pjb *PersonJobBuilder) AsA(
43 | position string) *PersonJobBuilder {
44 | pjb.person.Position = position
45 | return pjb
46 | }
47 |
48 | func (pjb *PersonJobBuilder) Earning(
49 | annualIncome int) *PersonJobBuilder {
50 | pjb.person.AnnualIncome = annualIncome
51 | return pjb
52 | }
53 |
54 | // aggregates the person builder
55 | // so it has access to person too
56 | type PersonAddressBuilder struct {
57 | PersonBuilder
58 | }
59 |
60 | // method to set up the location
61 | func (it *PersonBuilder) Lives() *PersonAddressBuilder {
62 | return &PersonAddressBuilder{*it}
63 | }
64 |
65 | func (it *PersonAddressBuilder) At(
66 | streetAddress string) *PersonAddressBuilder {
67 | it.person.StreetAddress = streetAddress
68 | return it
69 | }
70 |
71 | func (it *PersonAddressBuilder) In(
72 | city string) *PersonAddressBuilder {
73 | it.person.City = city
74 | return it
75 | }
76 |
77 | func (it *PersonAddressBuilder) WithPostcode(
78 | postcode string) *PersonAddressBuilder {
79 | it.person.Postcode = postcode
80 | return it
81 | }
82 |
83 | func main() {
84 | pb := NewPersonBuilder()
85 | pb.
86 | Lives().
87 | At("123 London Road").
88 | In("London").
89 | WithPostcode("SW12BC").
90 | Works().
91 | At("Fabrikam").
92 | AsA("Programmer").
93 | Earning(123000)
94 |
95 | person := pb.Build() //sum all together
96 | fmt.Println(*person) // initialize all params :D
97 | }
98 |
--------------------------------------------------------------------------------
/2-creational/builder/basic/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Builder
4 | // When a piecewise object construction it's complicated,
5 | // provide an API to do it succinctly.
6 |
7 | import (
8 | "fmt"
9 | "strings"
10 | )
11 |
12 | const (
13 | indentSize = 2
14 | )
15 |
16 | // this is our root element
17 | type HtmlElement struct {
18 | name, text string
19 | elements []HtmlElement
20 | }
21 |
22 | func (e *HtmlElement) String() string {
23 | return e.string(0)
24 | }
25 |
26 | // string method to print this as HTML
27 | func (e *HtmlElement) string(indent int) string {
28 | sb := strings.Builder{}
29 | i := strings.Repeat(" ", indentSize*indent)
30 | sb.WriteString(fmt.Sprintf("%s<%s>\n",
31 | i, e.name))
32 | if len(e.text) > 0 {
33 | sb.WriteString(strings.Repeat(" ",
34 | indentSize*(indent+1)))
35 | sb.WriteString(e.text)
36 | sb.WriteString("\n")
37 | }
38 |
39 | for _, el := range e.elements {
40 | sb.WriteString(el.string(indent + 1))
41 | }
42 | sb.WriteString(fmt.Sprintf("%s%s>\n",
43 | i, e.name))
44 | return sb.String()
45 | }
46 |
47 | // actually the builder
48 | type HtmlBuilder struct {
49 | rootName string
50 | root HtmlElement
51 | }
52 |
53 | func NewHtmlBuilder(rootName string) *HtmlBuilder {
54 | b := HtmlBuilder{rootName,
55 | HtmlElement{rootName, "", []HtmlElement{}}}
56 | return &b
57 | }
58 |
59 | func (b *HtmlBuilder) String() string {
60 | return b.root.String()
61 | }
62 |
63 | func (b *HtmlBuilder) AddChild(
64 | childName, childText string) {
65 | e := HtmlElement{childName, childText, []HtmlElement{}}
66 | b.root.elements = append(b.root.elements, e)
67 | }
68 |
69 | // returning the pointer allows us to continue adding childs
70 | func (b *HtmlBuilder) AddChildFluent(childName, childText string) *HtmlBuilder {
71 | e := HtmlElement{childName, childText, []HtmlElement{}}
72 | b.root.elements = append(b.root.elements, e)
73 |
74 | return b
75 | }
76 |
77 | func main() {
78 | // example without the builder.
79 | // kinda complex
80 | hello := "hello"
81 | sb := strings.Builder{}
82 | sb.WriteString("
")
83 | sb.WriteString(hello)
84 | sb.WriteString("
")
85 | fmt.Printf("%s\n", sb.String())
86 |
87 | words := []string{"hello", "world"}
88 | sb.Reset()
89 | // '
90 | sb.WriteString("")
91 | for _, v := range words {
92 | sb.WriteString("- ")
93 | sb.WriteString(v)
94 | sb.WriteString("
")
95 | }
96 | sb.WriteString("
")
97 | fmt.Println(sb.String())
98 |
99 | // using the builder it's cleaner and flexible
100 | b := NewHtmlBuilder("ul")
101 | b.AddChildFluent("li", "hello").
102 | AddChildFluent("li", "world")
103 | fmt.Println(b.String())
104 | }
105 |
--------------------------------------------------------------------------------
/2-creational/builder/functional/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Person struct {
6 | name, position string
7 | }
8 |
9 | type personMod func(*Person)
10 | type PersonBuilder struct {
11 | actions []personMod
12 | }
13 |
14 | func (b *PersonBuilder) Called(name string) *PersonBuilder {
15 | b.actions = append(b.actions, func(p *Person) {
16 | p.name = name
17 | })
18 | return b
19 | }
20 |
21 | func (b *PersonBuilder) Build() *Person {
22 | p := Person{}
23 | for _, a := range b.actions {
24 | a(&p)
25 | }
26 | return &p
27 | }
28 |
29 | // extend PersonBuilder
30 | func (b *PersonBuilder) WorksAsA(position string) *PersonBuilder {
31 | b.actions = append(b.actions, func(p *Person) {
32 | p.position = position
33 | })
34 | return b
35 | }
36 |
37 | func main() {
38 | b := PersonBuilder{}
39 | p := b.Called("Dmitri").WorksAsA("dev").Build()
40 | fmt.Println(*p)
41 | }
42 |
--------------------------------------------------------------------------------
/2-creational/builder/parameter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "strings"
4 |
5 | type email struct {
6 | from, to, subject, body string
7 | }
8 |
9 | type EmailBuilder struct {
10 | email email
11 | }
12 |
13 | func (b *EmailBuilder) From(from string) *EmailBuilder {
14 | if !strings.Contains(from, "@") {
15 | panic("email should contain @")
16 | }
17 |
18 | b.email.from = from
19 |
20 | return b
21 | }
22 |
23 | func (b *EmailBuilder) To(to string) *EmailBuilder {
24 | b.email.to = to
25 | return b
26 | }
27 |
28 | func (b *EmailBuilder) Subject(subject string) *EmailBuilder {
29 | b.email.subject = subject
30 |
31 | return b
32 | }
33 |
34 | func (b *EmailBuilder) Body(body string) *EmailBuilder
35 | b.email.body = body
36 |
37 | return b
38 | }
39 |
40 | func sendMailImpl(email *email) {
41 | // actually ends the email
42 | }
43 |
44 | type build func(*EmailBuilder)
45 |
46 | // this actually have access to the email
47 | func SendEmail(action build) {
48 | builder := EmailBuilder{}
49 | action(&builder
50 |
51 | // at this point, the email is initialized
52 | sendMailImpl(&builder.email)
53 | }
54 |
55 | func main() {
56 | SendEmail(func(b *EmailBuilder) {
57 | b.From("foo@bar.com").
58 | To("bar@baz.com").
59 | Subject("Meeting").
60 | Body("Hello, do you want to meet?")
61 | })
62 | }
--------------------------------------------------------------------------------
/2-creational/factories/function/main.go:
--------------------------------------------------------------------------------
1 | package factories
2 |
3 | // Factories
4 | // When the struct has too many fields and
5 | // you want to initialize them correctly.
6 | // Not each time you want to create them.
7 | // Wholesale object creation != builder (piecewise)
8 |
9 | import "fmt"
10 |
11 | type Person struct {
12 | Name string
13 | Age int
14 | }
15 |
16 | // factory function: just a single function handles all the creation
17 | func NewPerson(name string, age int) *Person {
18 | return &Person{name, age}
19 | }
20 |
21 | func main_() {
22 | // initialize directly
23 | p := Person{"John", 22}
24 | fmt.Println(p)
25 |
26 | // use a constructor
27 | // this gives us the ability to check values and do some business logic
28 | p2 := NewPerson("Jane", 21)
29 | p2.Age = 30
30 | fmt.Println(p2)
31 |
32 | // also we can use interfaces as the object returned in the constructor
33 | // just for tha sake of encapsulating the actual Person
34 | // and also allows us to use other underlying objects
35 | }
36 |
--------------------------------------------------------------------------------
/2-creational/factories/generator/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Employee struct {
6 | Name, Position string
7 | AnnualIncome int
8 | }
9 |
10 | // what if we want factories for specific roles?
11 |
12 | // functional approach
13 | func NewEmployeeFactory(position string,
14 | annualIncome int) func(name string) *Employee {
15 | return func(name string) *Employee {
16 | return &Employee{name, position, annualIncome}
17 | }
18 | }
19 |
20 | // structural approach
21 | type EmployeeFactory struct {
22 | Position string
23 | AnnualIncome int
24 | }
25 |
26 | func NewEmployeeFactory2(position string,
27 | annualIncome int) *EmployeeFactory {
28 | return &EmployeeFactory{position, annualIncome}
29 | }
30 |
31 | func (f *EmployeeFactory) Create(name string) *Employee {
32 | return &Employee{name, f.Position, f.AnnualIncome}
33 | }
34 |
35 | func main() {
36 | developerFactory := NewEmployeeFactory("Developer", 60000)
37 | managerFactory := NewEmployeeFactory("Manager", 80000)
38 |
39 | developer := developerFactory("Adam")
40 | fmt.Println(developer)
41 |
42 | manager := managerFactory("Jane")
43 | fmt.Println(manager)
44 |
45 | bossFactory := NewEmployeeFactory2("CEO", 100000)
46 | // can modify the fields
47 | bossFactory.AnnualIncome = 110000
48 | boss := bossFactory.Create("Sam")
49 | fmt.Println(boss)
50 | }
51 |
--------------------------------------------------------------------------------
/2-creational/factories/prototype/main.go:
--------------------------------------------------------------------------------
1 | package factories
2 |
3 | import "fmt"
4 |
5 | // imagine a wide types of employees with default values to be set
6 |
7 | type Employee struct {
8 | Name, Position string
9 | AnnualIncome int
10 | }
11 |
12 | const (
13 | Developer = iota
14 | Manager
15 | )
16 |
17 | // functional
18 | func NewEmployee(role int) *Employee {
19 | switch role {
20 | case Developer:
21 | return &Employee{"", "Developer", 60000}
22 | case Manager:
23 | return &Employee{"", "Manager", 80000}
24 | default:
25 | panic("unsupported role")
26 | }
27 | }
28 |
29 | func main() {
30 | m := NewEmployee(Manager)
31 | m.Name = "Sam"
32 | fmt.Println(m)
33 | }
34 |
--------------------------------------------------------------------------------
/2-creational/prototype/copy-method/main.go:
--------------------------------------------------------------------------------
1 | package prototype
2 |
3 | import "fmt"
4 |
5 | type Address struct {
6 | StreetAddress, City, Country string
7 | }
8 |
9 | // this solves the problems with the deep-copy method in the other folder
10 | // cause initializes a new Address
11 | func (a *Address) DeepCopy() *Address {
12 | return &Address{
13 | a.StreetAddress,
14 | a.City,
15 | a.Country}
16 | }
17 |
18 | type Person struct {
19 | Name string
20 | Address *Address
21 | Friends []string
22 | }
23 |
24 | func (p *Person) DeepCopy() *Person {
25 | q := *p // copies Name
26 | q.Address = p.Address.DeepCopy()
27 | copy(q.Friends, p.Friends)
28 | return &q
29 | }
30 |
31 | func main() {
32 | john := Person{"John",
33 | &Address{"123 London Rd", "London", "UK"},
34 | []string{"Chris", "Matt"}}
35 |
36 | jane := john.DeepCopy()
37 | jane.Name = "Jane"
38 | jane.Address.StreetAddress = "321 Baker St"
39 | jane.Friends = append(jane.Friends, "Angela")
40 |
41 | fmt.Println(john, john.Address)
42 | fmt.Println(jane, jane.Address)
43 | }
44 |
--------------------------------------------------------------------------------
/2-creational/prototype/deep-copy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // when it's easier to copy an existing object to fully initialize a new one
4 |
5 | import "fmt"
6 |
7 | type Address struct {
8 | StreetAddress, City, Country string
9 | }
10 |
11 | type Person struct {
12 | Name string
13 | Address *Address
14 | }
15 |
16 | func main() {
17 | john := Person{"John",
18 | &Address{"123 London Rd", "London", "UK"}}
19 |
20 | //jane := john
21 |
22 | // shallow copy
23 | //jane.Name = "Jane" // ok
24 |
25 | //jane.Address.StreetAddress = "321 Baker St"
26 |
27 | //fmt.Println(john.Name, john.Address)
28 | //fmt.Println(jane.Name, jane. Address)
29 |
30 | // what you really want
31 | jane := john
32 |
33 | // create a new pointer
34 | jane.Address = &Address{
35 | john.Address.StreetAddress,
36 | john.Address.City,
37 | john.Address.Country}
38 |
39 | jane.Name = "Jane" // ok
40 |
41 | jane.Address.StreetAddress = "321 Baker St"
42 |
43 | fmt.Println(john.Name, john.Address)
44 | fmt.Println(jane.Name, jane.Address)
45 | }
46 |
--------------------------------------------------------------------------------
/2-creational/prototype/factory/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "fmt"
7 | )
8 |
9 | type Address struct {
10 | Suite int
11 | StreetAddress, City string
12 | }
13 |
14 | type Employee struct {
15 | Name string
16 | Office Address
17 | }
18 |
19 | func (p *Employee) DeepCopy() *Employee {
20 | // note: no error handling below
21 | b := bytes.Buffer{}
22 | e := gob.NewEncoder(&b)
23 | _ = e.Encode(p)
24 |
25 | // peek into structure
26 | //fmt.Println(string(b.Bytes()))
27 |
28 | d := gob.NewDecoder(&b)
29 | result := Employee{}
30 | _ = d.Decode(&result)
31 | return &result
32 | }
33 |
34 | // employee factory
35 | // either a struct or some functions
36 | var mainOffice = Employee{
37 | "", Address{0, "123 East Dr", "London"}}
38 | var auxOffice = Employee{
39 | "", Address{0, "66 West Dr", "London"}}
40 |
41 | // utility method for configuring emp
42 | // ↓ lowercase
43 | func newEmployee(proto *Employee,
44 | name string, suite int) *Employee {
45 | result := proto.DeepCopy()
46 | result.Name = name
47 | result.Office.Suite = suite
48 | return result
49 | }
50 |
51 | // there you jjust change the office basic object
52 | func NewMainOfficeEmployee(
53 | name string, suite int) *Employee {
54 | return newEmployee(&mainOffice, name, suite)
55 | }
56 |
57 | func NewAuxOfficeEmployee(
58 | name string, suite int) *Employee {
59 | return newEmployee(&auxOffice, name, suite)
60 | }
61 |
62 | func main() {
63 | // most people work in one of two offices
64 |
65 | //john := Employee{"John",
66 | // Address{100, "123 East Dr", "London"}}
67 | //
68 | //jane := john.DeepCopy()
69 | //jane.Name = "Jane"
70 | //jane.Office.Suite = 200
71 | //jane.Office.StreetAddress = "66 West Dr"
72 |
73 | john := NewMainOfficeEmployee("John", 100)
74 | jane := NewAuxOfficeEmployee("Jane", 200)
75 |
76 | fmt.Println(john)
77 | fmt.Println(jane)
78 | }
79 |
--------------------------------------------------------------------------------
/2-creational/prototype/serialization/main.go:
--------------------------------------------------------------------------------
1 | package prototype
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "fmt"
7 | )
8 |
9 | type Address struct {
10 | StreetAddress, City, Country string
11 | }
12 |
13 | type Person struct {
14 | Name string
15 | Address *Address
16 | Friends []string
17 | }
18 |
19 | func (p *Person) DeepCopy() *Person {
20 | // note: no error handling below
21 | b := bytes.Buffer{}
22 | e := gob.NewEncoder(&b)
23 | _ = e.Encode(p)
24 |
25 | // peek into structure
26 | fmt.Println(string(b.Bytes()))
27 |
28 | d := gob.NewDecoder(&b)
29 | result := Person{}
30 | _ = d.Decode(&result)
31 |
32 | return &result
33 | }
34 |
35 | func main() {
36 | john := Person{"John",
37 | &Address{"123 London Rd", "London", "UK"},
38 | []string{"Chris", "Matt", "Sam"}}
39 |
40 | jane := john.DeepCopy()
41 | jane.Name = "Jane"
42 | jane.Address.StreetAddress = "321 Baker St"
43 | jane.Friends = append(jane.Friends, "Jill")
44 |
45 | fmt.Println(john, john.Address)
46 | fmt.Println(jane, jane.Address)
47 | }
48 |
--------------------------------------------------------------------------------
/2-creational/singleton/capitals.txt:
--------------------------------------------------------------------------------
1 | Tokyo
2 | 33200000
3 | New York
4 | 17800000
5 | Sao Paulo
6 | 17700000
7 | Seoul
8 | 17500000
9 | Mexico City
10 | 17400000
11 | Osaka
12 | 16425000
13 | Manila
14 | 14750000
15 | Mumbai
16 | 14350000
17 | Delhi
18 | 14300000
19 | Jakarta
20 | 14250000
--------------------------------------------------------------------------------
/2-creational/singleton/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | "sync"
9 | )
10 |
11 | // Singleton
12 | // When we only want a module to be instantied one
13 |
14 | // think of a module as a singleton
15 | type Database interface {
16 | GetPopulation(name string) int
17 | }
18 |
19 | // look that the singleton it's private
20 | type singletonDatabase struct {
21 | capitals map[string]int
22 | }
23 |
24 | func (db *singletonDatabase) GetPopulation(
25 | name string) int {
26 | return db.capitals[name]
27 | }
28 |
29 | // both init and sync.Once are thread-safe
30 | // but only sync.Once is lazy
31 |
32 | var once sync.Once
33 |
34 | //var instance *singletonDatabase
35 | //
36 | //func GetSingletonDatabase() *singletonDatabase {
37 | // once.Do(func() {
38 | // if instance == nil {
39 | // instance = &singletonDatabase{}
40 | // }
41 | // })
42 | // return instance
43 | //}
44 |
45 | var instance Database
46 |
47 | // init() — we could, but it's not lazy
48 |
49 | func readData(path string) (map[string]int, error) {
50 | file, err := os.Open(path)
51 | if err != nil {
52 | return nil, err
53 | }
54 |
55 | defer file.Close()
56 |
57 | scanner := bufio.NewScanner(file)
58 | scanner.Split(bufio.ScanLines)
59 |
60 | result := map[string]int{}
61 |
62 | for scanner.Scan() {
63 | k := scanner.Text()
64 | scanner.Scan()
65 | v, _ := strconv.Atoi(scanner.Text())
66 | result[k] = v
67 | }
68 |
69 | return result, nil
70 | }
71 |
72 | func GetSingletonDatabase() Database {
73 | once.Do(func() {
74 | db := singletonDatabase{}
75 |
76 | caps, err := readData("./2-creational/singleton/capitals.txt")
77 | if err == nil {
78 | db.capitals = caps
79 | }
80 | instance = &db
81 | })
82 | return instance
83 | }
84 |
85 | // this breaks the dependency inversion principle cause we are reliying on live data
86 | // we want to rely on abstractions
87 | // then we can test
88 | func GetTotalPopulation(cities []string) int {
89 | result := 0
90 | for _, city := range cities {
91 | result += GetSingletonDatabase().GetPopulation(city)
92 | }
93 |
94 | return result
95 | }
96 |
97 | func GetTotalPopulationEx(db Database, cities []string) int {
98 | result := 0
99 | for _, city := range cities {
100 | result += db.GetPopulation(city)
101 | }
102 |
103 | return result
104 | }
105 |
106 | type DummyDatabase struct {
107 | dummyData map[string]int
108 | }
109 |
110 | func (d *DummyDatabase) GetPopulation(name string) int {
111 | if len(d.dummyData) == 0 {
112 | d.dummyData = map[string]int{
113 | "alpha": 1,
114 | "beta": 2,
115 | "gamma": 3}
116 | }
117 | return d.dummyData[name]
118 | }
119 |
120 | func main() {
121 | db := GetSingletonDatabase()
122 | pop := db.GetPopulation("Seoul")
123 | fmt.Println("Pop of Seoul = ", pop)
124 |
125 | cities := []string{"Seoul", "Mexico City"}
126 | //tp := GetTotalPopulation(cities)
127 | tp := GetTotalPopulationEx(GetSingletonDatabase(), cities)
128 |
129 | ok := tp == (17500000 + 17400000) // testing on live data
130 | fmt.Println(ok)
131 |
132 | names := []string{"alpha", "gamma"} // expect 4
133 | tp = GetTotalPopulationEx(&DummyDatabase{}, names)
134 | ok = tp == 4
135 | fmt.Println(ok)
136 | }
137 |
--------------------------------------------------------------------------------
/3-structural/adapter/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Adapter
4 | // Getting the interface you need for the interface you have
5 |
6 | import (
7 | "crypto/md5"
8 | "encoding/json"
9 | "fmt"
10 | "strings"
11 | )
12 |
13 | // just a helper func
14 | func minmax(a, b int) (int, int) {
15 | if a < b {
16 | return a, b
17 | }
18 |
19 | return b, a
20 | }
21 |
22 | type Line struct {
23 | X1, Y1, X2, Y2 int
24 | }
25 |
26 | type VectorImage struct {
27 | Lines []Line
28 | }
29 |
30 | // the "interface" you are given
31 | func NewRectangle(width, height int) *VectorImage {
32 | width -= 1
33 | height -= 1
34 | return &VectorImage{[]Line{
35 | {0, 0, width, 0},
36 | {0, 0, 0, height},
37 | {width, 0, width, height},
38 | {0, height, width, height}}}
39 | }
40 |
41 | type Point struct {
42 | X, Y int
43 | }
44 |
45 | // the interface you need
46 | type RasterImage interface {
47 | GetPoints() []Point // points, not vectors
48 | }
49 |
50 | // helper func to draw
51 | func DrawPoints(owner RasterImage) string {
52 | // this first part is made for caching
53 | // so we don't have to re create local vars
54 | maxX, maxY := 0, 0
55 | points := owner.GetPoints()
56 | for _, pixel := range points {
57 | if pixel.X > maxX {
58 | maxX = pixel.X
59 | }
60 | if pixel.Y > maxY {
61 | maxY = pixel.Y
62 | }
63 | }
64 | maxX += 1
65 | maxY += 1
66 |
67 | data := make([][]rune, maxY)
68 | for i := 0; i < maxY; i++ {
69 | data[i] = make([]rune, maxX)
70 | for j := range data[i] {
71 | data[i][j] = ' '
72 | }
73 | }
74 |
75 | for _, point := range points {
76 | data[point.Y][point.X] = '*'
77 | }
78 |
79 | b := strings.Builder{}
80 | for _, line := range data {
81 | b.WriteString(string(line))
82 | b.WriteRune('\n')
83 | }
84 |
85 | return b.String()
86 | }
87 |
88 | // problem: I want to print a RasterImage
89 | // but I can only make a VectorImage
90 | type vectorToRasterAdapter struct {
91 | points []Point
92 | }
93 |
94 | var pointCache = map[[16]byte][]Point{}
95 |
96 | func (a *vectorToRasterAdapter) addLine(line Line) {
97 | left, right := minmax(line.X1, line.X2)
98 | top, bottom := minmax(line.Y1, line.Y2)
99 | dx := right - left
100 | dy := line.Y2 - line.Y1
101 |
102 | if dx == 0 {
103 | for y := top; y <= bottom; y++ {
104 | a.points = append(a.points, Point{left, y})
105 | }
106 | } else if dy == 0 {
107 | for x := left; x <= right; x++ {
108 | a.points = append(a.points, Point{x, top})
109 | }
110 | }
111 |
112 | fmt.Println("generated", len(a.points), "points")
113 | }
114 | func (a *vectorToRasterAdapter) addLineCached(line Line) {
115 | hash := func(obj interface{}) [16]byte {
116 | bytes, _ := json.Marshal(obj)
117 | return md5.Sum(bytes)
118 | }
119 | h := hash(line)
120 | if pts, ok := pointCache[h]; ok {
121 | for _, pt := range pts {
122 | a.points = append(a.points, pt)
123 | }
124 | return
125 | }
126 |
127 | left, right := minmax(line.X1, line.X2)
128 | top, bottom := minmax(line.Y1, line.Y2)
129 | dx := right - left
130 | dy := line.Y2 - line.Y1
131 |
132 | if dx == 0 {
133 | for y := top; y <= bottom; y++ {
134 | a.points = append(a.points, Point{left, y})
135 | }
136 | } else if dy == 0 {
137 | for x := left; x <= right; x++ {
138 | a.points = append(a.points, Point{x, top})
139 | }
140 | }
141 |
142 | // be sure to add these to the cache
143 | pointCache[h] = a.points
144 | fmt.Println("generated", len(a.points), "points")
145 | }
146 |
147 | func (a vectorToRasterAdapter) GetPoints() []Point {
148 | return a.points
149 | }
150 |
151 | func VectorToRaster(vi *VectorImage) RasterImage {
152 | adapter := vectorToRasterAdapter{}
153 | for _, line := range vi.Lines {
154 | adapter.addLineCached(line)
155 | }
156 |
157 | return adapter // as RasterImage
158 | }
159 |
160 | func main() {
161 | rc := NewRectangle(6, 4)
162 | a := VectorToRaster(rc) // adapter!
163 | _ = VectorToRaster(rc) // adapter! in this second execution it does not create the points again. They are cached
164 | fmt.Print(DrawPoints(a))
165 | }
166 |
--------------------------------------------------------------------------------
/3-structural/bridge/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Bridge
4 | // Connecting components together through abstractions
5 |
6 | // avoid the complexity of crossmultiple
7 | // imagine you have 5 types of figures and 2 ways of drawing
8 | // you would have to create 10 different method to do that
9 | // with bridge we have an interface that handles that
10 |
11 | import "fmt"
12 |
13 | // the interface that allows us to specify the type that we want
14 | type Renderer interface {
15 | RenderCircle(radius float32)
16 | }
17 |
18 | // one type of renderer
19 | type VectorRenderer struct {
20 | }
21 |
22 | func (v *VectorRenderer) RenderCircle(radius float32) {
23 | fmt.Println("Drawing a circle of radius", radius)
24 | }
25 |
26 | // other type of renderer
27 | type RasterRenderer struct {
28 | Dpi int
29 | }
30 |
31 | func (r *RasterRenderer) RenderCircle(radius float32) {
32 | fmt.Println("Drawing pixels for circle of radius", radius)
33 | }
34 |
35 | // has a renderer, and you can define it as you want
36 | type Circle struct {
37 | renderer Renderer
38 | radius float32
39 | }
40 |
41 | // use the abstraction
42 | func (c *Circle) Draw() {
43 | c.renderer.RenderCircle(c.radius)
44 | }
45 |
46 | // receive the renderer
47 | func NewCircle(renderer Renderer, radius float32) *Circle {
48 | return &Circle{renderer: renderer, radius: radius}
49 | }
50 |
51 | func (c *Circle) Resize(factor float32) {
52 | c.radius *= factor
53 | }
54 |
55 | func main() {
56 | raster := RasterRenderer{}
57 | vector := VectorRenderer{}
58 |
59 | circle := NewCircle(&vector, 5)
60 | circle.Draw()
61 |
62 | circle = NewCircle(&raster, 10)
63 | circle.Draw()
64 | }
65 |
--------------------------------------------------------------------------------
/3-structural/composite/drawing-example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // this is our basic object
9 | type GraphicObject struct {
10 | Name, Color string
11 | // there we can make a group for graphic objects that contains graphic objects
12 | Children []GraphicObject
13 | }
14 |
15 | func (g *GraphicObject) String() string {
16 | sb := strings.Builder{}
17 | g.print(&sb, 0)
18 | return sb.String()
19 | }
20 |
21 | func (g *GraphicObject) print(sb *strings.Builder, depth int) {
22 | sb.WriteString(strings.Repeat("*", depth))
23 | if len(g.Color) > 0 {
24 | sb.WriteString(g.Color)
25 | sb.WriteRune(' ')
26 | }
27 | sb.WriteString(g.Name)
28 | sb.WriteRune('\n')
29 |
30 | for _, child := range g.Children {
31 | child.print(sb, depth+1)
32 | }
33 | }
34 |
35 | func NewCircle(color string) *GraphicObject {
36 | return &GraphicObject{"Circle", color, nil}
37 | }
38 |
39 | func NewSquare(color string) *GraphicObject {
40 | return &GraphicObject{"Square", color, nil}
41 | }
42 |
43 | func main() {
44 | drawing := GraphicObject{"My Drawing", "", nil}
45 | drawing.Children = append(drawing.Children, *NewSquare("Red"))
46 | drawing.Children = append(drawing.Children, *NewCircle("Yellow"))
47 |
48 | // those have a double ** indicating that it's a deeper level
49 | group := GraphicObject{"Group 1", "", nil}
50 | group.Children = append(group.Children, *NewCircle("Blue"))
51 | group.Children = append(group.Children, *NewSquare("Blue"))
52 | drawing.Children = append(drawing.Children, group)
53 |
54 | fmt.Println(drawing.String())
55 | }
56 |
--------------------------------------------------------------------------------
/3-structural/composite/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Composite
4 | // Treating individual and aggregate objects uniformily (treat them the same)
5 | // Let us make compound objects that are treated as simplier objects
6 |
--------------------------------------------------------------------------------
/3-structural/composite/neural-network-example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // as long as it's implemented we can sum them up together
4 | type NeuronInterface interface {
5 | Iter() []*Neuron
6 | }
7 |
8 | // basic struct
9 | type Neuron struct {
10 | In, Out []*Neuron
11 | }
12 |
13 | // return itself
14 | func (n *Neuron) Iter() []*Neuron {
15 | return []*Neuron{n}
16 | }
17 |
18 | func (n *Neuron) ConnectTo(other *Neuron) {
19 | n.Out = append(n.Out, other)
20 | other.In = append(other.In, n)
21 | }
22 |
23 | // the different one
24 | type NeuronLayer struct {
25 | Neurons []Neuron
26 | }
27 |
28 | // but implements the Iter
29 | func (n *NeuronLayer) Iter() []*Neuron {
30 | result := make([]*Neuron, 0)
31 | for i := range n.Neurons {
32 | result = append(result, &n.Neurons[i])
33 | }
34 |
35 | return result
36 | }
37 |
38 | func NewNeuronLayer(count int) *NeuronLayer {
39 | return &NeuronLayer{make([]Neuron, count)}
40 | }
41 |
42 | // now this method let us to sum up all types of neurons
43 | func Connect(left, right NeuronInterface) {
44 | for _, l := range left.Iter() {
45 | for _, r := range right.Iter() {
46 | l.ConnectTo(r)
47 | }
48 | }
49 | }
50 |
51 | func main() {
52 | neuron1, neuron2 := &Neuron{}, &Neuron{}
53 | layer1, layer2 := NewNeuronLayer(3), NewNeuronLayer(4)
54 |
55 | //neuron1.ConnectTo(&neuron2)
56 |
57 | Connect(neuron1, neuron2)
58 | Connect(neuron1, layer1)
59 | Connect(layer2, neuron1)
60 | Connect(layer1, layer2)
61 | }
62 |
--------------------------------------------------------------------------------
/3-structural/decorator/main.go:
--------------------------------------------------------------------------------
1 | package decorator
2 |
3 | // Decorator
4 | // Adding behaviour without modifying the type itself
5 | // Not rewriting or altering existent code (Open-close principle)
6 | // Keep new functionality separate (single responsibility principle)
7 | // Through embedding
8 |
9 | import "fmt"
10 |
11 | // our base interface
12 | type Shape interface {
13 | Render() string
14 | }
15 |
16 | type Circle struct {
17 | Radius float32
18 | }
19 |
20 | func (c *Circle) Render() string {
21 | return fmt.Sprintf("Circle of radius %f",
22 | c.Radius)
23 | }
24 |
25 | func (c *Circle) Resize(factor float32) {
26 | c.Radius *= factor
27 | }
28 |
29 | type Square struct {
30 | Side float32
31 | }
32 |
33 | func (s *Square) Render() string {
34 | return fmt.Sprintf("Square with side %f", s.Side)
35 | }
36 |
37 | // possible, but not generic enough
38 | type ColoredSquare struct {
39 | Square
40 | Color string
41 | }
42 |
43 | // more generic c:
44 | type ColoredShape struct {
45 | Shape Shape
46 | Color string
47 | }
48 |
49 | func (c *ColoredShape) Render() string {
50 | return fmt.Sprintf("%s has the color %s",
51 | c.Shape.Render(), c.Color)
52 | }
53 |
54 | type TransparentShape struct {
55 | Shape Shape
56 | Transparency float32
57 | }
58 |
59 | func (t *TransparentShape) Render() string {
60 | return fmt.Sprintf("%s has %f%% transparency",
61 | t.Shape.Render(), t.Transparency*100.0)
62 | }
63 |
64 | func main() {
65 | circle := Circle{2}
66 | fmt.Println(circle.Render())
67 |
68 | redCircle := ColoredShape{&circle, "Red"}
69 | fmt.Println(redCircle.Render())
70 |
71 | rhsCircle := TransparentShape{&redCircle, 0.5}
72 | fmt.Println(rhsCircle.Render())
73 | }
74 |
--------------------------------------------------------------------------------
/3-structural/facade/main.go:
--------------------------------------------------------------------------------
1 | package facade
2 |
3 | import "fmt"
4 |
5 | // Facade
6 | // Exposing several components through a single interface
7 |
8 | // basic struct
9 | type Buffer struct {
10 | width, height int
11 | buffer []rune
12 | }
13 |
14 | func NewBuffer(width, height int) *Buffer {
15 | return &Buffer{width, height,
16 | make([]rune, width*height)}
17 | }
18 |
19 | func (b *Buffer) At(index int) rune {
20 | return b.buffer[index]
21 | }
22 |
23 | // more complex
24 | type Viewport struct {
25 | buffer *Buffer
26 | offset int
27 | }
28 |
29 | func NewViewport(buffer *Buffer) *Viewport {
30 | return &Viewport{buffer: buffer}
31 | }
32 |
33 | func (v *Viewport) GetCharacterAt(index int) rune {
34 | return v.buffer.At(v.offset + index)
35 | }
36 |
37 | // a facade over buffers and viewports
38 | type Console struct {
39 | buffers []*Buffer
40 | viewports []*Viewport
41 | offset int
42 | }
43 |
44 | // way to initialize in a default way
45 | func NewConsole() *Console {
46 | b := NewBuffer(10, 10)
47 | v := NewViewport(b)
48 | return &Console{[]*Buffer{b}, []*Viewport{v}, 0}
49 | }
50 |
51 | func (c *Console) GetCharacterAt(index int) rune {
52 | return c.viewports[0].GetCharacterAt(index)
53 | }
54 |
55 | func main() {
56 | c := NewConsole()
57 | u := c.GetCharacterAt(1)
58 |
59 | fmt.Println(u)
60 | }
61 |
--------------------------------------------------------------------------------
/3-structural/flyweight/main.go:
--------------------------------------------------------------------------------
1 | package flyweight
2 |
3 | // Flyweight
4 | // Space optimization!
5 | // By storing externally the data associated with similar objects
6 |
--------------------------------------------------------------------------------
/3-structural/flyweight/text-formatting-example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "unicode"
7 | )
8 |
9 | // this example focuses more in understanding the fundamentals
10 | // the how to
11 |
12 | // this is the unefficient way to do this
13 | type FormattedText struct {
14 | plainText string
15 | capitalize []bool
16 | }
17 |
18 | func (f *FormattedText) String() string {
19 | sb := strings.Builder{}
20 | for i := 0; i < len(f.plainText); i++ {
21 | c := f.plainText[i]
22 | if f.capitalize[i] {
23 | sb.WriteRune(unicode.ToUpper(rune(c)))
24 | } else {
25 | sb.WriteRune(rune(c))
26 | }
27 | }
28 | return sb.String()
29 | }
30 |
31 | func NewFormattedText(plainText string) *FormattedText {
32 | return &FormattedText{plainText,
33 | make([]bool, len(plainText))}
34 | }
35 |
36 | func (f *FormattedText) Capitalize(start, end int) {
37 | for i := start; i <= end; i++ {
38 | f.capitalize[i] = true
39 | }
40 | }
41 |
42 | // a more efficient way to do that
43 | type TextRange struct {
44 | Start, End int
45 | Capitalize, Bold, Italic bool
46 | }
47 |
48 | // helper func
49 | func (t *TextRange) Covers(position int) bool {
50 | return position >= t.Start && position <= t.End
51 | }
52 |
53 | type BetterFormattedText struct {
54 | plainText string
55 | formatting []*TextRange
56 | }
57 |
58 | func (b *BetterFormattedText) String() string {
59 | sb := strings.Builder{}
60 |
61 | for i := 0; i < len(b.plainText); i++ {
62 | c := b.plainText[i]
63 | for _, r := range b.formatting {
64 | if r.Covers(i) && r.Capitalize {
65 | c = uint8(unicode.ToUpper(rune(c)))
66 | }
67 | }
68 | sb.WriteRune(rune(c))
69 | }
70 |
71 | return sb.String()
72 | }
73 |
74 | // the constructor
75 | func NewBetterFormattedText(plainText string) *BetterFormattedText {
76 | return &BetterFormattedText{plainText: plainText}
77 | }
78 |
79 | func (b *BetterFormattedText) Range(start, end int) *TextRange {
80 | r := &TextRange{start, end, false, false, false}
81 | b.formatting = append(b.formatting, r)
82 | return r
83 | }
84 |
85 | func main() {
86 | text := "This is a brave new world"
87 |
88 | ft := NewFormattedText(text)
89 | ft.Capitalize(10, 15) // brave
90 | fmt.Println(ft.String())
91 |
92 | bft := NewBetterFormattedText(text)
93 | bft.Range(16, 19).Capitalize = true // new
94 | fmt.Println(bft.String())
95 | }
96 |
--------------------------------------------------------------------------------
/3-structural/flyweight/usernames-example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | // example for a Game where you can have +1M of users with similar names
9 | type User struct {
10 | FullName string
11 | }
12 |
13 | func NewUser(fullName string) *User {
14 | return &User{FullName: fullName}
15 | }
16 |
17 | var allNames []string
18 |
19 | // more efficient user
20 | type User2 struct {
21 | names []uint8
22 | }
23 |
24 | // uploading to the slice
25 | func NewUser2(fullName string) *User2 {
26 | getOrAdd := func(s string) uint8 {
27 | for i := range allNames {
28 | if allNames[i] == s {
29 | return uint8(i)
30 | }
31 | }
32 | allNames = append(allNames, s)
33 | return uint8(len(allNames) - 1)
34 | }
35 |
36 | result := User2{}
37 | parts := strings.Split(fullName, " ")
38 | for _, p := range parts {
39 | result.names = append(result.names, getOrAdd(p))
40 | }
41 | return &result
42 | }
43 |
44 | func (u *User2) FullName() string {
45 | var parts []string
46 | for _, id := range u.names {
47 | parts = append(parts, allNames[id])
48 | }
49 | return strings.Join(parts, " ")
50 | }
51 |
52 | func main() {
53 | // without using flyweight
54 | john := NewUser("John Doe")
55 | jane := NewUser("Jane Doe")
56 | alsoJane := NewUser("Jane Smith")
57 |
58 | fmt.Println(john.FullName)
59 | fmt.Println(jane.FullName)
60 | fmt.Println(alsoJane.FullName)
61 |
62 | fmt.Println("Memory taken by users:",
63 | len([]byte(john.FullName))+
64 | len([]byte(alsoJane.FullName))+
65 | len([]byte(jane.FullName)))
66 |
67 | john2 := NewUser2("John Doe")
68 | jane2 := NewUser2("Jane Doe")
69 | alsoJane2 := NewUser2("Jane Smith")
70 |
71 | fmt.Println(john2.FullName())
72 | fmt.Println(jane2.FullName())
73 | fmt.Println(alsoJane2.FullName())
74 |
75 | totalMem := 0
76 | for _, a := range allNames {
77 | totalMem += len([]byte(a))
78 | }
79 | totalMem += len(john2.names)
80 | totalMem += len(jane2.names)
81 | totalMem += len(alsoJane2.names)
82 |
83 | // flywight!
84 | fmt.Println("Memory taken by users2:", totalMem)
85 | }
86 |
--------------------------------------------------------------------------------
/3-structural/proxy/main.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | // Proxy
4 | // An interface for accesing a particular resource
5 |
6 | // There's a lot of proxy implementation for multiple purposes
7 |
8 | // Protection proxy: access control
9 | // Virtual proxy: pretend something it's there
10 |
11 | // Proxy != Decorator
12 | // Proxy tries to provide an identicial interface
13 | // Decorator provides an enhanced interface
14 |
--------------------------------------------------------------------------------
/3-structural/proxy/protection-proxy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // base interface
6 | type Driven interface {
7 | Drive()
8 | }
9 |
10 | type Car struct{}
11 |
12 | // implements the interface
13 | func (c *Car) Drive() {
14 | fmt.Println("Car being driven")
15 | }
16 |
17 | type Driver struct {
18 | Age int
19 | }
20 |
21 | // this can also be called 'secureCar' or something like that
22 | type CarProxy struct {
23 | // notice the private of the fields
24 | car Car
25 | driver *Driver
26 | }
27 |
28 | // also implements the interface
29 | func (c *CarProxy) Drive() {
30 | if c.driver.Age >= 16 {
31 | c.car.Drive()
32 | } else {
33 | fmt.Println("Driver too young")
34 | }
35 | }
36 |
37 | // constructor
38 | func NewCarProxy(driver *Driver) *CarProxy {
39 | return &CarProxy{Car{}, driver}
40 | }
41 |
42 | func main() {
43 | car := NewCarProxy(&Driver{12})
44 | car.Drive()
45 | }
46 |
--------------------------------------------------------------------------------
/3-structural/proxy/virtual-proxy/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // base interface
6 | type Image interface {
7 | Draw()
8 | }
9 |
10 | type Bitmap struct {
11 | filename string
12 | }
13 |
14 | func (b *Bitmap) Draw() {
15 | fmt.Println("Drawing image", b.filename)
16 | }
17 |
18 | // when we initilize it, it it's loading the image. But we don't want to
19 | func NewBitmap(filename string) *Bitmap {
20 | fmt.Println("Loading image from", filename)
21 | return &Bitmap{filename: filename}
22 | }
23 |
24 | func DrawImage(image Image) {
25 | fmt.Println("About to draw the image")
26 | image.Draw()
27 | fmt.Println("Done drawing the image")
28 | }
29 |
30 | // lazyBitmap allows us to load the image when we need
31 | type LazyBitmap struct {
32 | filename string
33 | bitmap *Bitmap
34 | }
35 |
36 | func (l *LazyBitmap) Draw() {
37 | // to know if has been initialized
38 | if l.bitmap == nil {
39 | l.bitmap = NewBitmap(l.filename)
40 | }
41 | l.bitmap.Draw()
42 | }
43 |
44 | func NewLazyBitmap(filename string) *LazyBitmap {
45 | return &LazyBitmap{filename: filename}
46 | }
47 |
48 | func main() {
49 | //bmp := NewBitmap("demo.png")
50 | bmp := NewLazyBitmap("demo.png")
51 | DrawImage(bmp)
52 | }
53 |
--------------------------------------------------------------------------------
/4-behavioural/chain-of-responsibility/broker-chain/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "sync"
6 | )
7 |
8 | // command query separation, Mediator, Chain of responsibility and Observer
9 | // in a sigle example
10 |
11 | type Argument int
12 |
13 | const (
14 | Attack Argument = iota
15 | Defense
16 | )
17 |
18 | // Command Query Separation stands for separing the query that will be appplied
19 | // to the value returned
20 | type Query struct {
21 | CreatureName string
22 | WhatToQuery Argument
23 | Value int
24 | }
25 |
26 | // receives a Query to handle
27 | type Observer interface {
28 | Handle(*Query)
29 | }
30 |
31 | // to be able to subscribe and unsubscribe
32 | type Observable interface {
33 | Subscribe(o Observer)
34 | Unsubscribe(o Observer)
35 | Fire(q *Query)
36 | }
37 |
38 | // has its observers
39 | type Game struct {
40 | observers sync.Map
41 | }
42 |
43 | func (g *Game) Subscribe(o Observer) {
44 | g.observers.Store(o, struct{}{})
45 | // ↑↑↑ empty anon struct
46 | }
47 |
48 | func (g *Game) Unsubscribe(o Observer) {
49 | g.observers.Delete(o)
50 | }
51 |
52 | func (g *Game) Fire(q *Query) {
53 | g.observers.Range(func(key, value interface{}) bool {
54 | if key == nil {
55 | return false
56 | }
57 | key.(Observer).Handle(q)
58 | return true
59 | })
60 | }
61 |
62 | type Creature struct {
63 | game *Game
64 | Name string
65 | attack, defense int // ← private!
66 | }
67 |
68 | func NewCreature(game *Game, name string, attack int, defense int) *Creature {
69 | return &Creature{game: game, Name: name, attack: attack, defense: defense}
70 | }
71 |
72 | func (c *Creature) Attack() int {
73 | q := Query{c.Name, Attack, c.attack}
74 | // in this will can have a kind of monitoring when each player calls attack
75 | c.game.Fire(&q)
76 |
77 | return q.Value
78 | }
79 |
80 | func (c *Creature) Defense() int {
81 | q := Query{c.Name, Defense, c.defense}
82 | c.game.Fire(&q)
83 |
84 | return q.Value
85 | }
86 |
87 | func (c *Creature) String() string {
88 | return fmt.Sprintf("%s (%d/%d)",
89 | c.Name, c.Attack(), c.Defense())
90 | }
91 |
92 | // data common to all modifiers
93 | type CreatureModifier struct {
94 | game *Game
95 | creature *Creature
96 | }
97 |
98 | func (c *CreatureModifier) Handle(*Query) {
99 | // nothing here!
100 | }
101 |
102 | type DoubleAttackModifier struct {
103 | CreatureModifier
104 | }
105 |
106 | func NewDoubleAttackModifier(g *Game, c *Creature) *DoubleAttackModifier {
107 | d := &DoubleAttackModifier{CreatureModifier{g, c}}
108 | g.Subscribe(d)
109 |
110 | return d
111 | }
112 |
113 | func (d *DoubleAttackModifier) Handle(q *Query) {
114 | if q.CreatureName == d.creature.Name &&
115 | q.WhatToQuery == Attack {
116 | q.Value *= 2
117 | }
118 | }
119 |
120 | func (d *DoubleAttackModifier) Close() error {
121 | d.game.Unsubscribe(d)
122 |
123 | return nil
124 | }
125 |
126 | func main() {
127 | game := &Game{sync.Map{}}
128 | goblin := NewCreature(game, "Strong Goblin", 2, 2)
129 | fmt.Println(goblin.String())
130 |
131 | {
132 | m := NewDoubleAttackModifier(game, goblin)
133 | fmt.Println(goblin.String())
134 | m.Close()
135 | }
136 |
137 | fmt.Println(goblin.String())
138 | }
139 |
--------------------------------------------------------------------------------
/4-behavioural/chain-of-responsibility/main.go:
--------------------------------------------------------------------------------
1 | package chainofresponsibility
2 |
3 | // Chain of Reponsibility
4 | // Sequence of handlers process ing an event one after another
5 | // Middleware (?)
6 |
7 | // A chain of components who all get a chance to process a command or a query,
8 | // optionally having a default processing implementation and
9 | // an ability to stop the processing chain.
10 |
11 | // Can be implemented as a linked list
12 | // or centralized contructor (subscribers)
13 |
14 | // Control addition and deletion as well
15 |
--------------------------------------------------------------------------------
/4-behavioural/chain-of-responsibility/method-chain/main.go:
--------------------------------------------------------------------------------
1 | package chainofresponsibility
2 |
3 | import "fmt"
4 |
5 | type Creature struct {
6 | Name string
7 | Attack, Defense int
8 | }
9 |
10 | func (c *Creature) String() string {
11 | return fmt.Sprintf("%s (%d/%d)",
12 | c.Name, c.Attack, c.Defense)
13 | }
14 |
15 | func NewCreature(name string, attack int, defense int) *Creature {
16 | return &Creature{Name: name, Attack: attack, Defense: defense}
17 | }
18 |
19 | // this will be our controller func
20 | type Modifier interface {
21 | Add(m Modifier)
22 | Handle()
23 | }
24 |
25 | // actually the struct that will point to a creature
26 | type CreatureModifier struct {
27 | creature *Creature
28 | next Modifier // singly linked list
29 | }
30 |
31 | // recursive func to add a modifier to the end
32 | func (c *CreatureModifier) Add(m Modifier) {
33 | if c.next != nil {
34 | c.next.Add(m)
35 | } else {
36 | c.next = m
37 | }
38 | }
39 |
40 | // calls the handle method of the modifier
41 | // handles the end of the chain
42 | func (c *CreatureModifier) Handle() {
43 | if c.next != nil {
44 | c.next.Handle()
45 | }
46 | }
47 |
48 | func NewCreatureModifier(creature *Creature) *CreatureModifier {
49 | return &CreatureModifier{creature: creature}
50 | }
51 |
52 | // aggregates the CreatureModifier
53 | type DoubleAttackModifier struct {
54 | CreatureModifier
55 | }
56 |
57 | func NewDoubleAttackModifier(
58 | c *Creature) *DoubleAttackModifier {
59 | return &DoubleAttackModifier{CreatureModifier{
60 | creature: c}}
61 | }
62 |
63 | type IncreasedDefenseModifier struct {
64 | CreatureModifier
65 | }
66 |
67 | func NewIncreasedDefenseModifier(
68 | c *Creature) *IncreasedDefenseModifier {
69 | return &IncreasedDefenseModifier{CreatureModifier{
70 | creature: c}}
71 | }
72 |
73 | // the hanlder
74 | func (i *IncreasedDefenseModifier) Handle() {
75 | if i.creature.Attack <= 2 {
76 | fmt.Println("Increasing",
77 | i.creature.Name, "\b's defense")
78 | i.creature.Defense++
79 | }
80 |
81 | // notice it points to he other modifier to call handle
82 | i.CreatureModifier.Handle()
83 | }
84 |
85 | func (d *DoubleAttackModifier) Handle() {
86 | fmt.Println("Doubling", d.creature.Name,
87 | "attack...")
88 | d.creature.Attack *= 2
89 | d.CreatureModifier.Handle()
90 | }
91 |
92 | type NoBonusesModifier struct {
93 | CreatureModifier
94 | }
95 |
96 | func NewNoBonusesModifier(
97 | c *Creature) *NoBonusesModifier {
98 | return &NoBonusesModifier{CreatureModifier{
99 | creature: c}}
100 | }
101 |
102 | // if fact this will anulate the other modifiers if added
103 | func (n *NoBonusesModifier) Handle() {
104 | // nothing here!
105 | }
106 |
107 | func main() {
108 | goblin := NewCreature("Goblin", 1, 1)
109 | fmt.Println(goblin.String())
110 |
111 | root := NewCreatureModifier(goblin)
112 |
113 | //root.Add(NewNoBonusesModifier(goblin))
114 |
115 | root.Add(NewDoubleAttackModifier(goblin))
116 | root.Add(NewIncreasedDefenseModifier(goblin))
117 | root.Add(NewDoubleAttackModifier(goblin))
118 |
119 | // eventually process the entire chain
120 | root.Handle()
121 | fmt.Println(goblin.String())
122 | }
123 |
--------------------------------------------------------------------------------
/4-behavioural/command/composite-command/main.go:
--------------------------------------------------------------------------------
1 | package compositecommand
2 |
3 | import "fmt"
4 |
5 | var overdraftLimit = -500
6 |
7 | type BankAccount struct {
8 | balance int
9 | }
10 |
11 | func (b *BankAccount) Deposit(amount int) {
12 | b.balance += amount
13 | fmt.Println("Deposited", amount,
14 | "\b, balance is now", b.balance)
15 | }
16 |
17 | func (b *BankAccount) Withdraw(amount int) bool {
18 | if b.balance-amount >= overdraftLimit {
19 | b.balance -= amount
20 | fmt.Println("Withdrew", amount,
21 | "\b, balance is now", b.balance)
22 | return true
23 | }
24 | return false
25 | }
26 |
27 | type Command interface {
28 | Call()
29 | Undo()
30 | // saving the status
31 | Succeeded() bool
32 | SetSucceeded(value bool)
33 | }
34 |
35 | type Action int
36 |
37 | const (
38 | Deposit Action = iota
39 | Withdraw
40 | )
41 |
42 | type BankAccountCommand struct {
43 | account *BankAccount
44 | action Action
45 | amount int
46 | succeeded bool
47 | }
48 |
49 | func (b *BankAccountCommand) SetSucceeded(value bool) {
50 | b.succeeded = value
51 | }
52 |
53 | // additional member
54 | func (b *BankAccountCommand) Succeeded() bool {
55 | return b.succeeded
56 | }
57 |
58 | func (b *BankAccountCommand) Call() {
59 | switch b.action {
60 | case Deposit:
61 | b.account.Deposit(b.amount)
62 | b.succeeded = true
63 | case Withdraw:
64 | b.succeeded = b.account.Withdraw(b.amount)
65 | }
66 | }
67 |
68 | func (b *BankAccountCommand) Undo() {
69 | if !b.succeeded {
70 | return
71 | }
72 | switch b.action {
73 | case Deposit:
74 | b.account.Withdraw(b.amount)
75 | case Withdraw:
76 | b.account.Deposit(b.amount)
77 | }
78 | }
79 |
80 | // group commands
81 | type CompositeBankAccountCommand struct {
82 | commands []Command
83 | }
84 |
85 | func (c *CompositeBankAccountCommand) Succeeded() bool {
86 | for _, cmd := range c.commands {
87 | if !cmd.Succeeded() {
88 | return false
89 | }
90 | }
91 | return true
92 | }
93 |
94 | func (c *CompositeBankAccountCommand) SetSucceeded(value bool) {
95 | for _, cmd := range c.commands {
96 | cmd.SetSucceeded(value)
97 | }
98 | }
99 |
100 | func (c *CompositeBankAccountCommand) Call() {
101 | for _, cmd := range c.commands {
102 | cmd.Call()
103 | }
104 | }
105 |
106 | func (c *CompositeBankAccountCommand) Undo() {
107 | // undo in reverse order
108 | for idx := range c.commands {
109 | c.commands[len(c.commands)-idx-1].Undo()
110 | }
111 | }
112 |
113 | func NewBankAccountCommand(account *BankAccount, action Action, amount int) *BankAccountCommand {
114 | return &BankAccountCommand{account: account, action: action, amount: amount}
115 | }
116 |
117 | type MoneyTransferCommand struct {
118 | CompositeBankAccountCommand
119 | from, to *BankAccount
120 | amount int
121 | }
122 |
123 | func NewMoneyTransferCommand(from *BankAccount, to *BankAccount, amount int) *MoneyTransferCommand {
124 | c := &MoneyTransferCommand{from: from, to: to, amount: amount}
125 | c.commands = append(c.commands,
126 | NewBankAccountCommand(from, Withdraw, amount))
127 | c.commands = append(c.commands,
128 | NewBankAccountCommand(to, Deposit, amount))
129 | return c
130 | }
131 |
132 | func (m *MoneyTransferCommand) Call() {
133 | ok := true
134 | for _, cmd := range m.commands {
135 | if ok {
136 | cmd.Call()
137 | ok = cmd.Succeeded()
138 | } else {
139 | cmd.SetSucceeded(false)
140 | }
141 | }
142 | }
143 |
144 | func main() {
145 | ba := &BankAccount{}
146 | cmdDeposit := NewBankAccountCommand(ba, Deposit, 100)
147 | cmdWithdraw := NewBankAccountCommand(ba, Withdraw, 1000)
148 | cmdDeposit.Call()
149 | cmdWithdraw.Call()
150 | fmt.Println(ba)
151 | cmdWithdraw.Undo()
152 | cmdDeposit.Undo()
153 | fmt.Println(ba)
154 |
155 | from := BankAccount{100}
156 | to := BankAccount{0}
157 | mtc := NewMoneyTransferCommand(&from, &to, 100) // try 1000
158 | mtc.Call()
159 |
160 | fmt.Println("from=", from, "to=", to)
161 |
162 | fmt.Println("Undoing...")
163 | mtc.Undo()
164 | fmt.Println("from=", from, "to=", to)
165 | }
166 |
--------------------------------------------------------------------------------
/4-behavioural/command/functional/main.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import "fmt"
4 |
5 | type BankAccount struct {
6 | Balance int
7 | }
8 |
9 | func Deposit(ba *BankAccount, amount int) {
10 | fmt.Println("Depositing", amount)
11 | ba.Balance += amount
12 | }
13 |
14 | func Withdraw(ba *BankAccount, amount int) {
15 | if ba.Balance >= amount {
16 | fmt.Println("Withdrawing", amount)
17 | ba.Balance -= amount
18 | }
19 | }
20 |
21 | func main() {
22 | ba := &BankAccount{0}
23 | var commands []func()
24 |
25 | // here we are just adding commands to be implemented in order
26 | // in some cases can be practical
27 | // where we don't need to have all control just perform actions
28 | commands = append(commands, func() {
29 | Deposit(ba, 100)
30 | })
31 | commands = append(commands, func() {
32 | Withdraw(ba, 100)
33 | })
34 |
35 | for _, cmd := range commands {
36 | cmd()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/4-behavioural/command/main.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | // Command
4 | // Want objects to represent a operation
5 | // Sometimes you cannot undo assignments
6 |
7 | // An object that represents an intruction to performn a
8 | // particular action.
9 |
10 | import "fmt"
11 |
12 | var overdraftLimit = -500
13 |
14 | type BankAccount struct {
15 | balance int
16 | }
17 |
18 | func (b *BankAccount) Deposit(amount int) {
19 | b.balance += amount
20 | fmt.Println("Deposited", amount,
21 | "\b, balance is now", b.balance)
22 | }
23 |
24 | func (b *BankAccount) Withdraw(amount int) bool {
25 | if b.balance-amount >= overdraftLimit {
26 | b.balance -= amount
27 | fmt.Println("Withdrew", amount,
28 | "\b, balance is now", b.balance)
29 | return true
30 | }
31 | return false
32 | }
33 |
34 | // to actually call or undo the methods
35 | type Command interface {
36 | Call()
37 | Undo()
38 | }
39 |
40 | type Action int
41 |
42 | const (
43 | Deposit Action = iota
44 | Withdraw
45 | )
46 |
47 | // to know if a which command will be run and if succedeed
48 | type BankAccountCommand struct {
49 | account *BankAccount
50 | action Action
51 | amount int
52 | succeeded bool
53 | }
54 |
55 | func (b *BankAccountCommand) Call() {
56 | switch b.action {
57 | case Deposit:
58 | b.account.Deposit(b.amount)
59 | b.succeeded = true
60 | case Withdraw:
61 | b.succeeded = b.account.Withdraw(b.amount)
62 | }
63 | }
64 |
65 | func (b *BankAccountCommand) Undo() {
66 | if !b.succeeded {
67 | return
68 | }
69 |
70 | // in our case the undo operation it's symetric
71 | switch b.action {
72 | case Deposit:
73 | b.account.Withdraw(b.amount)
74 | case Withdraw:
75 | b.account.Deposit(b.amount)
76 | }
77 | }
78 |
79 | func NewBankAccountCommand(account *BankAccount, action Action, amount int) *BankAccountCommand {
80 | return &BankAccountCommand{account: account, action: action, amount: amount}
81 | }
82 |
83 | func main() {
84 | ba := BankAccount{}
85 | cmd := NewBankAccountCommand(&ba, Deposit, 100)
86 | cmd.Call()
87 | cmd2 := NewBankAccountCommand(&ba, Withdraw, 50)
88 | cmd2.Call()
89 | fmt.Println(ba)
90 | }
91 |
--------------------------------------------------------------------------------
/4-behavioural/interpreter/main.go:
--------------------------------------------------------------------------------
1 | package interpreter
2 |
3 | // Interpreter
4 | // Text input needs to be processed
5 |
6 | // Does so by turning it into a separate lexican tokens (lexing)
7 | // and then interpreting sequences of said tokens (parsing
8 |
9 | import (
10 | "fmt"
11 | "strconv"
12 | "strings"
13 | "unicode"
14 | )
15 |
16 | type Element interface {
17 | Value() int
18 | }
19 |
20 | type Integer struct {
21 | value int
22 | }
23 |
24 | func NewInteger(value int) *Integer {
25 | return &Integer{value: value}
26 | }
27 |
28 | func (i *Integer) Value() int {
29 | return i.value
30 | }
31 |
32 | type Operation int
33 |
34 | const (
35 | Addition Operation = iota
36 | Subtraction
37 | )
38 |
39 | type BinaryOperation struct {
40 | Type Operation
41 | Left, Right Element
42 | }
43 |
44 | func (b *BinaryOperation) Value() int {
45 | switch b.Type {
46 | case Addition:
47 | return b.Left.Value() + b.Right.Value()
48 | case Subtraction:
49 | return b.Left.Value() + b.Right.Value()
50 | default:
51 | panic("Unsupported operation")
52 | }
53 | }
54 |
55 | type TokenType int
56 |
57 | const (
58 | Int TokenType = iota
59 | Plus
60 | Minus
61 | // left parentesis
62 | Lparen
63 | // right parentesis
64 | Rparen
65 | )
66 |
67 | type Token struct {
68 | Type TokenType
69 | Text string
70 | }
71 |
72 | func (t *Token) String() string {
73 | return fmt.Sprintf("`%s`", t.Text)
74 | }
75 |
76 | // first part it's converting the string into tokens (lexing part)
77 | func Lex(input string) []Token {
78 | var result []Token
79 |
80 | // not using range here
81 | for i := 0; i < len(input); i++ {
82 | switch input[i] {
83 | case '+':
84 | result = append(result, Token{Plus, "+"})
85 | case '-':
86 | result = append(result, Token{Minus, "-"})
87 | case '(':
88 | result = append(result, Token{Lparen, "("})
89 | case ')':
90 | result = append(result, Token{Rparen, ")"})
91 | default:
92 | sb := strings.Builder{}
93 | for j := i; j < len(input); j++ {
94 | if unicode.IsDigit(rune(input[j])) {
95 | sb.WriteRune(rune(input[j]))
96 | i++
97 | } else {
98 | result = append(result, Token{
99 | Int, sb.String()})
100 | i--
101 | break
102 | }
103 | }
104 | }
105 | }
106 | return result
107 | }
108 |
109 | // final part is parse those tokens
110 | func Parse(tokens []Token) Element {
111 | result := BinaryOperation{}
112 | haveLhs := false
113 | for i := 0; i < len(tokens); i++ {
114 | token := &tokens[i]
115 | switch token.Type {
116 | case Int:
117 | n, _ := strconv.Atoi(token.Text)
118 | integer := Integer{n}
119 | if !haveLhs {
120 | result.Left = &integer
121 | haveLhs = true
122 | } else {
123 | result.Right = &integer
124 | }
125 | case Plus:
126 | result.Type = Addition
127 | case Minus:
128 | result.Type = Subtraction
129 | case Lparen:
130 | j := i
131 | for ; j < len(tokens); j++ {
132 | if tokens[j].Type == Rparen {
133 | break
134 | }
135 | }
136 | // now j points to closing bracket, so
137 | // process subexpression without opening
138 | var subexp []Token
139 | for k := i + 1; k < j; k++ {
140 | subexp = append(subexp, tokens[k])
141 | }
142 | element := Parse(subexp)
143 | if !haveLhs {
144 | result.Left = element
145 | haveLhs = true
146 | } else {
147 | result.Right = element
148 | }
149 | i = j
150 | }
151 | }
152 | return &result
153 | }
154 |
155 | func main() {
156 | input := "(13+4)-(12+1)"
157 | tokens := Lex(input)
158 | fmt.Println(tokens)
159 |
160 | parsed := Parse(tokens)
161 | fmt.Printf("%s = %d\n",
162 | input, parsed.Value())
163 | }
164 |
--------------------------------------------------------------------------------
/4-behavioural/iterator/main.go:
--------------------------------------------------------------------------------
1 | package iterator
2 |
3 | import "fmt"
4 |
5 | // Iterator
6 | // Core functionality of data structures
7 | // Also called transversal
8 |
9 | // Keeps a pointer to the current element
10 | // Knows how to move to a different element
11 |
12 | // Object that facilities the transversal for a data structure
13 |
14 | // Also it's kinda built it in Go when using range()
15 |
16 | type Person struct {
17 | FirstName, MiddleName, LastName string
18 | }
19 |
20 | func (p *Person) Names() []string {
21 | return []string{p.FirstName, p.MiddleName, p.LastName}
22 | }
23 |
24 | func main() {
25 | p := Person{"Alexander", "Graham", "Bell"}
26 | for _, name := range p.Names() {
27 | fmt.Println(name)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/4-behavioural/iterator/tree-transversal/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Node struct {
6 | Value int
7 | left, right, parent *Node
8 | }
9 |
10 | func NewNode(value int, left *Node, right *Node) *Node {
11 | n := &Node{Value: value, left: left, right: right}
12 | left.parent = n
13 | right.parent = n
14 | return n
15 | }
16 |
17 | // node without children
18 | func NewTerminalNode(value int) *Node {
19 | return &Node{Value: value}
20 | }
21 |
22 | type InOrderIterator struct {
23 | Current *Node
24 | root *Node
25 | returnedStart bool
26 | }
27 |
28 | func NewInOrderIterator(root *Node) *InOrderIterator {
29 | i := &InOrderIterator{
30 | Current: root,
31 | root: root,
32 | returnedStart: false,
33 | }
34 | // move to the leftmost element
35 | for i.Current.left != nil {
36 | i.Current = i.Current.left
37 | }
38 | return i
39 | }
40 |
41 | func (i *InOrderIterator) Reset() {
42 | i.Current = i.root
43 | i.returnedStart = false
44 | }
45 |
46 | // to continue iterating
47 | func (i *InOrderIterator) MoveNext() bool {
48 | if i.Current == nil {
49 | return false
50 | }
51 | if !i.returnedStart {
52 | i.returnedStart = true
53 | return true // can use first element
54 | }
55 |
56 | if i.Current.right != nil {
57 | i.Current = i.Current.right
58 | for i.Current.left != nil {
59 | i.Current = i.Current.left
60 | }
61 | return true
62 | } else {
63 | p := i.Current.parent
64 | for p != nil && i.Current == p.right {
65 | i.Current = p
66 | p = p.parent
67 | }
68 | i.Current = p
69 | return i.Current != nil
70 | }
71 | }
72 |
73 | // actually the tree that will have the nodes
74 | type BinaryTree struct {
75 | root *Node
76 | }
77 |
78 | func NewBinaryTree(root *Node) *BinaryTree {
79 | return &BinaryTree{root: root}
80 | }
81 |
82 | // implements the interface
83 | func (b *BinaryTree) InOrder() *InOrderIterator {
84 | return NewInOrderIterator(b.root)
85 | }
86 |
87 | func main() {
88 | // 1
89 | // / \
90 | // 2 3
91 |
92 | // in-order: 213
93 | // preorder: 123
94 | // postorder: 231
95 |
96 | root := NewNode(1,
97 | NewTerminalNode(2),
98 | NewTerminalNode(3))
99 | it := NewInOrderIterator(root)
100 |
101 | for it.MoveNext() {
102 | fmt.Printf("%d,", it.Current.Value)
103 | }
104 | fmt.Println("\b")
105 |
106 | t := NewBinaryTree(root)
107 | for i := t.InOrder(); i.MoveNext(); {
108 | fmt.Printf("%d,", i.Current.Value)
109 | }
110 | fmt.Println("\b")
111 | }
112 |
--------------------------------------------------------------------------------
/4-behavioural/mediator/main.go:
--------------------------------------------------------------------------------
1 | package mediator
2 |
3 | // Mediator
4 | // Central component that facilitates the communication between components
5 |
6 | import "fmt"
7 |
8 | type Person struct {
9 | Name string
10 | Room *ChatRoom
11 | chatLog []string
12 | }
13 |
14 | func NewPerson(name string) *Person {
15 | return &Person{Name: name}
16 | }
17 |
18 | func (p *Person) Receive(sender, message string) {
19 | s := fmt.Sprintf("%s: '%s'", sender, message)
20 | fmt.Printf("[%s's chat session] %s\n", p.Name, s)
21 | p.chatLog = append(p.chatLog, s)
22 | }
23 |
24 | func (p *Person) Say(message string) {
25 | p.Room.Broadcast(p.Name, message)
26 | }
27 |
28 | func (p *Person) PrivateMessage(who, message string) {
29 | p.Room.Message(p.Name, who, message)
30 | }
31 |
32 | // actually the mediator
33 | type ChatRoom struct {
34 | people []*Person
35 | }
36 |
37 | // makes everyone receives the message
38 | func (c *ChatRoom) Broadcast(source, message string) {
39 | for _, p := range c.people {
40 | if p.Name != source {
41 | p.Receive(source, message)
42 | }
43 | }
44 | }
45 |
46 | func (c *ChatRoom) Join(p *Person) {
47 | joinMsg := p.Name + " joins the chat"
48 | c.Broadcast("Room", joinMsg)
49 |
50 | p.Room = c
51 | c.people = append(c.people, p)
52 | }
53 |
54 | func (c *ChatRoom) Message(src, dst, msg string) {
55 | for _, p := range c.people {
56 | if p.Name == dst {
57 | p.Receive(src, msg)
58 | }
59 | }
60 | }
61 |
62 | func main() {
63 | room := ChatRoom{}
64 |
65 | john := NewPerson("John")
66 | jane := NewPerson("Jane")
67 |
68 | room.Join(john)
69 | room.Join(jane)
70 |
71 | john.Say("hi room")
72 | jane.Say("oh, hey john")
73 |
74 | simon := NewPerson("Simon")
75 | room.Join(simon)
76 | simon.Say("hi everyone!")
77 |
78 | jane.PrivateMessage("Simon", "glad you could join us!")
79 | }
80 |
--------------------------------------------------------------------------------
/4-behavioural/memento/main.go:
--------------------------------------------------------------------------------
1 | package memento
2 |
3 | // Memento
4 | // Keeps a memento of an object's state to return to that state
5 |
6 | // May or may not directly expose state info
7 |
8 | // If perfect if the system it's simple. But if you have to save large snapshots
9 | // better use command to save memory.
10 |
11 | import (
12 | "fmt"
13 | )
14 |
15 | // the snapshot
16 | type Memento struct {
17 | Balance int
18 | }
19 |
20 | type BankAccount struct {
21 | balance int
22 | }
23 |
24 | // notice that the point can be nil
25 | func (b *BankAccount) Deposit(amount int) *Memento {
26 | b.balance += amount
27 | return &Memento{b.balance}
28 | }
29 |
30 | func (b *BankAccount) Restore(m *Memento) {
31 | b.balance = m.Balance
32 | }
33 |
34 | func main() {
35 | ba := BankAccount{100}
36 | m1 := ba.Deposit(50)
37 | m2 := ba.Deposit(25)
38 | fmt.Println(ba)
39 |
40 | ba.Restore(m1)
41 | fmt.Println(ba) // 150
42 |
43 | ba.Restore(m2)
44 | fmt.Println(ba)
45 | }
46 |
--------------------------------------------------------------------------------
/4-behavioural/memento/undo-redo/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | type Memento struct {
6 | Balance int
7 | }
8 |
9 | type BankAccount struct {
10 | balance int
11 | // to have the ability to undo and redo we need to keep track of the changes
12 | changes []*Memento
13 | // and also the pointer where we are at
14 | current int
15 | }
16 |
17 | func (b *BankAccount) String() string {
18 | return fmt.Sprint("Balance = $", b.balance,
19 | ", current = ", b.current)
20 | }
21 |
22 | func NewBankAccount(balance int) *BankAccount {
23 | b := &BankAccount{balance: balance}
24 | b.changes = append(b.changes, &Memento{balance})
25 |
26 | return b
27 | }
28 |
29 | func (b *BankAccount) Deposit(amount int) *Memento {
30 | b.balance += amount
31 |
32 | m := Memento{b.balance}
33 | b.changes = append(b.changes, &m)
34 |
35 | // changes the pointer
36 | b.current++
37 |
38 | fmt.Println("Deposited", amount,
39 | ", balance is now", b.balance)
40 |
41 | return &m
42 | }
43 |
44 | func (b *BankAccount) Restore(m *Memento) {
45 | // needs to check if it can undo something
46 | if m != nil {
47 | b.balance -= m.Balance
48 | b.changes = append(b.changes, m)
49 | b.current = len(b.changes) - 1
50 | }
51 | }
52 |
53 | func (b *BankAccount) Undo() *Memento {
54 | if b.current > 0 {
55 | b.current--
56 | m := b.changes[b.current]
57 | b.balance = m.Balance
58 |
59 | return m
60 | }
61 |
62 | return nil // nothing to undo
63 | }
64 |
65 | func (b *BankAccount) Redo() *Memento {
66 | if b.current+1 < len(b.changes) {
67 | b.current++
68 | m := b.changes[b.current]
69 | b.balance = m.Balance
70 |
71 | return m
72 | }
73 |
74 | return nil
75 | }
76 |
77 | func main() {
78 | ba := NewBankAccount(100)
79 | ba.Deposit(50)
80 | ba.Deposit(25)
81 | fmt.Println(ba)
82 |
83 | ba.Undo()
84 | fmt.Println("Undo 1:", ba)
85 | ba.Undo()
86 | fmt.Println("Undo 2:", ba)
87 | ba.Redo()
88 | fmt.Println("Redo:", ba)
89 | }
90 |
--------------------------------------------------------------------------------
/4-behavioural/observer/main.go:
--------------------------------------------------------------------------------
1 | package observer
2 |
3 | // Observer
4 | // When we need to be informed when certain things happen
5 | // Kinda a trigger
6 |
7 | // We want to listen to events and notified when they occur
8 | // We have an observer and an observable
9 |
10 | import (
11 | "container/list"
12 | "fmt"
13 | )
14 |
15 | // the object that will have the subscribers and will notify them
16 | type Observable struct {
17 | subs *list.List
18 | }
19 |
20 | // provide methods to subscribe and unsubscribe as well
21 | func (o *Observable) Subscribe(x Observer) {
22 | o.subs.PushBack(x)
23 | }
24 |
25 | func (o *Observable) Unsubscribe(x Observer) {
26 | for z := o.subs.Front(); z != nil; z = z.Next() {
27 | if z.Value.(Observer) == x {
28 | o.subs.Remove(z)
29 | }
30 | }
31 | }
32 |
33 | // notify method for all the subscribers
34 | func (o *Observable) Fire(data interface{}) {
35 | for z := o.subs.Front(); z != nil; z = z.Next() {
36 | z.Value.(Observer).Notify(data)
37 | }
38 | }
39 |
40 | // the object that will be notified
41 | type Observer interface {
42 | Notify(data interface{})
43 | }
44 |
45 | // whenever a person catches a cold,
46 | // a doctor must be called
47 | type Person struct {
48 | Observable
49 | Name string
50 | }
51 |
52 | func NewPerson(name string) *Person {
53 | return &Person{
54 | Observable: Observable{new(list.List)},
55 | Name: name,
56 | }
57 | }
58 |
59 | func (p *Person) CatchACold() {
60 | // notify them all!
61 | p.Fire(p.Name)
62 | }
63 |
64 | type DoctorService struct{}
65 |
66 | // actually what happens when it gets notified
67 | func (d *DoctorService) Notify(data interface{}) {
68 | fmt.Printf("A doctor has been called for %s",
69 | data.(string))
70 | }
71 |
72 | func main() {
73 | p := NewPerson("Boris")
74 | ds := &DoctorService{}
75 | p.Subscribe(ds)
76 |
77 | // let's test it!
78 | p.CatchACold()
79 | }
80 |
--------------------------------------------------------------------------------
/4-behavioural/observer/property-changes/main.go:
--------------------------------------------------------------------------------
1 | package propertychanges
2 |
3 | import (
4 | "container/list"
5 | "fmt"
6 | )
7 |
8 | type Observable struct {
9 | subs *list.List
10 | }
11 |
12 | func (o *Observable) Subscribe(x Observer) {
13 | o.subs.PushBack(x)
14 | }
15 |
16 | func (o *Observable) Unsubscribe(x Observer) {
17 | for z := o.subs.Front(); z != nil; z = z.Next() {
18 | if z.Value.(Observer) == x {
19 | o.subs.Remove(z)
20 | }
21 | }
22 | }
23 |
24 | func (o *Observable) Fire(data interface{}) {
25 | for z := o.subs.Front(); z != nil; z = z.Next() {
26 | z.Value.(Observer).Notify(data)
27 | }
28 | }
29 |
30 | type Observer interface {
31 | Notify(data interface{})
32 | }
33 |
34 | type Person struct {
35 | Observable
36 | age int
37 | }
38 |
39 | func NewPerson(age int) *Person {
40 | return &Person{Observable{new(list.List)}, age}
41 | }
42 |
43 | // actually we want to be notified when a propety had change
44 | type PropertyChanged struct {
45 | Name string
46 | Value interface{}
47 | }
48 |
49 | // getters and setters
50 | func (p *Person) Age() int { return p.age }
51 |
52 | func (p *Person) SetAge(age int) {
53 | if age == p.age {
54 | return
55 | } // no change
56 |
57 | p.age = age
58 | p.Fire(PropertyChanged{"Age", p.age}) // change, notify all
59 | }
60 |
61 | type TrafficManagement struct {
62 | o Observable
63 | }
64 |
65 | func (t *TrafficManagement) Notify(data interface{}) {
66 | if pc, ok := data.(PropertyChanged); ok {
67 | if pc.Value.(int) >= 16 {
68 | fmt.Println("Congrats, you can drive now!")
69 | // we no longer care
70 | t.o.Unsubscribe(t)
71 | }
72 | }
73 | }
74 |
75 | func main() {
76 | p := NewPerson(15)
77 | t := &TrafficManagement{p.Observable}
78 | p.Subscribe(t)
79 |
80 | for i := 16; i <= 20; i++ {
81 | fmt.Println("Setting age to", i)
82 | p.SetAge(i)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/4-behavioural/observer/property-dependencies/main.go:
--------------------------------------------------------------------------------
1 | package propertydependencies
2 |
3 | import (
4 | "container/list"
5 | "fmt"
6 | )
7 |
8 | type Observable struct {
9 | subs *list.List
10 | }
11 |
12 | func (o *Observable) Subscribe(x Observer) {
13 | o.subs.PushBack(x)
14 | }
15 |
16 | func (o *Observable) Unsubscribe(x Observer) {
17 | for z := o.subs.Front(); z != nil; z = z.Next() {
18 | if z.Value.(Observer) == x {
19 | o.subs.Remove(z)
20 | }
21 | }
22 | }
23 |
24 | func (o *Observable) Fire(data interface{}) {
25 | for z := o.subs.Front(); z != nil; z = z.Next() {
26 | z.Value.(Observer).Notify(data)
27 | }
28 | }
29 |
30 | type Observer interface {
31 | Notify(data interface{})
32 | }
33 |
34 | type Person struct {
35 | Observable
36 | age int
37 | }
38 |
39 | func NewPerson(age int) *Person {
40 | return &Person{Observable{new(list.List)}, age}
41 | }
42 |
43 | type PropertyChanged struct {
44 | Name string
45 | Value interface{}
46 | }
47 |
48 | // the fact that a person can vote depends on the age
49 | func (p *Person) CanVote() bool {
50 | return p.age >= 18
51 | }
52 |
53 | func (p *Person) Age() int { return p.age }
54 |
55 | func (p *Person) SetAge(age int) {
56 | if age == p.age {
57 | return
58 | } // no change
59 |
60 | // record the previous state
61 | oldCanVote := p.CanVote()
62 |
63 | p.age = age
64 | p.Fire(PropertyChanged{"Age", p.age})
65 |
66 | // review if change
67 | if oldCanVote != p.CanVote() {
68 | // notify them all
69 | p.Fire(PropertyChanged{"CanVote", p.CanVote()})
70 | }
71 | }
72 |
73 | type ElectrocalRoll struct {
74 | }
75 |
76 | func (e *ElectrocalRoll) Notify(data interface{}) {
77 | if pc, ok := data.(PropertyChanged); ok {
78 | if pc.Name == "CanVote" && pc.Value.(bool) {
79 | fmt.Println("Congratulations, you can vote!")
80 | }
81 | }
82 | }
83 |
84 | func main() {
85 | p := NewPerson(0)
86 | er := &ElectrocalRoll{}
87 | p.Subscribe(er)
88 |
89 | for i := 10; i < 20; i++ {
90 | fmt.Println("Setting age to", i)
91 | p.SetAge(i)
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/4-behavioural/state/handmade-state-machine/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strconv"
8 | )
9 |
10 | type State int
11 |
12 | // all the states
13 | const (
14 | OffHook State = iota
15 | Connecting
16 | Connected
17 | OnHold
18 | OnHook
19 | )
20 |
21 | // string method to be able to print this
22 | func (s State) String() string {
23 | switch s {
24 | case OffHook:
25 | return "OffHook"
26 | case Connecting:
27 | return "Connecting"
28 | case Connected:
29 | return "Connected"
30 | case OnHold:
31 | return "OnHold"
32 | case OnHook:
33 | return "OnHook"
34 | }
35 | return "Unknown"
36 | }
37 |
38 | type Trigger int
39 |
40 | // defining the triggers
41 | const (
42 | CallDialed Trigger = iota
43 | HungUp
44 | CallConnected
45 | PlacedOnHold
46 | TakenOffHold
47 | LeftMessage
48 | )
49 |
50 | func (t Trigger) String() string {
51 | switch t {
52 | case CallDialed:
53 | return "CallDialed"
54 | case HungUp:
55 | return "HungUp"
56 | case CallConnected:
57 | return "CallConnected"
58 | case PlacedOnHold:
59 | return "PlacedOnHold"
60 | case TakenOffHold:
61 | return "TakenOffHold"
62 | case LeftMessage:
63 | return "LeftMessage"
64 | }
65 | return "Unknown"
66 | }
67 |
68 | // this just makes sense if we talk about a trigger that goes to a state
69 | type TriggerResult struct {
70 | Trigger Trigger
71 | State State
72 | }
73 |
74 | // our rules, allowed transitions
75 | var rules = map[State][]TriggerResult{
76 | OffHook: {
77 | {CallDialed, Connecting},
78 | },
79 | Connecting: {
80 | {HungUp, OffHook},
81 | {CallConnected, Connected},
82 | },
83 | Connected: {
84 | {LeftMessage, OnHook},
85 | {HungUp, OnHook},
86 | {PlacedOnHold, OnHold},
87 | },
88 | OnHold: {
89 | {TakenOffHold, Connected},
90 | {HungUp, OnHook},
91 | },
92 | }
93 |
94 | func main() {
95 | state, exitState := OffHook, OnHook // define the exit state to terminate process
96 |
97 | for ok := true; ok; ok = state != exitState {
98 | fmt.Println("The phone is currently", state)
99 | fmt.Println("Select a trigger:")
100 |
101 | for i := 0; i < len(rules[state]); i++ {
102 | tr := rules[state][i]
103 | fmt.Println(strconv.Itoa(i), ".", tr.Trigger)
104 | }
105 |
106 | // be able to get the input of the user by terminal. Read the line
107 | input, _, _ := bufio.NewReader(os.Stdin).ReadLine()
108 |
109 | // convert to int
110 | i, _ := strconv.Atoi(string(input))
111 |
112 | tr := rules[state][i]
113 | state = tr.State
114 | }
115 |
116 | // we exit
117 | fmt.Println("We are done using the phone")
118 | }
119 |
--------------------------------------------------------------------------------
/4-behavioural/state/main.go:
--------------------------------------------------------------------------------
1 | package state
2 |
3 | // State
4 | // A pattern which object behavior is determined by its state
5 |
6 | // A formalized construct which manages state and transitions is called
7 | // a state machine
8 |
9 | import "fmt"
10 |
11 | type Switch struct {
12 | State State
13 | }
14 |
15 | func NewSwitch() *Switch {
16 | return &Switch{NewOffState()}
17 | }
18 |
19 | func (s *Switch) On() {
20 | s.State.On(s)
21 | }
22 |
23 | func (s *Switch) Off() {
24 | s.State.Off(s)
25 | }
26 |
27 | // our state machine
28 | type State interface {
29 | On(sw *Switch)
30 | Off(sw *Switch)
31 | }
32 |
33 | // base state allows us if no transition it's set
34 | type BaseState struct{}
35 |
36 | func (s *BaseState) On(sw *Switch) {
37 | fmt.Println("Light is already on")
38 | }
39 |
40 | func (s *BaseState) Off(sw *Switch) {
41 | fmt.Println("Light is already off")
42 | }
43 |
44 | type OnState struct {
45 | BaseState
46 | }
47 |
48 | func NewOnState() *OnState {
49 | fmt.Println("Light turned on")
50 | return &OnState{BaseState{}}
51 | }
52 |
53 | // in on state we just can go off
54 | func (o *OnState) Off(sw *Switch) {
55 | fmt.Println("Turning light off...")
56 | // reassign state
57 | sw.State = NewOffState()
58 | }
59 |
60 | type OffState struct {
61 | BaseState
62 | }
63 |
64 | func NewOffState() *OffState {
65 | fmt.Println("Light turned off")
66 | return &OffState{BaseState{}}
67 | }
68 |
69 | // in off state we just can go on
70 | func (o *OffState) On(sw *Switch) {
71 | fmt.Println("Turning light on...")
72 | // reassign state
73 | sw.State = NewOnState()
74 | }
75 |
76 | func main() {
77 | sw := NewSwitch()
78 | sw.On()
79 | sw.Off()
80 | sw.Off()
81 | }
82 |
--------------------------------------------------------------------------------
/4-behavioural/state/switch-based-state-machine/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "fmt"
6 | "os"
7 | "strings"
8 | )
9 |
10 | type State int
11 |
12 | const (
13 | Locked State = iota
14 | Failed
15 | Unlocked
16 | )
17 |
18 | func main() {
19 | // the code we have to guess
20 | code := "1234"
21 | state := Locked
22 |
23 | // strings builder to manipulate the entry
24 | entry := strings.Builder{}
25 |
26 | for {
27 | switch state {
28 | case Locked:
29 | // only reads input when you press Return
30 | r, _, _ := bufio.NewReader(os.Stdin).ReadRune() //read rune read only one char
31 |
32 | // saves it in the strings builder
33 | entry.WriteRune(r)
34 |
35 | if entry.String() == code {
36 | state = Unlocked
37 | break
38 | }
39 |
40 | // change state in failed
41 | if strings.Index(code, entry.String()) != 0 {
42 | // code is wrong
43 | state = Failed
44 | }
45 | case Failed:
46 | fmt.Println("FAILED")
47 | // resets, start again
48 | entry.Reset()
49 | // locked again
50 | state = Locked
51 | case Unlocked:
52 | fmt.Println("UNLOCKED")
53 | // finish condition
54 | return
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/4-behavioural/strategy/main.go:
--------------------------------------------------------------------------------
1 | package strategy
2 |
3 | // Strategy
4 | // Separates an algorithm into its 'skleton' and
5 | // concrete implementation steps, which can be
6 | // varied at runtime
7 |
8 | // Define an algoritm at high level
9 | // then, define an interface you expect each strategy to follow
10 |
11 | // Support the injection of that strategy into the high level
12 |
13 | import (
14 | "fmt"
15 | "strings"
16 | )
17 |
18 | type OutputFormat int
19 |
20 | // to support the change of strategy
21 | const (
22 | Markdown OutputFormat = iota
23 | Html
24 | )
25 |
26 | // the strategy interfacce, implementation give us the well-defined path to follow
27 | type ListStrategy interface {
28 | Start(builder *strings.Builder)
29 | End(builder *strings.Builder)
30 | AddListItem(builder *strings.Builder, item string)
31 | }
32 |
33 | type MarkdownListStrategy struct{}
34 |
35 | // start and end empty cause in markdown we don't need them
36 | func (m *MarkdownListStrategy) Start(builder *strings.Builder) {
37 |
38 | }
39 |
40 | func (m *MarkdownListStrategy) End(builder *strings.Builder) {
41 | }
42 |
43 | func (m *MarkdownListStrategy) AddListItem(
44 | builder *strings.Builder, item string) {
45 | builder.WriteString(" * " + item + "\n")
46 | }
47 |
48 | type HtmlListStrategy struct{}
49 |
50 | // in html we need to define the tags
51 | func (h *HtmlListStrategy) Start(builder *strings.Builder) {
52 | builder.WriteString("\n")
53 | }
54 |
55 | func (h *HtmlListStrategy) End(builder *strings.Builder) {
56 | builder.WriteString("
\n")
57 | }
58 |
59 | func (h *HtmlListStrategy) AddListItem(builder *strings.Builder, item string) {
60 | builder.WriteString(" " + item + "\n")
61 | }
62 |
63 | // our high-level struct
64 | type TextProcessor struct {
65 | builder strings.Builder
66 | listStrategy ListStrategy
67 | }
68 |
69 | // pass the strategy
70 | func NewTextProcessor(listStrategy ListStrategy) *TextProcessor {
71 | return &TextProcessor{strings.Builder{}, listStrategy}
72 | }
73 |
74 | // redefine strategy
75 | func (t *TextProcessor) SetOutputFormat(fmt OutputFormat) {
76 | switch fmt {
77 | case Markdown:
78 | t.listStrategy = &MarkdownListStrategy{}
79 | case Html:
80 | t.listStrategy = &HtmlListStrategy{}
81 | }
82 | }
83 |
84 | func (t *TextProcessor) AppendList(items []string) {
85 | t.listStrategy.Start(&t.builder)
86 | for _, item := range items {
87 | t.listStrategy.AddListItem(&t.builder, item)
88 | }
89 | t.listStrategy.End(&t.builder)
90 | }
91 |
92 | // allows us to reuse the text processor with another strategy
93 | func (t *TextProcessor) Reset() {
94 | t.builder.Reset()
95 | }
96 |
97 | func (t *TextProcessor) String() string {
98 | return t.builder.String()
99 | }
100 |
101 | func main() {
102 | tp := NewTextProcessor(&MarkdownListStrategy{})
103 | tp.AppendList([]string{"foo", "bar", "baz"})
104 | fmt.Println(tp)
105 |
106 | tp.Reset()
107 | tp.SetOutputFormat(Html)
108 | tp.AppendList([]string{"foo", "bar", "baz"})
109 | fmt.Println(tp)
110 | }
111 |
--------------------------------------------------------------------------------
/4-behavioural/template-method/functional-approach/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | // notice that here we don't have interfaces or structs
6 | // just funcs
7 | func PlayGame(start, takeTurn func(),
8 | haveWinner func() bool,
9 | winningPlayer func() int) {
10 | start()
11 | for !haveWinner() {
12 | takeTurn()
13 | }
14 | fmt.Printf("Player %d wins.\n", winningPlayer())
15 | }
16 |
17 | func main() {
18 | turn, maxTurns, currentPlayer := 1, 10, 0
19 |
20 | // define the functions at runtime
21 | start := func() {
22 | fmt.Println("Starting a game of chess.")
23 | }
24 |
25 | takeTurn := func() {
26 | turn++
27 | fmt.Printf("Turn %d taken by player %d\n",
28 | turn, currentPlayer)
29 | currentPlayer = (currentPlayer + 1) % 2
30 | }
31 |
32 | haveWinner := func() bool {
33 | return turn == maxTurns
34 | }
35 |
36 | winningPlayer := func() int {
37 | return currentPlayer
38 | }
39 |
40 | //Let's play!
41 | PlayGame(start, takeTurn, haveWinner, winningPlayer)
42 | }
43 |
--------------------------------------------------------------------------------
/4-behavioural/template-method/main.go:
--------------------------------------------------------------------------------
1 | package templatemethod
2 |
3 | // Template method
4 | // A high level blue print for an algorithm to be completed by inheritors
5 |
6 | // It's typically just a function, not a struct with a ref to the impl
7 | // Can still use interfaces
8 | // Can be functional
9 |
10 | // A skleton of an algorithm defined in a function.
11 | // Function can either use an interface or receive several
12 | // functions as arguments
13 |
14 | import "fmt"
15 |
16 | type Game interface {
17 | Start()
18 | HaveWinner() bool
19 | TakeTurn()
20 | WinningPlayer() int
21 | }
22 |
23 | // the high-level algorithm
24 | func PlayGame(g Game) {
25 | g.Start()
26 | for !g.HaveWinner() {
27 | g.TakeTurn()
28 | }
29 | fmt.Printf("Player %d wins.\n", g.WinningPlayer())
30 | }
31 |
32 | type chess struct {
33 | turn, maxTurns, currentPlayer int
34 | }
35 |
36 | func NewGameOfChess() Game {
37 | return &chess{1, 10, 0}
38 | }
39 |
40 | // the implementations of the methods
41 | func (c *chess) Start() {
42 | fmt.Println("Starting a game of chess.")
43 | }
44 |
45 | func (c *chess) HaveWinner() bool {
46 | return c.turn == c.maxTurns
47 | }
48 |
49 | func (c *chess) TakeTurn() {
50 | c.turn++
51 | fmt.Printf("Turn %d taken by player %d\n",
52 | c.turn, c.currentPlayer)
53 | c.currentPlayer = (c.currentPlayer + 1) % 2
54 | }
55 |
56 | func (c *chess) WinningPlayer() int {
57 | return c.currentPlayer
58 | }
59 |
60 | func main() {
61 | chess := NewGameOfChess()
62 | PlayGame(chess)
63 | }
64 |
--------------------------------------------------------------------------------
/4-behavioural/visitor/classic/main.go:
--------------------------------------------------------------------------------
1 | package visitor
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type ExpressionVisitor interface {
9 | VisitDoubleExpression(de *DoubleExpression)
10 | VisitAdditionExpression(ae *AdditionExpression)
11 | }
12 |
13 | // the accept method allows us to visit the type that we want
14 | type Expression interface {
15 | Accept(ev ExpressionVisitor)
16 | }
17 |
18 | type DoubleExpression struct {
19 | value float64
20 | }
21 |
22 | // if double expresion
23 | func (d *DoubleExpression) Accept(ev ExpressionVisitor) {
24 | ev.VisitDoubleExpression(d)
25 | }
26 |
27 | type AdditionExpression struct {
28 | left, right Expression
29 | }
30 |
31 | // if addition expression
32 | func (a *AdditionExpression) Accept(ev ExpressionVisitor) {
33 | ev.VisitAdditionExpression(a)
34 | }
35 |
36 | // has the builder
37 | type ExpressionPrinter struct {
38 | sb strings.Builder
39 | }
40 |
41 | func (e *ExpressionPrinter) VisitDoubleExpression(de *DoubleExpression) {
42 | e.sb.WriteString(fmt.Sprintf("%g", de.value))
43 | }
44 |
45 | func (e *ExpressionPrinter) VisitAdditionExpression(ae *AdditionExpression) {
46 | e.sb.WriteString("(")
47 | ae.left.Accept(e)
48 | e.sb.WriteString("+")
49 | ae.right.Accept(e)
50 | e.sb.WriteString(")")
51 | }
52 |
53 | func NewExpressionPrinter() *ExpressionPrinter {
54 | return &ExpressionPrinter{strings.Builder{}}
55 | }
56 |
57 | func (e *ExpressionPrinter) String() string {
58 | return e.sb.String()
59 | }
60 |
61 | func main() {
62 | // 1+(2+3)
63 | e := &AdditionExpression{
64 | &DoubleExpression{1},
65 | &AdditionExpression{
66 | left: &DoubleExpression{2},
67 | right: &DoubleExpression{3},
68 | },
69 | }
70 |
71 | ep := NewExpressionPrinter()
72 | ep.VisitAdditionExpression(e)
73 | fmt.Println(ep.String())
74 | }
75 |
--------------------------------------------------------------------------------
/4-behavioural/visitor/intrusive/main.go:
--------------------------------------------------------------------------------
1 | package visitor
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type Expression interface {
9 | Print(sb *strings.Builder)
10 | }
11 |
12 | // let's call this number... or something
13 | type DoubleExpression struct {
14 | value float64
15 | }
16 |
17 | // just prints the value
18 | func (d *DoubleExpression) Print(sb *strings.Builder) {
19 | sb.WriteString(fmt.Sprintf("%g", d.value))
20 | }
21 |
22 | type AdditionExpression struct {
23 | left, right Expression
24 | }
25 |
26 | // it's intrusive cause you pass the sb over the hierarchy
27 | // violating the open-closed principle
28 | func (a *AdditionExpression) Print(sb *strings.Builder) {
29 | sb.WriteString("(")
30 | // prints an expression
31 | a.left.Print(sb)
32 | sb.WriteString("+")
33 | // prints an expression
34 | a.right.Print(sb)
35 | sb.WriteString(")")
36 | }
37 |
38 | func main() {
39 | // 1+(2+3)
40 | e := AdditionExpression{
41 | &DoubleExpression{1},
42 | &AdditionExpression{
43 | left: &DoubleExpression{2},
44 | right: &DoubleExpression{3},
45 | },
46 | }
47 |
48 | sb := strings.Builder{}
49 | e.Print(&sb)
50 |
51 | fmt.Println(sb.String())
52 | }
53 |
--------------------------------------------------------------------------------
/4-behavioural/visitor/main.go:
--------------------------------------------------------------------------------
1 | package visitor
2 |
3 | // Visitor
4 | // Allows adding extra behavior to entire hierarchies of types
5 |
6 | // A pattern where a component (visitor) is allowed to traverse
7 | // the entire hierarchy of types.
8 | // Implemented by propagating a single Accept() method
9 | // throughout the entire hierarchy
10 |
--------------------------------------------------------------------------------
/4-behavioural/visitor/reflective/main.go:
--------------------------------------------------------------------------------
1 | package visitor
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type Expression interface {
9 | // nothing here!
10 | }
11 |
12 | type DoubleExpression struct {
13 | value float64
14 | }
15 |
16 | type AdditionExpression struct {
17 | left, right Expression
18 | }
19 |
20 | // have to cast the type
21 | func Print(e Expression, sb *strings.Builder) {
22 | if de, ok := e.(*DoubleExpression); ok {
23 | sb.WriteString(fmt.Sprintf("%g", de.value))
24 | } else if ae, ok := e.(*AdditionExpression); ok {
25 | sb.WriteString("(")
26 | Print(ae.left, sb)
27 | sb.WriteString("+")
28 | Print(ae.right, sb)
29 | sb.WriteString(")")
30 | }
31 |
32 | // breaks OCP
33 | // will work incorrectly on missing case
34 | }
35 |
36 | func main() {
37 | // 1+(2+3)
38 | e := &AdditionExpression{
39 | &DoubleExpression{1},
40 | &AdditionExpression{
41 | left: &DoubleExpression{2},
42 | right: &DoubleExpression{3},
43 | },
44 | }
45 |
46 | sb := strings.Builder{}
47 | Print(e, &sb)
48 |
49 | fmt.Println(sb.String())
50 | }
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # design-patterns-go
2 |
3 | Tasks done in the Udemy Design Patterns in Go course
4 |
5 | In this course we will cover:
6 |
7 | - SOLID Principles
8 | - Creational patterns: are concerned with object creation mechanisms, trying to create objects in a manner suitable to the situation.
9 | - Structural patterns: are primarily concerned with object composition, allowing you to create more flexible and efficient relationships between objects.
10 | - Behavioural patterns: focus on how objects collaborate and interact to accomplish tasks, responsibilities, and behaviors within a software system.
11 |
12 | Enjoy 🤓🤍
13 |
--------------------------------------------------------------------------------