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