refactor: stringer

This commit is contained in:
Inanc Gumus
2019-09-27 19:09:07 +03:00
parent 0127b45c1e
commit e6eba7314f
16 changed files with 130 additions and 65 deletions

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/
//
// In the depths of the Go standard library's fmt package...
// Printing functions use the handleMethods method.
// Example:
// var pocket money = 10
// fmt.Println(pocket)
// the argument can be any type of value
// stores the pocket variable in the argument variable
// ^
// |
func (p *pp) handleMethods(argument interface{}) (handled bool) {
// ...
// Checks whether a given argument is an error or an fmt.Stringer
switch v := argument.(type) {
// ...
// If the argument is a Stringer, calls its String() method
case Stringer:
// ...
// pocket.String()
// ^
// |
p.fmtString(v.String(), verb)
return
}
// ...
}
/*
The original `handleMethods` code is more involved:
https://github.com/golang/go/blob/6f51082da77a1d4cafd5b7af0db69293943f4066/src/fmt/print.go#L574
-> 574#handleMethods(..)
-> 627#Stringer type check: If `v` is a Stringer, run:
-> 630#v.String()
*/

View File

@ -16,13 +16,15 @@ type book struct {
published timestamp
}
// book satisfies the fmt.Stringer
// String method makes the book an fmt.Stringer.
// The list was calling the embedded product type's String(),
// now it calls the book.String().
func (b *book) String() string {
// product.String() has a pointer receiver.
// That's why you need to manually take the product's address here.
// Therefore, you need to manually take the product's address.
//
// If you pass: "b.product", Go would pass it as a copy to Sprintf.
// If you pass: "b.product", Go would pass it as a copy to `Sprintf`.
// In that case, Go can't deference b.product automatically.
// It's because: b.product would be different valuea copy.
// It's because: b.product would be a different value, a copy.
return fmt.Sprintf("%s - (%s)", &b.product, b.published)
}

View File

@ -8,7 +8,8 @@
package main
type game struct {
// game satisfies the fmt.Stringer
// because the product satisfies the fmt.Stringer
// game is an fmt.Stringer
// because the product is an fmt.Stringer
// and the game embeds the product
product
}

View File

@ -15,31 +15,38 @@ import (
type item interface {
discount(ratio float64)
// ~~ interface embedding ~~
//
// item interface embeds the fmt.Stringer interface.
//
// Go adds all the methods of the fmt.Stringer
// to the item interface.
fmt.Stringer // same (see below): String() string
// Go adds the methods of the fmt.Stringer
// to this item interface.
//
// same as this:
// String() string
fmt.Stringer
}
type list []item
// list satisfies the fmt.Stringer
// String method makes the list an fmt.Stringer.
func (l list) String() string {
if len(l) == 0 {
return "Sorry. We're waiting for delivery 🚚."
}
// use strings.Builder when you're combining unknown
// list of strings together.
// use the strings.Builder when you're combining
// a long list of strings together.
var str strings.Builder
for _, it := range l {
// the builder doesn't know about the stringer interface.
// that's why you need to call String method here.
// the builder.WriteString doesn't know about the stringer interface.
// because it takes a string argument.
// so, you need to call the String method yourself.
str.WriteString(it.String())
str.WriteRune('\n')
// or slower way:
// fmt.Fprintln(&str, it)
}
return str.String()

View File

@ -10,10 +10,15 @@ package main
import "fmt"
func main() {
// The money type is a stringer.
// You don't need to call the String method when printing a value of it.
// var pocket money = 10
// fmt.Println(pocket)
store := list{
&book{product{"moby dick", 10}, toTimestamp(118281600)},
&book{product{"odyssey", 15}, toTimestamp("733622400")},
&book{product{"hobbit", 25}, unknown},
&book{product{"hobbit", 25}, toTimestamp(nil)},
&puzzle{product{"rubik's cube", 5}},
&game{product{"minecraft", 20}},
&game{product{"tetris", 5}},
@ -22,12 +27,12 @@ func main() {
store.discount(.5)
// the store doesn't have a print method anymore.
// but the Print function can print it.
// it's because, the list satisfies the stringer.
// The list is a stringer.
// The `fmt.Print` function can print the `store`
// by calling `store`'s `String()` method.
//
// Underneath, `fmt.Print` uses a type switch to
// detect whether a type is a Stringer:
// https://golang.org/src/fmt/print.go#L627
fmt.Print(store)
// timestamp is useful even if it's zero.
var ts timestamp
fmt.Println(ts)
}

View File

@ -11,7 +11,7 @@ import "fmt"
type money float64
// money satisfies the fmt.Stringer
// String makes the money an fmt.Stringer
func (m money) String() string {
return fmt.Sprintf("$%.2f", m)
}

View File

@ -14,7 +14,7 @@ type product struct {
price money
}
// product satisfies the fmt.Stringer
// String makes the product an fmt.Stringer
func (p *product) String() string {
return fmt.Sprintf("%-15s: %s", p.title, p.price)
}

View File

@ -8,7 +8,8 @@
package main
type puzzle struct {
// game satisfies the fmt.Stringer
// because the product satisfies the fmt.Stringer
// puzzle is an fmt.Stringer
// because the product is an fmt.Stringer
// and the puzzle embeds the product
product
}

View File

@ -12,40 +12,42 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// timestamp stores, formats and automatically prints a timestamp: it's a stringer.
type timestamp struct {
// timestamp embeds a time, therefore it can be used as a time value.
// there is no need to convert a time value to a timestamp value.
time.Time
}
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
// Timestamp prints timestamps, it's a stringer.
// Timestamp is useful even if it's zero.
type timestamp time.Time
// String makes the timestamp a stringer.
// String method makes the timestamp an fmt.stringer.
func (ts timestamp) String() string {
t := time.Time(ts)
if t.IsZero() {
if ts.IsZero() {
return "unknown"
}
return t.Format(layout)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return ts.Format(layout)
}
// toTimestamp was "book.format" before.
// Now it returns a timestamp value depending on the type of `v`.
// toTimestamp returns a timestamp value depending on the type of `v`.
// toTimestamp was "book.format()" before.
func toTimestamp(v interface{}) timestamp {
var t int
switch v := v.(type) {
case int:
// book{title: "moby dick", price: 10, published: 118281600},
t = v
case string:
// book{title: "odyssey", price: 15, published: "733622400"},
t, _ = strconv.Atoi(v)
default:
return unknown
// book{title: "hobbit", price: 25},
return timestamp{}
}
return timestamp(time.Unix(int64(t), 0))
return timestamp{
Time: time.Unix(int64(t), 0),
}
}

View File

@ -8,7 +8,8 @@
package main
type toy struct {
// game satisfies the fmt.Stringer
// because the product satisfies the fmt.Stringer
// toy is an fmt.Stringer
// because the product is an fmt.Stringer
// and the toy embeds the product
product
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -58,6 +55,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}

View File

@ -12,9 +12,6 @@ import (
"time"
)
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
// unknown is the zero value of a timestamp.
var unknown = timestamp(time.Time{})
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
return "unknown"
}
// Mon Jan 2 15:04:05 -0700 MST 2006
const layout = "2006/01"
return t.Format(layout)
}