├── 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\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("") 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") 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 | --------------------------------------------------------------------------------