面向对象与接口
约 1447 字大约 5 分钟
2025-04-23
Golang 并非传统面向对象语言,但通过结构体嵌入和接口实现了类似 OOP 的特性。
封装
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,其他包只有通过被授权的操作(方法)才能对字段进行操作。
Go 通过首字母大小写控制访问权限:
| 命名 | 访问级别 |
|---|---|
| 首字母大写 | public — 跨包可访问 |
| 首字母小写 | private — 仅包内可访问 |
工厂方法 + Set/Get
// pack/person.go
package pack
type person struct {
Name string
age int // 小写,包外不可直接访问
salary float64
}
// 工厂函数(类似构造函数)
func NewPerson(name string) *person {
return &person{Name: name}
}
// Set 方法
func (p *person) SetAge(age int) {
p.age = age
}
// Get 方法
func (p *person) GetAge() int {
return p.age
}// main.go
p1 := pack.NewPerson("Eli")
p1.SetAge(20)
fmt.Println(p1.Name, p1.GetAge()) // Eli 20接口
为什么需要接口
type Cat struct{}
type Dog struct{}
type Sheep struct{}
func (c Cat) Say() { fmt.Println("喵喵喵") }
func (d Dog) Say() { fmt.Println("汪汪汪") }
func (s Sheep) Say() { fmt.Println("咩咩咩") }
// 没有接口时,每来一种动物都要写一个新函数
// func CatHungry(c Cat) { c.Say() }
// func DogHungry(d Dog) { d.Say() }
// func SheepHungry(s Sheep) { s.Say() }当有越来越多的小动物,代码该如何扩展?这时就需要接口。
接口定义与使用
// 定义一个 Sayer 接口,必须实现 Say() 方法
type Sayer interface {
Say()
}
// 通用的饿了会叫的方法
func MakeHungry(s Sayer) {
s.Say()
}
func main() {
var c2 Cat
var d2 Dog
var s2 Sheep
// 只要是实现了 Say() 方法的类型,都能作为 Sayer 使用
MakeHungry(c2) // 喵喵喵
MakeHungry(d2) // 汪汪汪
MakeHungry(s2) // 咩咩咩
// 接口变量能存储所有实现了该接口的类型
var s Sayer
s = c2
s.Say() // 喵喵喵
}接口的核心思想
Go 的接口是隐式实现的(duck typing)—— 一个类型只要实现了接口的所有方法,它就实现了该接口,无需显式声明 implements。
工厂模式封装示例
// pack/account.go
package pack
type Account struct {
account string
password string
salary float64
}
func NewAccount(account, password string, salary float64) *Account {
return &Account{
account: account,
password: password,
salary: salary,
}
}
func (a *Account) SetAccountName(name string) { a.account = name }
func (a *Account) GetAccountName() string { return a.account }// main.go
acc := test2.NewAccount("DavidEli", "135790", 666)
fmt.Println("创建成功", acc)
acc.SetAccountName("phunzage")
fmt.Println(acc.GetAccountName()) // phunzage类型断言 (Type Assertion)
类型断言用于提取接口值的底层具体类型。语法为 x.(T),其中 x 必须是接口类型,T 是目标类型。
基本语法
var x interface{} = "hello"
// 直接断言(不安全,失败会 panic)
v := x.(string)
fmt.Println(v) // hello
// ok 模式(推荐——安全)
v, ok := x.(string)
if ok {
fmt.Println("是字符串:", v)
} else {
fmt.Println("不是字符串类型")
}多类型判断(type switch)
func WhatType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("字符串:", v)
case int:
fmt.Println("整数:", v)
case bool:
fmt.Println("布尔值:", v)
default:
fmt.Println("未知类型")
}
}
func main() {
WhatType("Go语言") // 字符串: Go语言
WhatType(42) // 整数: 42
WhatType(true) // 布尔值: true
}空接口
空接口 interface{} 没有任何方法签名,因此任何类型都实现了空接口。它可以用来表示任意类型的值。
定义与使用
// 空接口可以接收任意类型的值
var any interface{}
any = 42
any = "hello"
any = 3.14
any = []int{1, 2, 3}
func PrintAny(v interface{}) {
fmt.Printf("值: %v, 类型: %T\n", v, v)
}经典应用:fmt.Println
// fmt.Println 的参数就是空接口切片
// func Println(a ...interface{}) (n int, err error)
fmt.Println(42) // 42
fmt.Println("hello") // hello
fmt.Println(true) // true
fmt.Println([]int{1, 2, 3}) // [1 2 3]map 和 slice 中的空接口
// map 值为空接口,可存储不同类型
m := make(map[string]interface{})
m["name"] = "Alice"
m["age"] = 30
m["active"] = true
// 切片元素为空接口
items := []interface{}{"Go", 2024, 3.14, true}使用空接口存储不同类型的值后,取出时需配合类型断言还原具体类型。
泛型 (Generics)
Go 1.18 引入了泛型,支持类型参数,使函数和结构体可以处理多种类型而无需重复编写代码。
类型参数
// [T any] 表示 T 可以是任意类型
func PrintSlice[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
PrintSlice([]int{1, 2, 3}) // 1 2 3
PrintSlice([]string{"a", "b", "c"}) // a b c
PrintSlice([]float64{1.1, 2.2}) // 1.1 2.2
}泛型结构体
// 泛型栈 —— 任意类型都能用
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func main() {
intStack := Stack[int]{}
intStack.Push(10)
intStack.Push(20)
fmt.Println(intStack.Pop()) // 20 true
strStack := Stack[string]{}
strStack.Push("hello")
strStack.Push("world")
fmt.Println(strStack.Pop()) // world true
}comparable 约束
comparable 约束表示类型必须支持 == 和 != 比较运算符,适合泛型查找或比较场景。
// 泛型查找 —— 查找切片中是否包含某个元素
func Contains[T comparable](s []T, target T) bool {
for _, v := range s {
if v == target {
return true
}
}
return false
}
func main() {
fmt.Println(Contains([]int{1, 2, 3, 4}, 3)) // true
fmt.Println(Contains([]string{"a", "b", "c"}, "z")) // false
}多类型参数
泛型函数可以同时使用多个类型参数,每个参数可以有不同的约束。
// map 转换 —— 将 []T1 通过函数 f 映射为 []T2
func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {
result := make([]T2, len(s))
for i, v := range s {
result[i] = f(v)
}
return result
}
func main() {
nums := []int{1, 2, 3}
// int → string
strs := Map(nums, func(n int) string {
return fmt.Sprintf("数字%d", n)
})
fmt.Println(strs) // [数字1 数字2 数字3]
// int → float64
squares := Map(nums, func(n int) float64 {
return float64(n * n)
})
fmt.Println(squares) // [1 4 9]
}总结
- Go 通过结构体 + 方法实现封装
- 通过接口实现多态(隐式实现,灵活度更高)
- 通过结构体嵌套(而非继承)实现代码复用
- Go 更加推崇组合优于继承
