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
|
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 {
|
func (b *book) String() string {
|
||||||
// product.String() has a pointer receiver.
|
// 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.
|
// 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)
|
return fmt.Sprintf("%s - (%s)", &b.product, b.published)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
type game struct {
|
type game struct {
|
||||||
// game satisfies the fmt.Stringer
|
// game is an fmt.Stringer
|
||||||
// because the product satisfies the fmt.Stringer
|
// because the product is an fmt.Stringer
|
||||||
|
// and the game embeds the product
|
||||||
product
|
product
|
||||||
}
|
}
|
||||||
|
@ -15,31 +15,38 @@ import (
|
|||||||
type item interface {
|
type item interface {
|
||||||
discount(ratio float64)
|
discount(ratio float64)
|
||||||
|
|
||||||
|
// ~~ interface embedding ~~
|
||||||
|
//
|
||||||
// item interface embeds the fmt.Stringer interface.
|
// item interface embeds the fmt.Stringer interface.
|
||||||
//
|
//
|
||||||
// Go adds all the methods of the fmt.Stringer
|
// Go adds the methods of the fmt.Stringer
|
||||||
// to the item interface.
|
// to this item interface.
|
||||||
fmt.Stringer // same (see below): String() string
|
//
|
||||||
|
// same as this:
|
||||||
// String() string
|
// String() string
|
||||||
|
fmt.Stringer
|
||||||
}
|
}
|
||||||
|
|
||||||
type list []item
|
type list []item
|
||||||
|
|
||||||
// list satisfies the fmt.Stringer
|
// String method makes the list an fmt.Stringer.
|
||||||
func (l list) String() string {
|
func (l list) String() string {
|
||||||
if len(l) == 0 {
|
if len(l) == 0 {
|
||||||
return "Sorry. We're waiting for delivery 🚚."
|
return "Sorry. We're waiting for delivery 🚚."
|
||||||
}
|
}
|
||||||
|
|
||||||
// use strings.Builder when you're combining unknown
|
// use the strings.Builder when you're combining
|
||||||
// list of strings together.
|
// a long list of strings together.
|
||||||
var str strings.Builder
|
var str strings.Builder
|
||||||
|
|
||||||
for _, it := range l {
|
for _, it := range l {
|
||||||
// the builder doesn't know about the stringer interface.
|
// the builder.WriteString doesn't know about the stringer interface.
|
||||||
// that's why you need to call String method here.
|
// because it takes a string argument.
|
||||||
|
// so, you need to call the String method yourself.
|
||||||
str.WriteString(it.String())
|
str.WriteString(it.String())
|
||||||
str.WriteRune('\n')
|
str.WriteRune('\n')
|
||||||
|
|
||||||
|
// or slower way:
|
||||||
|
// fmt.Fprintln(&str, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
return str.String()
|
return str.String()
|
||||||
|
@ -10,10 +10,15 @@ package main
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
func main() {
|
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{
|
store := list{
|
||||||
&book{product{"moby dick", 10}, toTimestamp(118281600)},
|
&book{product{"moby dick", 10}, toTimestamp(118281600)},
|
||||||
&book{product{"odyssey", 15}, toTimestamp("733622400")},
|
&book{product{"odyssey", 15}, toTimestamp("733622400")},
|
||||||
&book{product{"hobbit", 25}, unknown},
|
&book{product{"hobbit", 25}, toTimestamp(nil)},
|
||||||
&puzzle{product{"rubik's cube", 5}},
|
&puzzle{product{"rubik's cube", 5}},
|
||||||
&game{product{"minecraft", 20}},
|
&game{product{"minecraft", 20}},
|
||||||
&game{product{"tetris", 5}},
|
&game{product{"tetris", 5}},
|
||||||
@ -22,12 +27,12 @@ func main() {
|
|||||||
|
|
||||||
store.discount(.5)
|
store.discount(.5)
|
||||||
|
|
||||||
// the store doesn't have a print method anymore.
|
// The list is a stringer.
|
||||||
// but the Print function can print it.
|
// The `fmt.Print` function can print the `store`
|
||||||
// it's because, the list satisfies the stringer.
|
// 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)
|
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
|
type money float64
|
||||||
|
|
||||||
// money satisfies the fmt.Stringer
|
// String makes the money an fmt.Stringer
|
||||||
func (m money) String() string {
|
func (m money) String() string {
|
||||||
return fmt.Sprintf("$%.2f", m)
|
return fmt.Sprintf("$%.2f", m)
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ type product struct {
|
|||||||
price money
|
price money
|
||||||
}
|
}
|
||||||
|
|
||||||
// product satisfies the fmt.Stringer
|
// String makes the product an fmt.Stringer
|
||||||
func (p *product) String() string {
|
func (p *product) String() string {
|
||||||
return fmt.Sprintf("%-15s: %s", p.title, p.price)
|
return fmt.Sprintf("%-15s: %s", p.title, p.price)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
type puzzle struct {
|
type puzzle struct {
|
||||||
// game satisfies the fmt.Stringer
|
// puzzle is an fmt.Stringer
|
||||||
// because the product satisfies the fmt.Stringer
|
// because the product is an fmt.Stringer
|
||||||
|
// and the puzzle embeds the product
|
||||||
product
|
product
|
||||||
}
|
}
|
||||||
|
@ -12,40 +12,42 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
// timestamp stores, formats and automatically prints a timestamp: it's a stringer.
|
||||||
const layout = "2006/01"
|
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.
|
// String method makes the timestamp an fmt.stringer.
|
||||||
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.
|
|
||||||
func (ts timestamp) String() string {
|
func (ts timestamp) String() string {
|
||||||
t := time.Time(ts)
|
if ts.IsZero() {
|
||||||
|
|
||||||
if t.IsZero() {
|
|
||||||
return "unknown"
|
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.
|
// toTimestamp returns a timestamp value depending on the type of `v`.
|
||||||
// Now it returns a timestamp value depending on the type of `v`.
|
// toTimestamp was "book.format()" before.
|
||||||
func toTimestamp(v interface{}) timestamp {
|
func toTimestamp(v interface{}) timestamp {
|
||||||
var t int
|
var t int
|
||||||
|
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case int:
|
case int:
|
||||||
|
// book{title: "moby dick", price: 10, published: 118281600},
|
||||||
t = v
|
t = v
|
||||||
case string:
|
case string:
|
||||||
|
// book{title: "odyssey", price: 15, published: "733622400"},
|
||||||
t, _ = strconv.Atoi(v)
|
t, _ = strconv.Atoi(v)
|
||||||
default:
|
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
|
package main
|
||||||
|
|
||||||
type toy struct {
|
type toy struct {
|
||||||
// game satisfies the fmt.Stringer
|
// toy is an fmt.Stringer
|
||||||
// because the product satisfies the fmt.Stringer
|
// because the product is an fmt.Stringer
|
||||||
|
// and the toy embeds the product
|
||||||
product
|
product
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -58,6 +55,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mon Jan 2 15:04:05 -0700 MST 2006
|
|
||||||
const layout = "2006/01"
|
|
||||||
|
|
||||||
// unknown is the zero value of a timestamp.
|
// unknown is the zero value of a timestamp.
|
||||||
var unknown = timestamp(time.Time{})
|
var unknown = timestamp(time.Time{})
|
||||||
|
|
||||||
@ -57,6 +54,9 @@ func (ts timestamp) String() string {
|
|||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mon Jan 2 15:04:05 -0700 MST 2006
|
||||||
|
const layout = "2006/01"
|
||||||
|
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user