From e6eba7314fd2978a464c4841ae3fe0e5269e574a Mon Sep 17 00:00:00 2001 From: Inanc Gumus Date: Fri, 27 Sep 2019 19:09:07 +0300 Subject: [PATCH] refactor: stringer --- interfaces/09-stringer/_handlemethods.go | 46 +++++++++++++++++++ interfaces/09-stringer/book.go | 10 ++-- interfaces/09-stringer/game.go | 5 +- interfaces/09-stringer/list.go | 25 ++++++---- interfaces/09-stringer/main.go | 21 +++++---- interfaces/09-stringer/money.go | 2 +- interfaces/09-stringer/product.go | 2 +- interfaces/09-stringer/puzzle.go | 5 +- interfaces/09-stringer/timestamp.go | 38 +++++++-------- interfaces/09-stringer/toy.go | 5 +- interfaces/10-marshaler/timestamp.go | 6 +-- interfaces/11-decode-toiface/timestamp.go | 6 +-- interfaces/12-reflection/timestamp.go | 6 +-- interfaces/13-reflection-2/timestamp.go | 6 +-- interfaces/14-io-reader/timestamp.go | 6 +-- .../15-io-reader-composition/timestamp.go | 6 +-- 16 files changed, 130 insertions(+), 65 deletions(-) create mode 100644 interfaces/09-stringer/_handlemethods.go diff --git a/interfaces/09-stringer/_handlemethods.go b/interfaces/09-stringer/_handlemethods.go new file mode 100644 index 0000000..e6f362b --- /dev/null +++ b/interfaces/09-stringer/_handlemethods.go @@ -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() +*/ \ No newline at end of file diff --git a/interfaces/09-stringer/book.go b/interfaces/09-stringer/book.go index ee5d00a..75ba2c8 100644 --- a/interfaces/09-stringer/book.go +++ b/interfaces/09-stringer/book.go @@ -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) } diff --git a/interfaces/09-stringer/game.go b/interfaces/09-stringer/game.go index 686fa81..a73291b 100644 --- a/interfaces/09-stringer/game.go +++ b/interfaces/09-stringer/game.go @@ -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 } diff --git a/interfaces/09-stringer/list.go b/interfaces/09-stringer/list.go index d640d7e..465cc73 100644 --- a/interfaces/09-stringer/list.go +++ b/interfaces/09-stringer/list.go @@ -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() diff --git a/interfaces/09-stringer/main.go b/interfaces/09-stringer/main.go index 05e9862..3708391 100644 --- a/interfaces/09-stringer/main.go +++ b/interfaces/09-stringer/main.go @@ -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) } diff --git a/interfaces/09-stringer/money.go b/interfaces/09-stringer/money.go index a246529..aa661f9 100644 --- a/interfaces/09-stringer/money.go +++ b/interfaces/09-stringer/money.go @@ -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) } diff --git a/interfaces/09-stringer/product.go b/interfaces/09-stringer/product.go index ef28671..08b7449 100644 --- a/interfaces/09-stringer/product.go +++ b/interfaces/09-stringer/product.go @@ -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) } diff --git a/interfaces/09-stringer/puzzle.go b/interfaces/09-stringer/puzzle.go index 1a4a6d7..2e4572d 100644 --- a/interfaces/09-stringer/puzzle.go +++ b/interfaces/09-stringer/puzzle.go @@ -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 } diff --git a/interfaces/09-stringer/timestamp.go b/interfaces/09-stringer/timestamp.go index b8cd2a4..87a4c62 100644 --- a/interfaces/09-stringer/timestamp.go +++ b/interfaces/09-stringer/timestamp.go @@ -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), + } } diff --git a/interfaces/09-stringer/toy.go b/interfaces/09-stringer/toy.go index 9c630c8..9d97da4 100644 --- a/interfaces/09-stringer/toy.go +++ b/interfaces/09-stringer/toy.go @@ -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 } diff --git a/interfaces/10-marshaler/timestamp.go b/interfaces/10-marshaler/timestamp.go index dad84a2..aa8c3d8 100644 --- a/interfaces/10-marshaler/timestamp.go +++ b/interfaces/10-marshaler/timestamp.go @@ -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) } diff --git a/interfaces/11-decode-toiface/timestamp.go b/interfaces/11-decode-toiface/timestamp.go index 28629d8..ab29528 100644 --- a/interfaces/11-decode-toiface/timestamp.go +++ b/interfaces/11-decode-toiface/timestamp.go @@ -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) } diff --git a/interfaces/12-reflection/timestamp.go b/interfaces/12-reflection/timestamp.go index 28629d8..ab29528 100644 --- a/interfaces/12-reflection/timestamp.go +++ b/interfaces/12-reflection/timestamp.go @@ -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) } diff --git a/interfaces/13-reflection-2/timestamp.go b/interfaces/13-reflection-2/timestamp.go index 28629d8..ab29528 100644 --- a/interfaces/13-reflection-2/timestamp.go +++ b/interfaces/13-reflection-2/timestamp.go @@ -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) } diff --git a/interfaces/14-io-reader/timestamp.go b/interfaces/14-io-reader/timestamp.go index 28629d8..ab29528 100644 --- a/interfaces/14-io-reader/timestamp.go +++ b/interfaces/14-io-reader/timestamp.go @@ -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) } diff --git a/interfaces/15-io-reader-composition/timestamp.go b/interfaces/15-io-reader-composition/timestamp.go index 28629d8..ab29528 100644 --- a/interfaces/15-io-reader-composition/timestamp.go +++ b/interfaces/15-io-reader-composition/timestamp.go @@ -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) }