mirror of
https://github.com/bootdotdev/fcc-learn-golang-assets.git
synced 2025-12-15 01:31:17 +00:00
first
This commit is contained in:
58
course/5-interfaces/exercises/1-interfaces/code.go
Normal file
58
course/5-interfaces/exercises/1-interfaces/code.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sendMessage(msg message) {
|
||||
// ?
|
||||
}
|
||||
|
||||
type message interface {
|
||||
// ?
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type birthdayMessage struct {
|
||||
birthdayTime time.Time
|
||||
recipientName string
|
||||
}
|
||||
|
||||
func (bm birthdayMessage) getMessage() string {
|
||||
return fmt.Sprintf("Hi %s, it is your birthday on %s", bm.recipientName, bm.birthdayTime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
type sendingReport struct {
|
||||
reportName string
|
||||
numberOfSends int
|
||||
}
|
||||
|
||||
func (sr sendingReport) getMessage() string {
|
||||
return fmt.Sprintf(`Your "%s" report is ready. You've sent %v messages.`, sr.reportName, sr.numberOfSends)
|
||||
}
|
||||
|
||||
func test(m message) {
|
||||
sendMessage(m)
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(sendingReport{
|
||||
reportName: "First Report",
|
||||
numberOfSends: 10,
|
||||
})
|
||||
test(birthdayMessage{
|
||||
recipientName: "John Doe",
|
||||
birthdayTime: time.Date(1994, 03, 21, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
test(sendingReport{
|
||||
reportName: "First Report",
|
||||
numberOfSends: 10,
|
||||
})
|
||||
test(birthdayMessage{
|
||||
recipientName: "Bill Deer",
|
||||
birthdayTime: time.Date(1934, 05, 01, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
}
|
||||
58
course/5-interfaces/exercises/1-interfaces/complete.go
Normal file
58
course/5-interfaces/exercises/1-interfaces/complete.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func sendMessage(msg message) {
|
||||
fmt.Println(msg.getMessage())
|
||||
}
|
||||
|
||||
type message interface {
|
||||
getMessage() string
|
||||
}
|
||||
|
||||
// don't edit below this line
|
||||
|
||||
type birthdayMessage struct {
|
||||
birthdayTime time.Time
|
||||
recipientName string
|
||||
}
|
||||
|
||||
func (bm birthdayMessage) getMessage() string {
|
||||
return fmt.Sprintf("Hi %s, it is your birthday on %s", bm.recipientName, bm.birthdayTime.Format(time.RFC3339))
|
||||
}
|
||||
|
||||
type sendingReport struct {
|
||||
reportName string
|
||||
numberOfSends int
|
||||
}
|
||||
|
||||
func (sr sendingReport) getMessage() string {
|
||||
return fmt.Sprintf(`Your "%s" report is ready. You've sent %v messages.`, sr.reportName, sr.numberOfSends)
|
||||
}
|
||||
|
||||
func test(m message) {
|
||||
sendMessage(m)
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(sendingReport{
|
||||
reportName: "First Report",
|
||||
numberOfSends: 10,
|
||||
})
|
||||
test(birthdayMessage{
|
||||
recipientName: "John Doe",
|
||||
birthdayTime: time.Date(1994, 03, 21, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
test(sendingReport{
|
||||
reportName: "First Report",
|
||||
numberOfSends: 10,
|
||||
})
|
||||
test(birthdayMessage{
|
||||
recipientName: "Bill Deer",
|
||||
birthdayTime: time.Date(1934, 05, 01, 0, 0, 0, 0, time.UTC),
|
||||
})
|
||||
}
|
||||
8
course/5-interfaces/exercises/1-interfaces/expected.txt
Normal file
8
course/5-interfaces/exercises/1-interfaces/expected.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Your "First Report" report is ready. You've sent 10 messages.
|
||||
====================================
|
||||
Hi John Doe, it is your birthday on 1994-03-21T00:00:00Z
|
||||
====================================
|
||||
Your "First Report" report is ready. You've sent 10 messages.
|
||||
====================================
|
||||
Hi Bill Deer, it is your birthday on 1934-05-01T00:00:00Z
|
||||
====================================
|
||||
42
course/5-interfaces/exercises/1-interfaces/readme.md
Normal file
42
course/5-interfaces/exercises/1-interfaces/readme.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Interfaces in Go
|
||||
|
||||
Interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it.
|
||||
|
||||
In the following example, a "shape" must be able to return its area and perimeter. Both `rect` and `circle` fulfill the interface.
|
||||
|
||||
```go
|
||||
type shape interface {
|
||||
area() float64
|
||||
perimeter() float64
|
||||
}
|
||||
|
||||
type rect struct {
|
||||
width, height float64
|
||||
}
|
||||
func (r rect) area() float64 {
|
||||
return r.width * r.height
|
||||
}
|
||||
func (r rect) perimeter() float64 {
|
||||
return 2*r.width + 2*r.height
|
||||
}
|
||||
|
||||
type circle struct {
|
||||
radius float64
|
||||
}
|
||||
func (c circle) area() float64 {
|
||||
return math.Pi * c.radius * c.radius
|
||||
}
|
||||
func (c circle) perimeter() float64 {
|
||||
return 2 * math.Pi * c.radius
|
||||
}
|
||||
```
|
||||
|
||||
When a type implements an interface, it can then be used as the interface type.
|
||||
|
||||
## Assignment
|
||||
|
||||
The `birthdayMessage` and `sendingReport` structs have already implemented the `getMessage` methods. The `getMessage` method simply returns a string, and any type that implements the method can be considered a `message`.
|
||||
|
||||
First, add the `getMessage()` method as a requirement on the method interface.
|
||||
|
||||
Second, complete the `sendMessage` function. It should print a message's `message`, which it obtains through the interface method. Notice that your code doesn't need to worry *at all* about whether a specific message is a `birthdayMessage` or a `sendingReport`!
|
||||
59
course/5-interfaces/exercises/2-implements/code.go
Normal file
59
course/5-interfaces/exercises/2-implements/code.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type employee interface {
|
||||
getName() string
|
||||
getSalary() int
|
||||
}
|
||||
|
||||
type contractor struct {
|
||||
name string
|
||||
hourlyPay int
|
||||
hoursPerYear int
|
||||
}
|
||||
|
||||
func (c contractor) getName() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
// ?
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
type fullTime struct {
|
||||
name string
|
||||
salary int
|
||||
}
|
||||
|
||||
func (ft fullTime) getSalary() int {
|
||||
return ft.salary
|
||||
}
|
||||
|
||||
func (ft fullTime) getName() string {
|
||||
return ft.name
|
||||
}
|
||||
|
||||
func test(e employee) {
|
||||
fmt.Println(e.getName(), e.getSalary())
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(fullTime{
|
||||
name: "Jack",
|
||||
salary: 50000,
|
||||
})
|
||||
test(contractor{
|
||||
name: "Bob",
|
||||
hourlyPay: 100,
|
||||
hoursPerYear: 73,
|
||||
})
|
||||
test(contractor{
|
||||
name: "Jill",
|
||||
hourlyPay: 872,
|
||||
hoursPerYear: 982,
|
||||
})
|
||||
}
|
||||
61
course/5-interfaces/exercises/2-implements/complete.go
Normal file
61
course/5-interfaces/exercises/2-implements/complete.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type employee interface {
|
||||
getName() string
|
||||
getSalary() int
|
||||
}
|
||||
|
||||
type contractor struct {
|
||||
name string
|
||||
hourlyPay int
|
||||
hoursPerYear int
|
||||
}
|
||||
|
||||
func (c contractor) getName() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c contractor) getSalary() int {
|
||||
return c.hourlyPay * c.hoursPerYear
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
type fullTime struct {
|
||||
name string
|
||||
salary int
|
||||
}
|
||||
|
||||
func (ft fullTime) getSalary() int {
|
||||
return ft.salary
|
||||
}
|
||||
|
||||
func (ft fullTime) getName() string {
|
||||
return ft.name
|
||||
}
|
||||
|
||||
func test(e employee) {
|
||||
fmt.Println(e.getName(), e.getSalary())
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(fullTime{
|
||||
name: "Jack",
|
||||
salary: 50000,
|
||||
})
|
||||
test(contractor{
|
||||
name: "Bob",
|
||||
hourlyPay: 100,
|
||||
hoursPerYear: 73,
|
||||
})
|
||||
test(contractor{
|
||||
name: "Jill",
|
||||
hourlyPay: 872,
|
||||
hoursPerYear: 982,
|
||||
})
|
||||
}
|
||||
6
course/5-interfaces/exercises/2-implements/expected.txt
Normal file
6
course/5-interfaces/exercises/2-implements/expected.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Jack 50000
|
||||
====================================
|
||||
Bob 7300
|
||||
====================================
|
||||
Jill 856304
|
||||
====================================
|
||||
13
course/5-interfaces/exercises/2-implements/readme.md
Normal file
13
course/5-interfaces/exercises/2-implements/readme.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Interface Implementation
|
||||
|
||||
Interfaces are implemented *implicitly*.
|
||||
|
||||
A type never declares that it implements a given interface. If an interface exists and a type has the proper methods defined, then the type automatically fulfills that interface.
|
||||
|
||||
## Assignment
|
||||
|
||||
At Textio we have full-time employees and contract employees. We have been tasked with making a more general `employee` interface so that dealing with different employee types is simpler.
|
||||
|
||||
Add the missing `getSalary` method to the `contractor` type so that it fulfills the `employee` interface.
|
||||
|
||||
A contractor's salary is their hourly pay multiplied by how many hours they work per year.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "How is an interface fulfilled?",
|
||||
"answers": [
|
||||
"A type has all the required interface's methods defined on it",
|
||||
"A struct embeds the interface in its definition"
|
||||
]
|
||||
}
|
||||
5
course/5-interfaces/exercises/3-implicit/readme.md
Normal file
5
course/5-interfaces/exercises/3-implicit/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Interfaces are implemented implicitly
|
||||
|
||||
A type implements an interface by implementing its methods. Unlike in many other languages, there is no explicit declaration of intent, there is no "implements" keyword.
|
||||
|
||||
Implicit interfaces *decouple* the definition of an interface from its implementation. You may add methods to a type and in the process be unknowingly implementing various interfaces, and *that's okay*.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Can a type fulfill multiple interfaces?",
|
||||
"answers": [
|
||||
"Yes, why not?",
|
||||
"Never"
|
||||
]
|
||||
}
|
||||
5
course/5-interfaces/exercises/3a-implicit/readme.md
Normal file
5
course/5-interfaces/exercises/3a-implicit/readme.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Interfaces are implemented implicitly
|
||||
|
||||
A type implements an interface by implementing its methods. Unlike in many other languages, there is no explicit declaration of intent, there is no "implements" keyword.
|
||||
|
||||
Implicit interfaces *decouple* the definition of an interface from its implementation. You may add methods to a type and in the process be unknowingly implementing various interfaces, and *that's okay*.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"question": "Go uses the ____ keyword to show that a type implements an interface",
|
||||
"answers": [
|
||||
"there is no keyword in Go",
|
||||
"implements",
|
||||
"fulfills",
|
||||
"inherits"
|
||||
]
|
||||
}
|
||||
27
course/5-interfaces/exercises/4-quiz/readme.md
Normal file
27
course/5-interfaces/exercises/4-quiz/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Interfaces Quiz
|
||||
|
||||
Remember, interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it.
|
||||
|
||||
```go
|
||||
type shape interface {
|
||||
area() float64
|
||||
}
|
||||
```
|
||||
|
||||
If a type in your code implements an `area` method, with the same signature (e.g. accepts nothing and returns a `float64`), then that object is said to *implement* the `shape` interface.
|
||||
|
||||
```go
|
||||
type circle struct{
|
||||
radius int
|
||||
}
|
||||
|
||||
func (c *circle) area() float64 {
|
||||
return 3.14 * c.radius * c.radius
|
||||
}
|
||||
```
|
||||
|
||||
This is *different from most other languages*, where you have to *explicitly* assign an interface type to an object, like with Java:
|
||||
|
||||
```java
|
||||
class Circle implements Shape
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"question": "In the example given, the ____ type implements the ____ interface",
|
||||
"answers": [
|
||||
"circle, shape",
|
||||
"shape, circle",
|
||||
"circle, area",
|
||||
"shape, area"
|
||||
]
|
||||
}
|
||||
27
course/5-interfaces/exercises/4a-quiz/readme.md
Normal file
27
course/5-interfaces/exercises/4a-quiz/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Interfaces Quiz
|
||||
|
||||
Remember, interfaces are collections of method signatures. A type "implements" an interface if it has all of the methods of the given interface defined on it.
|
||||
|
||||
```go
|
||||
type shape interface {
|
||||
area() float64
|
||||
}
|
||||
```
|
||||
|
||||
If a type in your code implements an `area` method, with the same signature (e.g. accepts nothing and returns a `float64`), then that object is said to *implement* the `shape` interface.
|
||||
|
||||
```go
|
||||
type circle struct{
|
||||
radius int
|
||||
}
|
||||
|
||||
func (c *circle) area() float64 {
|
||||
return 3.14 * c.radius * c.radius
|
||||
}
|
||||
```
|
||||
|
||||
This is *different from most other languages*, where you have to *explicitly* assign an interface type to an object, like with Java:
|
||||
|
||||
```java
|
||||
class Circle implements Shape
|
||||
```
|
||||
61
course/5-interfaces/exercises/5-multiple_interfaces/code.go
Normal file
61
course/5-interfaces/exercises/5-multiple_interfaces/code.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (e email) cost() float64 {
|
||||
// ?
|
||||
}
|
||||
|
||||
func (e email) print() {
|
||||
// ?
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type printer interface {
|
||||
print()
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
}
|
||||
|
||||
func print(p printer) {
|
||||
p.print()
|
||||
}
|
||||
|
||||
func test(e expense, p printer) {
|
||||
fmt.Printf("Printing with cost: $%.2f ...\n", e.cost())
|
||||
p.print()
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: false,
|
||||
body: "I want my money back",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: true,
|
||||
body: "Are you free for a chat?",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
}
|
||||
test(e, e)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (e email) cost() float64 {
|
||||
if !e.isSubscribed {
|
||||
return float64(len(e.body)) * .05
|
||||
}
|
||||
return float64(len(e.body)) * .01
|
||||
}
|
||||
|
||||
func (e email) print() {
|
||||
fmt.Println(e.body)
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type printer interface {
|
||||
print()
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
}
|
||||
|
||||
func print(p printer) {
|
||||
p.print()
|
||||
}
|
||||
|
||||
func test(e expense, p printer) {
|
||||
fmt.Printf("Printing with cost: $%.2f ...\n", e.cost())
|
||||
p.print()
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: false,
|
||||
body: "I want my money back",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: true,
|
||||
body: "Are you free for a chat?",
|
||||
}
|
||||
test(e, e)
|
||||
e = email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
}
|
||||
test(e, e)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
Printing with cost: $0.11 ...
|
||||
hello there
|
||||
====================================
|
||||
Printing with cost: $1.00 ...
|
||||
I want my money back
|
||||
====================================
|
||||
Printing with cost: $0.24 ...
|
||||
Are you free for a chat?
|
||||
====================================
|
||||
Printing with cost: $1.85 ...
|
||||
This meeting could have been an email
|
||||
====================================
|
||||
@@ -0,0 +1,15 @@
|
||||
# Multiple Interfaces
|
||||
|
||||
A type can implement any number of interfaces in Go. For example, the empty interface, `interface{}`, is *always* implemented by every type because it has no requirements.
|
||||
|
||||
## Assignment
|
||||
|
||||
Add the required methods so that the `email` type implements both the `expense` and `printer` interfaces.
|
||||
|
||||
### cost()
|
||||
|
||||
If the email is *not* "subscribed", then the cost is `0.05` for each character in the body. If it *is*, then the cost is `0.01` per character.
|
||||
|
||||
### print()
|
||||
|
||||
The `print` method should print to standard out the email's body text.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Are you required to name the arguments of an interface in order for your code to compile properly?",
|
||||
"answers": [
|
||||
"No",
|
||||
"Yes"
|
||||
]
|
||||
}
|
||||
23
course/5-interfaces/exercises/6-naming_args/readme.md
Normal file
23
course/5-interfaces/exercises/6-naming_args/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Name Your Interface Arguments
|
||||
|
||||
Consider the following interface:
|
||||
|
||||
```go
|
||||
type Copier interface {
|
||||
Copy(string, string) int
|
||||
}
|
||||
```
|
||||
|
||||
Based on the code alone, can you deduce what *kinds* of strings you should pass into the `Copy` function?
|
||||
|
||||
We know the function signature expects 2 string types, but what are they? Filenames? URLs? Raw string data? For that matter, what the heck is that `int` that's being returned?
|
||||
|
||||
Let's add some named arguments and return data to make it more clear.
|
||||
|
||||
```go
|
||||
type Copier interface {
|
||||
Copy(sourceFile string, destinationFile string) (bytesCopied int)
|
||||
}
|
||||
```
|
||||
|
||||
Much better. We can see what the expectations are now. The first argument is the `sourceFile`, the second argument is the `destinationFile`, and `bytesCopied`, an integer, is returned.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Why would you name your interface's method's parameters?",
|
||||
"answers": [
|
||||
"Readability and clarity",
|
||||
"Execution speed",
|
||||
"Memory savings"
|
||||
]
|
||||
}
|
||||
23
course/5-interfaces/exercises/6a-naming_args/readme.md
Normal file
23
course/5-interfaces/exercises/6a-naming_args/readme.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Name Your Interface Arguments
|
||||
|
||||
Consider the following interface:
|
||||
|
||||
```go
|
||||
type Copier interface {
|
||||
Copy(string, string) int
|
||||
}
|
||||
```
|
||||
|
||||
Based on the code alone, can you deduce what *kinds* of strings you should pass into the `Copy` function?
|
||||
|
||||
We know the function signature expects 2 string types, but what are they? Filenames? URLs? Raw string data? For that matter, what the heck is that `int` that's being returned?
|
||||
|
||||
Let's add some named arguments and return data to make it more clear.
|
||||
|
||||
```go
|
||||
type Copier interface {
|
||||
Copy(sourceFile string, destinationFile string) (bytesCopied int)
|
||||
}
|
||||
```
|
||||
|
||||
Much better. We can see what the expectations are now. The first argument is the `sourceFile`, the second argument is the `destinationFile`, and `bytesCopied`, an integer, is returned.
|
||||
95
course/5-interfaces/exercises/7-type_assertion/code.go
Normal file
95
course/5-interfaces/exercises/7-type_assertion/code.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getExpenseReport(e expense) (string, float64) {
|
||||
// ?
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
func (e email) cost() float64 {
|
||||
if !e.isSubscribed {
|
||||
return float64(len(e.body)) * .05
|
||||
}
|
||||
return float64(len(e.body)) * .01
|
||||
}
|
||||
|
||||
func (s sms) cost() float64 {
|
||||
if !s.isSubscribed {
|
||||
return float64(len(s.body)) * .1
|
||||
}
|
||||
return float64(len(s.body)) * .03
|
||||
}
|
||||
|
||||
func (i invalid) cost() float64 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toAddress string
|
||||
}
|
||||
|
||||
type sms struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toPhoneNumber string
|
||||
}
|
||||
|
||||
type invalid struct{}
|
||||
|
||||
func estimateYearlyCost(e expense, averageMessagesPerYear int) float64 {
|
||||
return e.cost() * float64(averageMessagesPerYear)
|
||||
}
|
||||
|
||||
func test(e expense) {
|
||||
address, cost := getExpenseReport(e)
|
||||
switch e.(type) {
|
||||
case email:
|
||||
fmt.Printf("Report: The email going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
case sms:
|
||||
fmt.Printf("Report: The sms going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
default:
|
||||
fmt.Println("Report: Invalid expense")
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
toAddress: "john@does.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "jane@doe.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "elon@doe.com",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toPhoneNumber: "+155555509832",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toPhoneNumber: "+155555504444",
|
||||
})
|
||||
test(invalid{})
|
||||
}
|
||||
105
course/5-interfaces/exercises/7-type_assertion/complete.go
Normal file
105
course/5-interfaces/exercises/7-type_assertion/complete.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getExpenseReport(e expense) (string, float64) {
|
||||
em, ok := e.(email)
|
||||
if ok {
|
||||
return em.toAddress, em.cost()
|
||||
}
|
||||
|
||||
sm, ok := e.(sms)
|
||||
if ok {
|
||||
return sm.toPhoneNumber, sm.cost()
|
||||
}
|
||||
|
||||
return "", 0.0
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
func (e email) cost() float64 {
|
||||
if !e.isSubscribed {
|
||||
return float64(len(e.body)) * .05
|
||||
}
|
||||
return float64(len(e.body)) * .01
|
||||
}
|
||||
|
||||
func (s sms) cost() float64 {
|
||||
if !s.isSubscribed {
|
||||
return float64(len(s.body)) * .1
|
||||
}
|
||||
return float64(len(s.body)) * .03
|
||||
}
|
||||
|
||||
func (i invalid) cost() float64 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toAddress string
|
||||
}
|
||||
|
||||
type sms struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toPhoneNumber string
|
||||
}
|
||||
|
||||
type invalid struct{}
|
||||
|
||||
func estimateYearlyCost(e expense, averageMessagesPerYear int) float64 {
|
||||
return e.cost() * float64(averageMessagesPerYear)
|
||||
}
|
||||
|
||||
func test(e expense) {
|
||||
address, cost := getExpenseReport(e)
|
||||
switch e.(type) {
|
||||
case email:
|
||||
fmt.Printf("Report: The email going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
case sms:
|
||||
fmt.Printf("Report: The sms going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
default:
|
||||
fmt.Println("Report: Invalid expense")
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
toAddress: "john@does.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "jane@doe.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "elon@doe.com",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toPhoneNumber: "+155555509832",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toPhoneNumber: "+155555504444",
|
||||
})
|
||||
test(invalid{})
|
||||
}
|
||||
12
course/5-interfaces/exercises/7-type_assertion/expected.txt
Normal file
12
course/5-interfaces/exercises/7-type_assertion/expected.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Report: The email going to john@does.com will cost: 0.11
|
||||
====================================
|
||||
Report: The email going to jane@doe.com will cost: 1.85
|
||||
====================================
|
||||
Report: The email going to elon@doe.com will cost: 1.85
|
||||
====================================
|
||||
Report: The sms going to +155555509832 will cost: 3.70
|
||||
====================================
|
||||
Report: The sms going to +155555504444 will cost: 3.70
|
||||
====================================
|
||||
Report: Invalid expense
|
||||
====================================
|
||||
27
course/5-interfaces/exercises/7-type_assertion/readme.md
Normal file
27
course/5-interfaces/exercises/7-type_assertion/readme.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Type assertions in Go
|
||||
|
||||
When working with interfaces in Go, every once-in-awhile you'll need access to the underlying type of an interface value. You can cast an interface to its underlying type using a *type assertion*.
|
||||
|
||||
```go
|
||||
type shape interface {
|
||||
area() float64
|
||||
}
|
||||
|
||||
type circle struct {
|
||||
radius float64
|
||||
}
|
||||
|
||||
// "c" is a new circle cast from "s"
|
||||
// which is an instance of a shape.
|
||||
// "ok" is a bool that is true if s was a circle
|
||||
// or false if s isn't a circle
|
||||
c, ok := s.(circle)
|
||||
```
|
||||
|
||||
## Assignment
|
||||
|
||||
Implement the `getExpenseReport` function.
|
||||
|
||||
* If the `expense` is an `email` then it should return the email's `toAddress` and the `cost` of the email.
|
||||
* If the `expense` is an `sms` then it should return the sms's `toPhoneNumber` and its `cost`.
|
||||
* If the expense has any other underlying type, just return an empty string and `0.0` for the cost.
|
||||
95
course/5-interfaces/exercises/8-type_switch/code.go
Normal file
95
course/5-interfaces/exercises/8-type_switch/code.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getExpenseReport(e expense) (string, float64) {
|
||||
// ?
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
func (e email) cost() float64 {
|
||||
if !e.isSubscribed {
|
||||
return float64(len(e.body)) * .05
|
||||
}
|
||||
return float64(len(e.body)) * .01
|
||||
}
|
||||
|
||||
func (s sms) cost() float64 {
|
||||
if !s.isSubscribed {
|
||||
return float64(len(s.body)) * .1
|
||||
}
|
||||
return float64(len(s.body)) * .03
|
||||
}
|
||||
|
||||
func (i invalid) cost() float64 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toAddress string
|
||||
}
|
||||
|
||||
type sms struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toPhoneNumber string
|
||||
}
|
||||
|
||||
type invalid struct{}
|
||||
|
||||
func estimateYearlyCost(e expense, averageMessagesPerYear int) float64 {
|
||||
return e.cost() * float64(averageMessagesPerYear)
|
||||
}
|
||||
|
||||
func test(e expense) {
|
||||
address, cost := getExpenseReport(e)
|
||||
switch e.(type) {
|
||||
case email:
|
||||
fmt.Printf("Report: The email going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
case sms:
|
||||
fmt.Printf("Report: The sms going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
default:
|
||||
fmt.Println("Report: Invalid expense")
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
toAddress: "john@does.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "jane@doe.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "Wanna catch up later?",
|
||||
toAddress: "elon@doe.com",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "I'm a Nigerian prince, please send me your bank info so I can deposit $1000 dollars",
|
||||
toPhoneNumber: "+155555509832",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "I don't need this",
|
||||
toPhoneNumber: "+155555504444",
|
||||
})
|
||||
test(invalid{})
|
||||
}
|
||||
102
course/5-interfaces/exercises/8-type_switch/complete.go
Normal file
102
course/5-interfaces/exercises/8-type_switch/complete.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getExpenseReport(e expense) (string, float64) {
|
||||
switch v := e.(type) {
|
||||
case email:
|
||||
return v.toAddress, v.cost()
|
||||
case sms:
|
||||
return v.toPhoneNumber, v.cost()
|
||||
default:
|
||||
return "", 0.0
|
||||
}
|
||||
}
|
||||
|
||||
// don't touch below this line
|
||||
|
||||
func (e email) cost() float64 {
|
||||
if !e.isSubscribed {
|
||||
return float64(len(e.body)) * .05
|
||||
}
|
||||
return float64(len(e.body)) * .01
|
||||
}
|
||||
|
||||
func (s sms) cost() float64 {
|
||||
if !s.isSubscribed {
|
||||
return float64(len(s.body)) * .1
|
||||
}
|
||||
return float64(len(s.body)) * .03
|
||||
}
|
||||
|
||||
func (i invalid) cost() float64 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
type expense interface {
|
||||
cost() float64
|
||||
}
|
||||
|
||||
type email struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toAddress string
|
||||
}
|
||||
|
||||
type sms struct {
|
||||
isSubscribed bool
|
||||
body string
|
||||
toPhoneNumber string
|
||||
}
|
||||
|
||||
type invalid struct{}
|
||||
|
||||
func estimateYearlyCost(e expense, averageMessagesPerYear int) float64 {
|
||||
return e.cost() * float64(averageMessagesPerYear)
|
||||
}
|
||||
|
||||
func test(e expense) {
|
||||
address, cost := getExpenseReport(e)
|
||||
switch e.(type) {
|
||||
case email:
|
||||
fmt.Printf("Report: The email going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
case sms:
|
||||
fmt.Printf("Report: The sms going to %s will cost: %.2f\n", address, cost)
|
||||
fmt.Println("====================================")
|
||||
default:
|
||||
fmt.Println("Report: Invalid expense")
|
||||
fmt.Println("====================================")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
test(email{
|
||||
isSubscribed: true,
|
||||
body: "hello there",
|
||||
toAddress: "john@does.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "This meeting could have been an email",
|
||||
toAddress: "jane@doe.com",
|
||||
})
|
||||
test(email{
|
||||
isSubscribed: false,
|
||||
body: "Wanna catch up later?",
|
||||
toAddress: "elon@doe.com",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "I'm a Nigerian prince, please send me your bank info so I can deposit $1000 dollars",
|
||||
toPhoneNumber: "+155555509832",
|
||||
})
|
||||
test(sms{
|
||||
isSubscribed: false,
|
||||
body: "I don't need this",
|
||||
toPhoneNumber: "+155555504444",
|
||||
})
|
||||
test(invalid{})
|
||||
}
|
||||
12
course/5-interfaces/exercises/8-type_switch/expected.txt
Normal file
12
course/5-interfaces/exercises/8-type_switch/expected.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Report: The email going to john@does.com will cost: 0.11
|
||||
====================================
|
||||
Report: The email going to jane@doe.com will cost: 1.85
|
||||
====================================
|
||||
Report: The email going to elon@doe.com will cost: 1.05
|
||||
====================================
|
||||
Report: The sms going to +155555509832 will cost: 8.30
|
||||
====================================
|
||||
Report: The sms going to +155555504444 will cost: 1.70
|
||||
====================================
|
||||
Report: Invalid expense
|
||||
====================================
|
||||
41
course/5-interfaces/exercises/8-type_switch/readme.md
Normal file
41
course/5-interfaces/exercises/8-type_switch/readme.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Type Switches
|
||||
|
||||
A *type switch* makes it easy to do several type assertions in a series.
|
||||
|
||||
A type switch is similar to a regular switch statement, but the cases specify *types* instead of *values*.
|
||||
|
||||
```go
|
||||
func printNumericValue(num interface{}) {
|
||||
switch v := num.(type) {
|
||||
case int:
|
||||
fmt.Printf("%T\n", v)
|
||||
case string:
|
||||
fmt.Printf("%T\n", v)
|
||||
default:
|
||||
fmt.Printf("%T\n", v)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
printNumericValue(1)
|
||||
// prints "int"
|
||||
|
||||
printNumericValue("1")
|
||||
// prints "string"
|
||||
|
||||
printNumericValue(struct{}{})
|
||||
// prints "struct {}"
|
||||
}
|
||||
```
|
||||
|
||||
`fmt.Printf("%T\n", v)` prints the *type* of a variable.
|
||||
|
||||
## Assignment
|
||||
|
||||
After submitting our last snippet of code for review, a more experienced gopher told us to use a type switch instead of successive assertions. Let's make that improvement!
|
||||
|
||||
Implement the `getExpenseReport` function *using a type switch*.
|
||||
|
||||
* If the `expense` is an `email` then it should return the email's `toAddress` and the `cost` of the email.
|
||||
* If the `expense` is an `sms` then it should return the sms's `toPhoneNumber` and its `cost`.
|
||||
* If the expense has any other underlying type, just return an empty string and `0.0` for the cost.
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"question": "Interfaces should have as _ methods as possible",
|
||||
"answers": [
|
||||
"Few",
|
||||
"Many",
|
||||
"Complex"
|
||||
]
|
||||
}
|
||||
59
course/5-interfaces/exercises/9-clean_interfaces/readme.md
Normal file
59
course/5-interfaces/exercises/9-clean_interfaces/readme.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Clean Interfaces
|
||||
|
||||
Writing clean interfaces is *hard*. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean.
|
||||
|
||||
## 1. Keep Interfaces Small
|
||||
|
||||
If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept.
|
||||
|
||||
Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior:
|
||||
|
||||
```go
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
Readdir(count int) ([]os.FileInfo, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
```
|
||||
|
||||
Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple `[]byte`.
|
||||
|
||||
## 2. Interfaces Should Have No Knowledge of Satisfying Types
|
||||
|
||||
An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time.
|
||||
|
||||
For example, let’s assume we are building an interface to describe the components necessary to define a car.
|
||||
|
||||
```go
|
||||
type car interface {
|
||||
Color() string
|
||||
Speed() int
|
||||
IsFiretruck() bool
|
||||
}
|
||||
```
|
||||
|
||||
`Color()` and `Speed()` make perfect sense, they are methods confined to the scope of a car. IsFiretruck() is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. In order for this pattern to make any amount of sense, we would need a whole list of possible subtypes. `IsPickup()`, `IsSedan()`, `IsTank()`… where does it end??
|
||||
|
||||
Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as:
|
||||
|
||||
```go
|
||||
type firetruck interface {
|
||||
car
|
||||
HoseLength() int
|
||||
}
|
||||
```
|
||||
|
||||
Which inherits the required methods from `car` and adds one additional required method to make the `car` a `firetruck`.
|
||||
|
||||
## 3. Interfaces Are Not Classes
|
||||
|
||||
* Interfaces are not classes, they are slimmer.
|
||||
* Interfaces don’t have constructors or deconstructors that require that data is created or destroyed.
|
||||
* Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces.
|
||||
* Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the `fmt.Stringer` interface, they all need their own version of the `String()` function.
|
||||
|
||||
## Optional: Further reading
|
||||
|
||||
[Best Practices for Interfaces in Go](https://blog.boot.dev/golang/golang-interfaces/)
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "It's okay for types to be aware of the interfaces they satisfy",
|
||||
"answers": [
|
||||
"True",
|
||||
"False"
|
||||
]
|
||||
}
|
||||
59
course/5-interfaces/exercises/9a-clean_interfaces/readme.md
Normal file
59
course/5-interfaces/exercises/9a-clean_interfaces/readme.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Clean Interfaces
|
||||
|
||||
Writing clean interfaces is *hard*. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean.
|
||||
|
||||
## 1. Keep Interfaces Small
|
||||
|
||||
If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept.
|
||||
|
||||
Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior:
|
||||
|
||||
```go
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
Readdir(count int) ([]os.FileInfo, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
```
|
||||
|
||||
Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple `[]byte`.
|
||||
|
||||
## 2. Interfaces Should Have No Knowledge of Satisfying Types
|
||||
|
||||
An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time.
|
||||
|
||||
For example, let’s assume we are building an interface to describe the components necessary to define a car.
|
||||
|
||||
```go
|
||||
type car interface {
|
||||
Color() string
|
||||
Speed() int
|
||||
IsFiretruck() bool
|
||||
}
|
||||
```
|
||||
|
||||
`Color()` and `Speed()` make perfect sense, they are methods confined to the scope of a car. IsFiretruck() is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. For this pattern to make any amount of sense, we would need a whole list of possible subtypes. `IsPickup()`, `IsSedan()`, `IsTank()`… where does it end??
|
||||
|
||||
Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as:
|
||||
|
||||
```go
|
||||
type firetruck interface {
|
||||
car
|
||||
HoseLength() int
|
||||
}
|
||||
```
|
||||
|
||||
Which inherits the required methods from `car` and adds one additional required method to make the `car` a `firetruck`.
|
||||
|
||||
## 3. Interfaces Are Not Classes
|
||||
|
||||
* Interfaces are not classes, they are slimmer.
|
||||
* Interfaces don’t have constructors or deconstructors that require that data is created or destroyed.
|
||||
* Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces.
|
||||
* Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the `fmt.Stringer` interface, they all need their own version of the `String()` function.
|
||||
|
||||
## Optional: Further reading
|
||||
|
||||
[Best Practices for Interfaces in Go](https://blog.boot.dev/golang/golang-interfaces/)
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "It's okay for interfaces to be aware of the types that satisfy them",
|
||||
"answers": [
|
||||
"False",
|
||||
"True"
|
||||
]
|
||||
}
|
||||
59
course/5-interfaces/exercises/9b-clean_interfaces/readme.md
Normal file
59
course/5-interfaces/exercises/9b-clean_interfaces/readme.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Clean Interfaces
|
||||
|
||||
Writing clean interfaces is *hard*. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean.
|
||||
|
||||
## 1. Keep Interfaces Small
|
||||
|
||||
If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept.
|
||||
|
||||
Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior:
|
||||
|
||||
```go
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
Readdir(count int) ([]os.FileInfo, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
```
|
||||
|
||||
Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple `[]byte`.
|
||||
|
||||
## 2. Interfaces Should Have No Knowledge of Satisfying Types
|
||||
|
||||
An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time.
|
||||
|
||||
For example, let’s assume we are building an interface to describe the components necessary to define a car.
|
||||
|
||||
```go
|
||||
type car interface {
|
||||
Color() string
|
||||
Speed() int
|
||||
IsFiretruck() bool
|
||||
}
|
||||
```
|
||||
|
||||
`Color()` and `Speed()` make perfect sense, they are methods confined to the scope of a car. IsFiretruck() is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. For this pattern to make any amount of sense, we would need a whole list of possible subtypes. `IsPickup()`, `IsSedan()`, `IsTank()`… where does it end??
|
||||
|
||||
Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as:
|
||||
|
||||
```go
|
||||
type firetruck interface {
|
||||
car
|
||||
HoseLength() int
|
||||
}
|
||||
```
|
||||
|
||||
Which inherits the required methods from `car` and adds one additional required method to make the `car` a `firetruck`.
|
||||
|
||||
## 3. Interfaces Are Not Classes
|
||||
|
||||
* Interfaces are not classes, they are slimmer.
|
||||
* Interfaces don’t have constructors or deconstructors that require that data is created or destroyed.
|
||||
* Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces.
|
||||
* Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the `fmt.Stringer` interface, they all need their own version of the `String()` function.
|
||||
|
||||
## Optional: Further reading
|
||||
|
||||
[Best Practices for Interfaces in Go](https://blog.boot.dev/golang/golang-interfaces/)
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"question": "Interfaces allow you to define a method's behavior once and use it for many different types",
|
||||
"answers": [
|
||||
"False",
|
||||
"True"
|
||||
]
|
||||
}
|
||||
59
course/5-interfaces/exercises/9c-clean_interfaces/readme.md
Normal file
59
course/5-interfaces/exercises/9c-clean_interfaces/readme.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Clean Interfaces
|
||||
|
||||
Writing clean interfaces is *hard*. Frankly, anytime you’re dealing with abstractions in code, the simple can become complex very quickly if you’re not careful. Let’s go over some rules of thumb for keeping interfaces clean.
|
||||
|
||||
## 1. Keep Interfaces Small
|
||||
|
||||
If there is only one piece of advice that you take away from this article, make it this: keep interfaces small! Interfaces are meant to define the minimal behavior necessary to accurately represent an idea or concept.
|
||||
|
||||
Here is an example from the standard HTTP package of a larger interface that’s a good example of defining minimal behavior:
|
||||
|
||||
```go
|
||||
type File interface {
|
||||
io.Closer
|
||||
io.Reader
|
||||
io.Seeker
|
||||
Readdir(count int) ([]os.FileInfo, error)
|
||||
Stat() (os.FileInfo, error)
|
||||
}
|
||||
```
|
||||
|
||||
Any type that satisfies the interface’s behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesn’t need to know if it’s dealing with a file on disk, a network buffer, or a simple `[]byte`.
|
||||
|
||||
## 2. Interfaces Should Have No Knowledge of Satisfying Types
|
||||
|
||||
An interface should define what is necessary for other types to classify as a member of that interface. They shouldn’t be aware of any types that happen to satisfy the interface at design time.
|
||||
|
||||
For example, let’s assume we are building an interface to describe the components necessary to define a car.
|
||||
|
||||
```go
|
||||
type car interface {
|
||||
Color() string
|
||||
Speed() int
|
||||
IsFiretruck() bool
|
||||
}
|
||||
```
|
||||
|
||||
`Color()` and `Speed()` make perfect sense, they are methods confined to the scope of a car. IsFiretruck() is an anti-pattern. We are forcing all cars to declare whether or not they are firetrucks. For this pattern to make any amount of sense, we would need a whole list of possible subtypes. `IsPickup()`, `IsSedan()`, `IsTank()`… where does it end??
|
||||
|
||||
Instead, the developer should have relied on the native functionality of type assertion to derive the underlying type when given an instance of the car interface. Or, if a sub-interface is needed, it can be defined as:
|
||||
|
||||
```go
|
||||
type firetruck interface {
|
||||
car
|
||||
HoseLength() int
|
||||
}
|
||||
```
|
||||
|
||||
Which inherits the required methods from `car` and adds one additional required method to make the `car` a `firetruck`.
|
||||
|
||||
## 3. Interfaces Are Not Classes
|
||||
|
||||
* Interfaces are not classes, they are slimmer.
|
||||
* Interfaces don’t have constructors or deconstructors that require that data is created or destroyed.
|
||||
* Interfaces aren’t hierarchical by nature, though there is syntactic sugar to create interfaces that happen to be supersets of other interfaces.
|
||||
* Interfaces define function signatures, but not underlying behavior. Making an interface often won’t DRY up your code in regards to struct methods. For example, if five types satisfy the `fmt.Stringer` interface, they all need their own version of the `String()` function.
|
||||
|
||||
## Optional: Further reading
|
||||
|
||||
[Best Practices for Interfaces in Go](https://blog.boot.dev/golang/golang-interfaces/)
|
||||
Reference in New Issue
Block a user