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