Go Learning Path - Module 6: Structs and Interfaces
go教程目录
Module 1: Hello World & Basic Concepts
Module 2: Variables, Data Types, and Constants
Module 2: Variables, Data Types, and Constants
Module 4: Control Structures (if/else, loops)
Module 5: Arrays, Slices, and Maps Arrays
Module 6: Structs and Interfaces
Module 7: Pointers and Memory Management
Module 8: Concurrency with Goroutines and Channels
Module 9: Error Handling and Defer/Panic/Recover
Module 10: Advanced Topics - Testing and Standard Library
Structs and interfaces are essential for organizing and structuring your Go code. Structs allow you to define custom data types, while interfaces enable polymorphism and abstraction.
Structs
A struct is a collection of fields grouped together under a single type.
Defining and Using Structs
package main
import "fmt"
// Define a Person struct
type Person struct {
Name string
Age int
Address string
}
func main() {
// Create a struct instance
p1 := Person{Name: "Alice", Age: 30, Address: "123 Main St"}
// Another way to create a struct
p2 := Person{"Bob", 25, "456 Oak Ave"}
// Access and modify fields
fmt.Printf("Person 1: %+v\n", p1) // %+v prints field names
fmt.Printf("Person 2: %+v\n", p2)
// Access individual fields
fmt.Printf("Name: %s, Age: %d\n", p1.Name, p1.Age)
// Modify fields
p1.Age = 31
fmt.Printf("Updated age: %d\n", p1.Age)
}
Struct Literals
func main() {
// Create struct with some fields specified
p1 := Person{Name: "Carol", Age: 28} // Address will be zero value ""
// Create using new (returns pointer to zero value)
p2 := new(Person) // Equivalent to: p2 := &Person{}
p2.Name = "Dave"
p2.Age = 35
fmt.Printf("p1: %+v\n", p1)
fmt.Printf("p2: %+v\n", *p2) // Dereference to print the value
}
Struct Tags
Struct tags provide metadata about fields, often used by libraries for JSON/XML marshaling:
type Employee struct {
ID int `json:"employee_id"`
Name string `json:"full_name"`
Email string `json:"email_address" validate:"email"`
Age int `json:"age,omitempty"` // omitempty means don't include if zero
}
func main() {
emp := Employee{
ID: 123,
Name: "John Doe",
Email: "john@example.com",
Age: 30,
}
fmt.Printf("Employee: %+v\n", emp)
// The tags won't show here, but they're used by reflection
}
Nested Structs
type Address struct {
Street, City, State string
ZipCode int
}
type Person struct {
Name string
Age int
Address Address // Embedded struct
}
func main() {
person := Person{
Name: "Jane Smith",
Age: 27,
Address: Address{
Street: "789 Pine St",
City: "Seattle",
State: "WA",
ZipCode: 98101,
},
}
// Access nested fields
fmt.Printf("Name: %s\n", person.Name)
fmt.Printf("City: %s\n", person.Address.City)
// You can also use anonymous structs
type Response struct {
Status string
Payload struct {
Message string
Code int
}
}
resp := Response{
Status: "success",
Payload: struct {
Message string
Code int
}{
Message: "Operation completed",
Code: 200,
},
}
fmt.Printf("Response: %+v\n", resp)
}
Methods on Structs
Methods are functions with a special receiver argument that operates on a struct instance:
package main
import "fmt"
type Rectangle struct {
Width, Height float64
}
// Method with value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Method with pointer receiver (to modify the struct)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
// Method to return a new scaled rectangle (value receiver)
func (r Rectangle) Scaled(factor float64) Rectangle {
return Rectangle{Width: r.Width * factor, Height: r.Height * factor}
}
func main() {
rect := Rectangle{Width: 10, Height: 5}
fmt.Printf("Original area: %.2f\n", rect.Area())
// Scale the original rectangle
rect.Scale(2)
fmt.Printf("After scaling by 2: width=%.2f, height=%.2f\n", rect.Width, rect.Height)
fmt.Printf("New area: %.2f\n", rect.Area())
// Get a new scaled rectangle without modifying original
biggerRect := rect.Scaled(1.5)
fmt.Printf("New rectangle area: %.2f\n", biggerRect.Area())
fmt.Printf("Original unchanged: width=%.2f, height=%.2f\n", rect.Width, rect.Height)
}
Interfaces
An interface is a contract that specifies a set of methods a type must implement. Go uses duck typing: "if it walks like a duck and quacks like a duck, it's a duck."
Basic Interface
package main
import "fmt"
// Define an interface
type Shape interface {
Area() float64
Perimeter() float64
}
// Implement the interface with a Rectangle
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Implement the same interface with a Circle
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * 3.14159 * c.Radius
}
func main() {
var s Shape // Interface variable
s = Rectangle{Width: 10, Height: 5}
fmt.Printf("Rectangle area: %.2f, perimeter: %.2f\n", s.Area(), s.Perimeter())
s = Circle{Radius: 5}
fmt.Printf("Circle area: %.2f, perimeter: %.2f\n", s.Area(), s.Perimeter())
// Polymorphism: use same interface for different types
shapes := []Shape{
Rectangle{Width: 3, Height: 4},
Circle{Radius: 2},
Rectangle{Width: 6, Height: 8},
Circle{Radius: 3.5},
}
for _, shape := range shapes {
fmt.Printf("Type: %T, Area: %.2f, Perimeter: %.2f\n",
shape, shape.Area(), shape.Perimeter())
}
}
Empty Interface
The empty interface interface{} (or any in newer Go versions) can hold any type:
package main
import "fmt"
func describe(i interface{}) {
fmt.Printf("Type: %T, Value: %v\n", i, i)
}
func main() {
// Using empty interface
var anything interface{}
anything = 42
describe(anything) // Type: int, Value: 42
anything = "hello"
describe(anything) // Type: string, Value: hello
anything = true
describe(anything) // Type: bool, Value: true
// Function that accepts any type
describe(3.14)
describe([]int{1, 2, 3})
describe([2]string{"hello", "world"})
}
Type Assertions
Extract the concrete value from an interface:
func main() {
var i interface{} = "hello"
// Type assertion to extract the value
s := i.(string)
fmt.Printf("String value: %s\n", s) // hello
// Safe type assertion (avoids panic)
if str, ok := i.(string); ok {
fmt.Printf("It's a string: %s\n", str)
}
// This would panic if i doesn't hold an int
if num, ok := i.(int); !ok {
fmt.Println("i is not an int")
}
// Type switches
describeType := func(i interface{}) {
switch v := i.(type) {
case string:
fmt.Printf("String: %s (length: %d)\n", v, len(v))
case int:
fmt.Printf("Integer: %d\n", v)
case bool:
fmt.Printf("Boolean: %t\n", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}
describeType("hello")
describeType(42)
describeType(true)
describeType(3.14)
}
Interface Composition
Interfaces can be composed from other interfaces:
package main
import "fmt"
// Basic interfaces
type Speaker interface {
Speak() string
}
type Mover interface {
Move() string
}
// Combined interface
type Animal interface {
Speaker
Mover
Name() string
}
// Dog implements Animal
type Dog struct {
name string
}
func (d Dog) Speak() string {
return "Woof!"
}
func (d Dog) Move() string {
return "Running on four legs"
}
func (d Dog) Name() string {
return d.name
}
// Cat also implements Animal
type Cat struct {
name string
}
func (c Cat) Speak() string {
return "Meow!"
}
func (c Cat) Move() string {
return "Walking gracefully"
}
func (c Cat) Name() string {
return c.name
}
func main() {
animals := []Animal{
Dog{name: "Buddy"},
Cat{name: "Whiskers"},
}
for _, animal := range animals {
fmt.Printf("%s: %s, %s, %s\n",
animal.Name(), animal.Speak(), animal.Move(), "Animal")
}
}
Built-in Interfaces
Go has several important built-in interfaces, such as error and Stringer:
package main
import (
"fmt"
)
// Implement the Stringer interface
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}
// Custom error implementation
type CustomError struct {
Message string
Code int
}
func (e CustomError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func main() {
person := Person{Name: "Alice", Age: 30}
fmt.Println(person) // Uses the String() method
// Using custom error
err := CustomError{Message: "Something went wrong", Code: 404}
if err != nil {
fmt.Println(err) // Uses the Error() method
}
}
Advanced Interface Patterns
The Writer/Reader Interface Pattern
package main
import (
"fmt"
"io"
"os"
"strings"
)
func main() {
// Using io.Writer interface
var w io.Writer
// os.Stdout implements io.Writer
w = os.Stdout
fmt.Fprint(w, "Written to stdout\n")
// strings.Builder implements io.Writer
var builder strings.Builder
w = &builder
fmt.Fprint(w, "Written to builder")
fmt.Println("From builder:", builder.String())
}
Functional Interface Pattern
package main
import "fmt"
// Interface for operations
type Operation interface {
Execute(x, y int) int
}
// Different implementations
type AddOp struct{}
func (AddOp) Execute(x, y int) int { return x + y }
type MultiplyOp struct{}
func (MultiplyOp) Execute(x, y int) int { return x * y }
type Calculator struct {
op Operation
}
func (c *Calculator) Calculate(x, y int) int {
return c.op.Execute(x, y)
}
func (c *Calculator) SetOperation(op Operation) {
c.op = op
}
func main() {
calc := &Calculator{op: AddOp{}}
fmt.Printf("5 + 3 = %d\n", calc.Calculate(5, 3))
calc.SetOperation(MultiplyOp{})
fmt.Printf("5 * 3 = %d\n", calc.Calculate(5, 3))
}
Exercises
-
Define a
Vehicleinterface with methodsStart(),Stop(), andGetSpeed(), then implement it for different vehicle types (Car, Bike, Boat). -
Create a
Drawableinterface with aDraw()method and implement it for shapes like Circle, Rectangle, and Triangle. -
Implement a
Loggerinterface with methods likeInfo(),Warning(), andError(), and create different logger implementations (ConsoleLogger, FileLogger). -
Write a function that takes an interface{} and determines its type using a type switch, printing different information depending on the type.
-
Create a
Sorterinterface with aSort()method and implement different sorting algorithms (BubbleSort, QuickSort, etc.) that satisfy the interface.