Skip to content

面向对象:结构体与接口

本章讲解Go面向对象:struct定义与初始化、方法与接收者、接口隐式实现、组合优于继承。重点:Go没有类继承,用struct+interface实现组合;理解值接收者vs指针接收者的区别。

前置知识:第4章 类型系统 学习目标:掌握Go OOP核心概念、理解接口值和nil行为


// Python class
class User:
def __init__(self, name string, age int):
self.name = name
self.age = age
// Go struct
type User struct {
Name string
Age int
}
// 创建实例
user := User{Name: "Alice", Age: 30}
// 指定字段创建
user := User{
Name: "Alice",
Age: 30,
}
// 字段顺序(不推荐)
user := User{"Alice", 30}
type User struct {
Name string
Age int
Email string
}
// 全部字段
u1 := User{Name: "Alice", Age: 30, Email: "alice@example.com"}
// 部分字段(其他用零值)
u2 := User{Name: "Bob"}
// u2.Age = 0, u2.Email = ""
// 字段名加冒号
u3 := User{Name: "Carol", Age: 25}
// 顺序初始化
u4 := User{"Dave", 35} // 不推荐,可读性差
user := User{Name: "Alice", Age: 30}
fmt.Println(user.Name) // Alice
fmt.Println(user.Age) // 30
user.Age = 31 // 修改字段
fmt.Println(user.Age) // 31
user := User{Name: "Alice", Age: 30}
// 取地址
p := &user
// 指针访问字段(语法糖,自动解引用)
fmt.Println(p.Name) // Alice,等同于(*p).Name
p.Age = 31 // 等同于(*p).Age = 31

本节目的:理解Go方法语法、值接收者vs指针接收者的区别

Go的方法与函数不同,方法有接收者:

type User struct {
Name string
Age int
}
// 定义方法(value receiver)
func (u User) Greet() {
fmt.Printf("Hello, I'm %s, %d years old\n", u.Name, u.Age)
}
// 调用
user := User{Name: "Alice", Age: 30}
user.Greet() // Hello, I'm Alice, 30 years old

Value Receiver(值接收者):

func (u User) SetName(name string) {
u.Name = name // 修改的是副本
}
user := User{Name: "Alice", Age: 30}
user.SetName("Bob")
fmt.Println(user.Name) // Alice(未修改)

Pointer Receiver(指针接收者):

func (u *User) SetName(name string) {
u.Name = name // 修改原对象
}
user := User{Name: "Alice", Age: 30}
user.SetName("Bob")
fmt.Println(user.Name) // Bob(已修改)

通常遵循这个原则:

// 1. 需要修改对象时
func (u *User) SetAge(age int) {
u.Age = age
}
// 2. 对象较大时(避免拷贝)
func (u *User) HeavyOperation() { /* ... */ }
// 3. 统一风格(通常全用指针)
// 注:Go会自动转换,user.SetAge(25)等效于(&user).SetAge(25)
class User:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def greet(self):
print(f"Hello, I'm {self.name}, {self.age} years old")
def set_age(self, age: int):
self.age = age
user = User("Alice", 30)
user.greet()
user.set_age(31)

主要差异:

  • Python:方法第一个参数是self
  • Go:方法有接收者,接收者可以是值或指针

值接收者的方法,无法修改对象:

type User struct {
Name string
Age int
}
func (u User) SetName(name string) {
u.Name = name
}

指针接收者的方法,可以修改对象:

func (u *User) SetAge(age int) {
u.Age = age
}
type Address struct {
City string
Country string
}
type User struct {
Name string
Address // 嵌入(匿名字段)
}
// 使用
user := User{
Name: "Alice",
Address: Address{
City: "Beijing",
Country: "China",
},
}
fmt.Println(user.City) // Beijing(直接访问嵌入字段)
fmt.Println(user.Address.City) // Beijing

Python继承:

class Address:
def __init__(self, city, country):
self.city = city
self.country = country
class User:
def __init__(self, name, address):
self.name = name
self.address = address

本节目的:理解Go接口隐式实现、掌握接口值和nil的处理

type Writer interface {
Write([]byte) (int, error)
}

任何实现了Write([]byte) (int, error)的类型,都实现了Writer接口。无需显式声明”实现Writer”。

type User struct {
Name string
}
// User实现了Writer接口
// 因为User有Write([]byte) (int, error)方法
func (u *User) Write(data []byte) (int, error) {
fmt.Printf("Writing: %s\n", string(data))
return len(data), nil
}
// 检查是否实现接口
var _ Writer = (*User)(nil) // 编译时检查
// Error接口
type error interface {
Error() string
}
// Stringer接口
type Stringer interface {
String() string
}
// Reader接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer接口
type Writer interface {
Write(p []byte) (n int, err error)
}
// Closer接口
type Closer interface {
Close() error
}
// 空接口表示"任何类型"
var i interface{} = 10
var s interface{} = "hello"
var user interface{} = User{Name: "Alice", Age: 30}
// 函数接受任意类型
func printValue(v interface{}) {
fmt.Println(v)
}
// 切片可以存储任意类型
var items []interface{} = []interface{}{1, "hello", User{}}

接口值由两部分组成:动态类型和动态值:

var writer Writer
// nil接口
writer = nil // 动态类型=nil,动态值=nil
// 带值的接口
writer = &User{Name: "Alice"}
// 动态类型=*User,动态值=&User{Name: "Alice"}
// 调用方法
writer.Write([]byte("test")) // 调用*User的Write方法
var writer Writer
// writer == nil 为 true
// writer.Write(nil) 会panic(调用nil上的方法)
var i interface{} = "hello"
// 方式一:ok模式
s, ok := i.(string)
if ok {
fmt.Println(s)
}
// 方式二:不安全
s := i.(string) // 如果不是string,panic
// 方式三:switch
switch v := i.(type) {
case string:
fmt.Println("string:", v)
case int:
fmt.Println("int:", v)
default:
fmt.Println("unknown")
}

本节目的:理解Go的组合模式、用嵌入替代继承

Go没有继承,只有组合:

type Base struct {
Name string
Age int
}
type Child struct {
Base // 组合Base
School string // 新增字段
}
// 访问组合字段
child := Child{
Base: Base{Name: "Alice", Age: 10},
School: "No.1 School",
}
fmt.Println(child.Name) // Alice
fmt.Println(child.Base.Name) // Alice
type Animal struct {
Name string
}
func (a *Animal) Speak() {
fmt.Println("...")
}
type Dog struct {
Animal // 组合Animal
Breed string
}
func (d *Dog) Speak() {
fmt.Println("Woof!")
}
d := Dog{
Animal: Animal{Name: "Buddy"},
Breed: "Golden Retriever",
}
d.Speak() // Woof!
d.Name // Buddy(继承自Animal)
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 组合多个接口
type ReadCloser interface {
Reader
Closer
}
// 验证
var _ ReadCloser = (*os.File)(nil)

通过组合和代理,可以实现类似继承的效果:

type Base struct {
name string
}
func (b *Base) SetName(name string) {
b.name = name
}
func (b *Base) GetName() string {
return b.name
}
type Child struct {
*Base // 组合(指针)
school string
}
// Child的方法可以调用Base的方法
func (c *Child) GetSchool() string {
return c.school
}
func main() {
c := &Child{
Base: &Base{},
}
c.SetName("Alice") // 代理到Base
fmt.Println(c.GetName())
}

  1. 构造函数替代new:Go没有构造函数,用NewUser(name string) *User工厂函数代替直接创建
  2. 指针接收者修改状态:需要修改对象或大结构体用*User,仅读取用User
  3. 接口小而精确:接口应该有一两个方法,如io.Reader、io.Writer,不要设计大接口
  4. 避免nil接口陷阱:空接口interface{}存放nil指针后不是nil,检测时用== nil不可靠
  5. 嵌入而非继承:用struct { *Base }嵌入组合,而非模拟Python的继承

PythonGo
class User:type User struct { }
self.nameu.Name
def greet(self):func (u *User) Greet()
class Child(Parent):type Child struct { Parent }
显式继承隐式实现接口
多继承接口组合
无pointer receiver修改对象

Go的设计原则:

  • 结构体是数据容器
  • 方法属于结构体
  • 接口是行为契约
  • 组合优于继承

  1. 定义User结构体,实现Greet方法和SetAge方法(使用指针接收者)
  2. 创建Writer接口,实现到User结构体
  3. 用空接口实现一个通用打印函数
  4. 用类型断言从interface提取string和int
  5. 创建Person结构体,组合Address结构体,实现完整的继承效果