interfaces: refactor

This commit is contained in:
Inanc Gumus
2019-08-19 10:21:11 +03:00
parent b95be49711
commit 158f475a2d
89 changed files with 784 additions and 2 deletions

View File

@ -0,0 +1,36 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type book struct {
title string
price float64
}
func (b book) print() {
// b is a copy of the original `book` value here.
fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
}
// ----------------------------------------------------------------------------
// + you can use the same method names among different types.
// + you don't need to type `printGame`, it's just: `print`.
//
// func (b book) printBook() {
// // b is a copy of the original `book` value here.
// fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
// }
// ----------------------------------------------------------------------------
// b is a copy of the original `book` value here.
//
// func printBook(b book) {
// fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
// }

View File

@ -0,0 +1,36 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type game struct {
title string
price float64
}
func (g game) print() {
fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
}
// PREVIOUS CODE:
// ----------------------------------------------------------------------------
// you can use same method name among different types.
// you don't need to type `printGame`, it's just: `print`.
//
// func (g game) printGame() {
// fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
// }
// ----------------------------------------------------------------------------
// you cannot use the same function name within the same package.
//
// func printGame(g game) {
// fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
// }

View File

@ -0,0 +1,46 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
func main() {
mobydick := book{
title: "moby dick",
price: 10,
}
minecraft := game{
title: "minecraft",
price: 20,
}
tetris := game{
title: "tetris",
price: 5,
}
// #4: method expressions
// methods are just functions that receive a value of a type.
game.print(minecraft) // sends `minecraft` value to `game.print`
game.print(tetris) // sends `tetris` value to `game.print`
fmt.Println()
// #3
mobydick.print() // sends `mobydick` value to `book.print`
minecraft.print() // sends `minecraft` value to `game.print`
tetris.print() // sends `tetris` value to `game.print`
// #2
// mobydick.printBook()
// minecraft.printGame()
// #1
// printBook(mobydick)
// printGame(minecraft)
}

View File

@ -0,0 +1,20 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type book struct {
title string
price float64
}
func (b book) print() {
// b is a copy of the original `book` value here.
fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
}

View File

@ -0,0 +1,34 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type game struct {
title string
price float64
}
func (g *game) print() {
fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
}
// + discount gets a copy of `*game`.
// + discount can update the original `game` through the game pointer.
// + it's better to use the same receiver type: `*game` for all methods.
func (g *game) discount(ratio float64) {
g.price *= (1 - ratio)
}
// PREVIOUS CODE:
// ----------------------------------------------------------------------------
// + `g` is a copy: `discount` cannot change the original `g`.
// func (g game) discount(ratio float64) {
// g.price *= (1 - ratio)
// }

View File

@ -0,0 +1,28 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
// + you can attach methods to any concrete type.
// + rule: you need to declare a new type in the same package.
type list []game
func (l list) print() {
// `list` acts like a `[]game`
if len(l) == 0 {
fmt.Println("Sorry. Our store is closed. We're waiting for the delivery 🚚.")
return
}
fmt.Printf("My Store:\n")
fmt.Printf("---------\n")
for _, it := range l {
it.print()
}
}

View File

@ -0,0 +1,27 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
func main() {
var (
// mobydick = book{title: "moby dick", price: 10}
minecraft = game{title: "minecraft", price: 20}
tetris = game{title: "tetris", price: 5}
)
var items []game
items = append(items, minecraft, tetris)
// you can attach methods to a compatible type on the fly:
my := list([]game{minecraft, tetris})
// you can call methods even on a nil value
// my = nil
my.print() // or: list.print(items)
}

View File

@ -0,0 +1,20 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type book struct {
title string
price float64
}
func (b book) print() {
// b is a copy of the original `book` value here.
fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
}

View File

@ -0,0 +1,34 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type game struct {
title string
price float64
}
func (g *game) print() {
fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
}
// + discount gets a copy of `*game`.
// + discount can update the original `game` through the game pointer.
// + it's better to use the same receiver type: `*game` for all methods.
func (g *game) discount(ratio float64) {
g.price *= (1 - ratio)
}
// PREVIOUS CODE:
// ----------------------------------------------------------------------------
// + `g` is a copy: `discount` cannot change the original `g`.
// func (g game) discount(ratio float64) {
// g.price *= (1 - ratio)
// }

View File

@ -0,0 +1,33 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type printer interface {
print()
}
type list []printer
func (l list) print() {
if len(l) == 0 {
fmt.Println("Sorry. Our store is closed. We're waiting for the delivery 🚚.")
return
}
fmt.Printf("My Store:\n")
fmt.Printf("---------\n")
for _, it := range l {
it.print()
// you cannot access to the discount method of the game type.
// `it` is a printer not a game.
// it.discount(.5)
}
}

View File

@ -0,0 +1,52 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
func main() {
var (
mobydick = book{title: "moby dick", price: 10}
minecraft = game{title: "minecraft", price: 20}
tetris = game{title: "tetris", price: 5}
rubik = puzzle{title: "rubik's cube", price: 5}
)
var store list
store = append(store, &minecraft, &tetris, mobydick, rubik)
tetris.discount(.8)
// printer(tetris).discount(.8)
store.print()
}
/*
var my list
// + only the `*game` have methods.
// + `game` doesn't have any methods.
// + `item` interface couldn't be satisfied with a `game` value.
// + it can only be satisfied with a `*game` value.
// my = my.add(minecraft)
my = append(my, &minecraft)
my = append(my, &tetris)
my = append(my, &mobydick)
// interface value stores a pointer to minecraft
minecraft.discount(.5)
my.list()
my.discount(.5)
my.list()
*/
// ! cannot add mobydick to the store: it's a book type, not a game type.
// interfaces to the rescue!
// mobydick := book{title: "moby dick", price: 10}
// my.add(&mobydick)
// ----------------------------------------------------------------

View File

@ -0,0 +1,19 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type puzzle struct {
title string
price float64
}
func (p puzzle) print() {
fmt.Printf("%-15s: $%.2f \n", p.title, p.price)
}

View File

@ -0,0 +1,24 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type book struct {
title string
price float64
}
func (b book) print() {
fmt.Printf("%-15s: $%.2f \n", b.title, b.price)
}
// TODO: NEW
func (b book) total() float64 {
return b.price
}

View File

@ -0,0 +1,28 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
type game struct {
title string
price float64
}
func (g *game) print() {
fmt.Printf("%-15s: $%.2f\n", g.title, g.price)
}
func (g *game) discount(ratio float64) {
g.price *= (1 - ratio)
}
// TODO: NEW
func (g *game) total() float64 {
return g.price
}

View File

@ -0,0 +1,71 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import "fmt"
// TODO: NEW
type printer interface {
print()
}
// TODO: NEW
type pricer interface {
total() float64
}
// TODO: NEW
// interface embedding
type item interface {
printer
pricer
}
// a slice of item interface values
type list []item
func (l list) list() {
// `list` acts like a `[]game`
if len(l) == 0 {
fmt.Println("Sorry. Our store is closed. We're waiting for the delivery 🚚.")
return
}
fmt.Printf("My Store:\n")
fmt.Printf("---------\n")
for _, it := range l {
it.print()
}
// TODO: NEW
fmt.Printf("\nGROSS: $%.2f\n", l.total())
}
// TODO: NEW
func (l list) total() (gross float64) {
for _, it := range l {
gross += it.total()
}
return gross
}
// TODO: NEW
func (l list) discount(ratio float64) {
type discounter interface {
discount(float64)
}
for _, it := range l {
dit, ok := it.(discounter)
if !ok {
continue
}
dit.discount(ratio)
}
}

View File

@ -0,0 +1,35 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
func main() {
var (
minecraft = game{title: "minecraft", price: 20}
tetris = game{title: "tetris", price: 5}
mobydick = book{title: "moby dick", price: 10}
)
var my list
// my.list() // nil receiver is a valid value
// + only the `*game` have methods.
// + `game` doesn't have any methods.
// + `item` interface couldn't be satisfied with a `game` value.
// + it can only be satisfied with a `*game` value.
// my = my.add(minecraft)
my = append(my, &minecraft)
my = append(my, &tetris)
my = append(my, &mobydick)
// interface value stores a pointer to minecraft
minecraft.discount(.5)
my.list()
my.discount(.5)
my.list()
}

View File

@ -0,0 +1,36 @@
# Problem
## All these functions operate on a *parser value:
+ `main.go`:
+ `summarize(*p parser)`
+ `parser.go`:
+ `func parse(p *parser, line string) (r result)`
+ `func update(p *parser, r result)`
+ `func err(p *parser) error`
## What is an object in Go?
+ Object = A value of a type : `&parser{}` is an object.
+ `type parser struct` is the definition (class/blueprint) of the object.
### Methods are behaviors of objects:
+ Parser needs to parse, update (analyse) the parsings, and report an error if any.
+ Parser also needs to summarize the results.
+ So, all these functions belong to a parser object.
# Solution
+ Attach the functions to the parser using methods.
+ Methods are the behavior of the parser.
+ Keep the methods that belong to a single type in the same file.
1. Move the `summarize` into `parser.go`.
2. `parser.go`: Use methods.
3. `main()` : Use methods of the `parser`.
# Convince
+ `p.` shows the fields and methods belong to the `parser`.
+ `p.` selects a method from the method set of the `parser`.
+ `p.method` sends the `p` as the first parameter to the method.
+ `p` is the receiver: `*pointer`. It's a pointer receiver.
+ Receiver is copied to the method.

View File

@ -0,0 +1,16 @@
learngoprogramming.com 10
learngoprogramming.com 15
learngoprogramming.com 10
learngoprogramming.com 20
learngoprogramming.com 5
golang.org 40
golang.org 20
golang.org 45
golang.org 15
blog.golang.org 60
blog.golang.org 30
blog.golang.org 20
blog.golang.org 65
blog.golang.org 15
inanc.io 30
inanc.io 70

View File

@ -0,0 +1,16 @@
learngoprogramming.com 10
learngoprogramming.com 15
learngoprogramming.com 10
learngoprogramming.com 20
learngoprogramming.com
golang.org 40
golang.org 20
golang.org 45
golang.org 15
blog.golang.org 60
blog.golang.org 30
blog.golang.org 20
blog.golang.org 65
blog.golang.org 15
inanc.io 30
inanc.io 70

View File

@ -0,0 +1,16 @@
learngoprogramming.com 10
learngoprogramming.com 15
learngoprogramming.com 10
learngoprogramming.com 20
learngoprogramming.com 5
golang.org 40
golang.org 20
golang.org -50
golang.org 15
blog.golang.org 60
blog.golang.org 30
blog.golang.org 20
blog.golang.org 65
blog.golang.org 15
inanc.io 30
inanc.io 70

View File

@ -0,0 +1,16 @@
learngoprogramming.com 10
learngoprogramming.com 15
learngoprogramming.com 10
learngoprogramming.com 20
learngoprogramming.com 5
golang.org FORTY
golang.org 20
golang.org 45
golang.org 15
blog.golang.org 60
blog.golang.org 30
blog.golang.org 20
blog.golang.org 65
blog.golang.org 15
inanc.io 30
inanc.io 70

View File

@ -0,0 +1,39 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
p := newParser()
fmt.Printf("%T\n", (*parser).parse)
return
in := bufio.NewScanner(os.Stdin)
for in.Scan() {
parsed := p.parse(in.Text())
p.update(parsed)
}
p.summarize()
dumpErrs([]error{in.Err(), p.err()})
}
// dumpErrs together to simplify multiple error handling
func dumpErrs(errs []error) {
for _, err := range errs {
if err != nil {
fmt.Println("> Err:", err)
}
}
}

View File

@ -0,0 +1,100 @@
// For more tutorials: https://blog.learngoprogramming.com
//
// Copyright © 2018 Inanc Gumus
// Learn Go Programming Course
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
//
package main
import (
"fmt"
"sort"
"strconv"
"strings"
)
// result stores details about a log line
type result struct {
domain string
visits int
// add more metrics when needed
}
// parser keep tracks of the parsing
type parser struct {
sum map[string]result // metrics per domain
domains []string // unique domain names
total int // total visits for all domains
lines int // number of parsed lines (for the error messages)
lerr error // the last error occurred
}
// newParser creates and returns a new parser
func newParser() *parser {
return &parser{sum: make(map[string]result)}
}
// parse result from a log line
func (p *parser) parse(line string) (r result) {
if p.lerr != nil {
return
}
p.lines++
fields := strings.Fields(line)
if len(fields) != 2 {
p.lerr = fmt.Errorf("wrong input: %v (line #%d)", fields, p.lines)
return
}
var err error
r.domain = fields[0]
r.visits, err = strconv.Atoi(fields[1])
if r.visits < 0 || err != nil {
p.lerr = fmt.Errorf("wrong input: %q (line #%d)", fields[1], p.lines)
}
return
}
// update the parsing results
func (p *parser) update(r result) {
if p.lerr != nil {
return
}
// collect unique domains for easy access to sum map
if _, ok := p.sum[r.domain]; !ok {
p.domains = append(p.domains, r.domain)
}
// keep track of total visits
p.total += r.visits
// group the log lines by domain
p.sum[r.domain] = result{
domain: r.domain,
visits: r.visits + p.sum[r.domain].visits,
}
}
// summarize the parsing results
func (p *parser) summarize() {
sort.Strings(p.domains)
fmt.Printf("%-30s %10s\n", "DOMAIN", "VISITS")
fmt.Println(strings.Repeat("-", 45))
for _, domain := range p.domains {
fmt.Printf("%-30s %10d\n", domain, p.sum[domain].visits)
}
fmt.Printf("\n%-30s %10d\n", "TOTAL", p.total)
}
// err returns the last error encountered
func (p *parser) err() error {
return p.lerr
}

View File

@ -11,7 +11,7 @@ import (
"bufio"
"os"
"github.com/inancgumus/learngo/27-interfaces/logparser-testing/report"
"github.com/inancgumus/learngo/interfaces/04-log-parser/testing/report"
)
func main() {

View File

@ -13,7 +13,7 @@ import (
"os"
"strings"
"github.com/inancgumus/learngo/27-interfaces/logparser-testing/report"
"github.com/inancgumus/learngo/interfaces/04-log-parser/testing/report"
)
// summarize prints the parsing results.