This commit is contained in:
wagslane
2023-05-01 15:25:27 -06:00
parent f8912668b8
commit 9be3074de6
868 changed files with 58698 additions and 2 deletions

View 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),
})
}

View 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),
})
}

View 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
====================================

View 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`!

View 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,
})
}

View 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,
})
}

View File

@@ -0,0 +1,6 @@
Jack 50000
====================================
Bob 7300
====================================
Jill 856304
====================================

View 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.

View File

@@ -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"
]
}

View 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*.

View File

@@ -0,0 +1,7 @@
{
"question": "Can a type fulfill multiple interfaces?",
"answers": [
"Yes, why not?",
"Never"
]
}

View 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*.

View File

@@ -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"
]
}

View 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
```

View File

@@ -0,0 +1,9 @@
{
"question": "In the example given, the ____ type implements the ____ interface",
"answers": [
"circle, shape",
"shape, circle",
"circle, area",
"shape, area"
]
}

View 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
```

View 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)
}

View File

@@ -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)
}

View File

@@ -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
====================================

View File

@@ -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.

View File

@@ -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"
]
}

View 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.

View File

@@ -0,0 +1,8 @@
{
"question": "Why would you name your interface's method's parameters?",
"answers": [
"Readability and clarity",
"Execution speed",
"Memory savings"
]
}

View 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.

View 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{})
}

View 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{})
}

View 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
====================================

View 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.

View 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{})
}

View 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{})
}

View 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
====================================

View 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.

View File

@@ -0,0 +1,8 @@
{
"question": "Interfaces should have as _ methods as possible",
"answers": [
"Few",
"Many",
"Complex"
]
}

View File

@@ -0,0 +1,59 @@
# Clean Interfaces
Writing clean interfaces is *hard*. Frankly, anytime youre dealing with abstractions in code, the simple can become complex very quickly if youre not careful. Lets 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 thats 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 interfaces behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesnt need to know if its 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 shouldnt be aware of any types that happen to satisfy the interface at design time.
For example, lets 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 dont have constructors or deconstructors that require that data is created or destroyed.
* Interfaces arent 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 wont 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/)

View File

@@ -0,0 +1,7 @@
{
"question": "It's okay for types to be aware of the interfaces they satisfy",
"answers": [
"True",
"False"
]
}

View File

@@ -0,0 +1,59 @@
# Clean Interfaces
Writing clean interfaces is *hard*. Frankly, anytime youre dealing with abstractions in code, the simple can become complex very quickly if youre not careful. Lets 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 thats 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 interfaces behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesnt need to know if its 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 shouldnt be aware of any types that happen to satisfy the interface at design time.
For example, lets 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 dont have constructors or deconstructors that require that data is created or destroyed.
* Interfaces arent 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 wont 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/)

View File

@@ -0,0 +1,7 @@
{
"question": "It's okay for interfaces to be aware of the types that satisfy them",
"answers": [
"False",
"True"
]
}

View File

@@ -0,0 +1,59 @@
# Clean Interfaces
Writing clean interfaces is *hard*. Frankly, anytime youre dealing with abstractions in code, the simple can become complex very quickly if youre not careful. Lets 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 thats 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 interfaces behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesnt need to know if its 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 shouldnt be aware of any types that happen to satisfy the interface at design time.
For example, lets 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 dont have constructors or deconstructors that require that data is created or destroyed.
* Interfaces arent 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 wont 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/)

View File

@@ -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"
]
}

View File

@@ -0,0 +1,59 @@
# Clean Interfaces
Writing clean interfaces is *hard*. Frankly, anytime youre dealing with abstractions in code, the simple can become complex very quickly if youre not careful. Lets 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 thats 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 interfaces behaviors can be considered by the HTTP package as a *File*. This is convenient because the HTTP package doesnt need to know if its 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 shouldnt be aware of any types that happen to satisfy the interface at design time.
For example, lets 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 dont have constructors or deconstructors that require that data is created or destroyed.
* Interfaces arent 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 wont 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/)