refactor: stringer
This commit is contained in:
46
interfaces/09-stringer/_handlemethods.go
Normal file
46
interfaces/09-stringer/_handlemethods.go
Normal 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()
|
||||
*/
|
@ -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 value—a copy.
|
||||
// It's because: b.product would be a different value, a copy.
|
||||
return fmt.Sprintf("%s - (%s)", &b.product, b.published)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user