Files
notes/resource/go/Go 语言的接口与方法.md
T
2026-03-01 01:43:46 +08:00

464 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 1 章:Go 语言的面向对象编程思想
它没有传统 OOP 语言(如 Java, C++)中的 `class``extends` 等关键字,但通过 `struct``method``interface` 的组合,为开发者提供了强大而灵活的面向对象编程能力。
在软件开发中,代码的组织遵循着一个自然的进化路径:
1. 函数(Function):最初,功能代码被聚合在独立的函数中。
2. 结构体(Struct):随着复杂度的增加,相关的变量和状态被组合成结构体,用于封装数据。
3. 方法(Method):将操作这些数据的函数(行为)与结构体(数据)绑定,就形成了方法,这已非常接近于传统 OOP 中“类”的概念。
# 第 2 章:核心构建块:类型、结构体与方法
## 2.1 `type` 关键字:定义新类型与类型别名
`type` 关键字是 Go 中构建自定义数据结构的基础。
1. **定义新类型**`type MyInt int`
- 创建了一个全新的、独立的类型 `MyInt`
- 它与 `int` 在类型系统上不兼容,需要显式转换才能混合运算。
2. **定义类型别名**`type AliasInt = int`
- `AliasInt` 只是 `int` 的一个别名,它们是完全相同的类型。
- 主要用于提高代码可读性,在 Go 1.9 版本引入。`any` 就是 `interface{}` 的类型别名。
```go
package main
import "fmt"
// 1. 定义一个全新的类型 MyInt
type MyInt int
// 2. 为 int 类型定义一个别名 Number
// 在 Go 1.18 之后,any 就是 interface{} 的类型别名
type Number = int
func main() {
// --- 新类型示例 ---
var a MyInt = 10
var b int = 20
// a 和 b 是不同类型,直接相加会编译错误
// fmt.Println(a + b) // a + b (mismatched types MyInt and int)
// 必须将 MyInt 类型的 a 转换为 int 类型
fmt.Printf("新类型 MyInt 转换为 int 后与 int 相加: %d\n", int(a)+b)
fmt.Printf("类型 a: %T, 类型 b: %T\n", a, b)
fmt.Println("--------------------")
// --- 类型别名示例 ---
var x Number = 30
var y int = 40
// x 和 y 本质上都是 int 类型,可以直接运算
fmt.Printf("类型别名 Number 与 int 直接相加: %d\n", x+y)
fmt.Printf("类型 x: %T, 类型 y: %T\n", x, y)
}
```
## 2.2 `struct`:数据聚合的基石
`struct` (结构体)是 Go 中聚合零个或多个任意类型字段的数据集合,它是构建复杂数据结构和实现面向对象编程中“数据”部分的基础。
```go
package main
import "fmt"
// 定义一个 Person 结构体,用于封装人的属性
type Person struct {
Name string
Age int
}
func main() {
// 创建并初始化一个 Person 实例
p1 := Person{
Name: "Alice",
Age: 30,
}
fmt.Printf("创建的 Person 实例: %+v\n", p1)
// 访问结构体的字段
fmt.Printf("姓名: %s, 年龄: %d\n", p1.Name, p1.Age)
}
```
## 2.3 `method`:为类型绑定行为
方法(Method)是绑定到特定类型的函数。它将数据(struct)和操作数据的行为(function)结合起来。
- **函数 (Function)**: `func DoSomething()`,独立调用。
- **方法 (Method)**: `func (t MyType) DoSomething()`,通过类型的实例 `instance.DoSomething()` 调用。
方法的接收者可以是**值类型**或**指针类型**。
```go
package main
import "fmt"
// 定义一个矩形结构体
type Rectangle struct {
width, height float64
}
// 1. 值接收者方法 (Value Receiver)
// 接收者 r 是 Rectangle 的一个副本。
// 方法内部对 r 的修改不会影响原始的 Rectangle 实例。
// 适用于不需要修改原始数据,并且结构体较小的场景。
func (r Rectangle) Area() float64 {
// r.width = 100 // 这里的修改是无效的,因为 r 只是一个副本
return r.width * r.height
}
// 2. 指针接收者方法 (Pointer Receiver)
// 接收者 r 是一个指向 Rectangle 实例的指针。
// 方法内部对 *r 的修改会直接影响原始的 Rectangle 实例。
// 适用于需要修改原始数据,或结构体较大以避免复制开销的场景。
func (r *Rectangle) Scale(factor float64) {
r.width *= factor
r.height *= factor
}
func main() {
rect := Rectangle{width: 10, height: 5}
// 调用值接收者方法
fmt.Printf("原始矩形: %+v\n", rect)
fmt.Printf("面积: %.2f\n", rect.Area())
// 调用指针接收者方法
// Go 语言会自动进行转换,rect.Scale(2) 等效于 (&rect).Scale(2)
rect.Scale(2)
fmt.Printf("缩放后的矩形: %+v\n", rect)
fmt.Printf("缩放后的面积: %.2f\n", rect.Area())
}
```
## 值接收者 vs. 指针接收者选择指南
1. **要修改接收者的状态吗?**
- **是** -> **必须**使用指针接收者 (`*T`)。
- **否** -> 可以使用值接收者 (`T`)。
2. **性能考虑**
- 如果接收者是一个**大型结构体**或数组,使用**指针接收者**可以避免昂贵的内存拷贝。
3. **一致性**
- 如果一个类型已经有了指针接收者的方法,那么其他方法也建议使用指针接收者以保持一致。
4. **特殊类型**
- 如果类型包含 `sync.Mutex` 或其他不应被复制的同步字段,**必须**使用指针接收者。
# 第 3 章:代码复用:组合与内嵌
Go 语言通过结构体**内嵌**Embedding)和**组合**Composition)实现代码复用,而不是传统的类继承。
## 3.1 结构体内嵌(模拟继承 - is-a 关系)
通过在结构体中**匿名地**嵌入另一个结构体类型,可以获得被嵌入结构体的所有字段和方法,这在形式上模拟了“继承”。
```go
package main
import "fmt"
// "父类":动物
type Animal struct {
name string
}
// Animal 的方法
func (a *Animal) Move() {
fmt.Printf("%s is moving.\n", a.name)
}
// "子类":狗
type Dog struct {
Animal // 匿名内嵌 AnimalDog is an Animal
breed string
}
// Dog 重写了 Move 方法
func (d *Dog) Move() {
fmt.Printf("%s the %s is running happily!\n", d.name, d.breed)
}
// Dog 特有的方法
func (d *Dog) Bark() {
fmt.Printf("%s says: Woof! Woof!\n", d.name)
}
func main() {
// 创建一个 Dog 实例
dog := &Dog{
Animal: Animal{name: "Buddy"},
breed: "Golden Retriever",
}
// 1. 可以直接访问内嵌结构体的字段
fmt.Printf("Dog's name is: %s\n", dog.name)
// 2. 调用 Dog 自己重写的方法 (就近原则)
dog.Move()
// 3. 调用内嵌结构体的原始方法 (需要显式指定)
dog.Animal.Move()
// 4. 调用 Dog 特有的方法
dog.Bark()
}
```
## 3.2 组合(has-a 关系)
组合是通过在结构体中包含一个**具名**的字段来实现的。这表示一个类型“拥有”另一个类型的实例。 **Go 语言哲学:优先使用组合而不是继承。**
```go
package main
import "fmt"
// 定义引擎结构体
type Engine struct {
horsepower int
}
// 引擎的方法
func (e *Engine) Start() {
fmt.Printf("Engine with %d HP starts. Vroom!\n", e.horsepower)
}
// 定义汽车结构体
type Car struct {
brand string
engine Engine // 具名字段,Car has an Engine
}
func main() {
// 创建一个 Car 实例,它“拥有”一个 Engine
myCar := Car{
brand: "Ferrari",
engine: Engine{
horsepower: 710,
},
}
fmt.Printf("My car is a %s.\n", myCar.brand)
// 访问组合部件的方法需要通过字段名
myCar.engine.Start()
}
```
# 第 4 章:多态的基石:接口
接口(Interface)是 Go 实现多态的核心。它定义了一组方法的集合(契约),任何类型只要实现了接口中所有的方法,就被认为是该接口的实现,这个过程是**隐式**的。
## 4.1 接口的定义与实现(多态)
多态即“多种形态”。一个变量可以持有实现了同一接口的不同类型的值,并在运行时调用相应类型的方法。
```go
package main
import "fmt"
// 1. 定义一个接口 (契约)
// Shaper 接口定义了一个行为:计算面积
type Shaper interface {
Area() float64
}
// 2. 定义实现该接口的多个结构体
type Circle struct {
radius float64
}
// Circle 实现了 Shaper 接口
func (c Circle) Area() float64 {
return 3.14159 * c.radius * c.radius
}
type Square struct {
side float64
}
// Square 实现了 Shaper 接口
func (s Square) Area() float64 {
return s.side * s.side
}
// 3. 定义一个接收接口类型参数的函数,实现多态
func PrintArea(s Shaper) {
fmt.Printf("This shape is of type %T and its area is %.2f\n", s, s.Area())
}
func main() {
// 创建不同类型的实例
circle := Circle{radius: 5}
square := Square{side: 4}
// 使用多态函数
// 尽管 circle 和 square 是不同类型
// 但它们都满足 Shaper 接口,所以可以被 PrintArea 函数接收
PrintArea(circle)
PrintArea(square)
// 接口变量可以持有任何实现了该接口的类型的值
var shaper Shaper
shaper = circle
fmt.Printf("Shaper variable holding a Circle: Area=%.2f\n", shaper.Area())
shaper = square
fmt.Printf("Shaper variable holding a Square: Area=%.2f\n", shaper.Area())
}
```
## 4.2 空接口 (`interface{}` 或 `any`)
空接口不包含任何方法,因此**所有类型都默认实现了空接口**。它可以用来存储任意类型的值。自 Go 1.18 起,`any` 成为了 `interface{}` 的官方别名。
```go
package main
import "fmt"
// 这个函数可以接收并打印任何类型的值
func printAnything(v any) { // 使用 any 代替 interface{}
fmt.Printf("Value: %v, Type: %T\n", v, v)
}
func main() {
printAnything(42)
printAnything("Hello, Go!")
printAnything(true)
printAnything(3.14)
// 在集合类型中非常有用
heterogeneousSlice := []any{"text", 100, false, 2.718}
fmt.Println("\nA slice with mixed types:", heterogeneousSlice)
hetegeneousMap := map[string]any{
"name": "Excalicode",
"version": 1.0,
"active": true,
}
fmt.Println("A map with mixed value types:", hetegeneousMap)
}
```
## 4.3 接口断言(Type Assertion
接口断言用于在运行时检查一个接口类型变量所持有的具体类型,并获取其底层值。
1. **安全形式**`value, ok := i.(Type)`。如果断言成功,`ok``true``value` 为具体类型的值;如果失败,`ok``false``value` 为该类型的零值。**这是推荐的方式**。
2. **非安全形式**`value := i.(Type)`。如果断言失败,程序会发生 `panic`
3. **Type Switch**`switch v := i.(type)`。用于判断多种可能的类型。
```go
package main
import "fmt"
func process(v any) {
fmt.Printf("\nProcessing value: %v\n", v)
// 1. 安全的类型断言
str, ok := v.(string)
if ok {
fmt.Printf("It's a string! Value: \"%s\"\n", str)
return
}
// 2. 使用 Type Switch 处理多种类型
switch t := v.(type) {
case int:
fmt.Printf("It's an int! Value: %d\n", t)
case bool:
fmt.Printf("It's a bool! Value: %v\n", t)
case float64:
fmt.Printf("It's a float64! Value: %.2f\n", t)
default:
fmt.Printf("It's an unknown type: %T\n", t)
}
}
func main() {
process("Go is awesome")
process(2024)
process(true)
process(3.14159)
process([]int{1, 2, 3})
}
```
## 4.4 接口嵌套(Interface Embedding
一个接口可以嵌入其他接口,从而组合它们的方法集。一个类型必须实现所有被嵌入接口的方法,才能满足这个组合后的大接口。
```go
package main
import "fmt"
// 定义基础接口
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// 接口嵌套:ReadWriter 组合了 Reader 和 Writer
type ReadWriter interface {
Reader
Writer
}
// 定义一个实现 ReadWriter 接口的结构体
type MemoryBuffer struct {
data string
}
// 实现 Reader 接口
func (mb *MemoryBuffer) Read() string {
return mb.data
}
// 实现 Writer 接口
func (mb *MemoryBuffer) Write(data string) {
mb.data = data
fmt.Printf("Wrote '%s' to buffer.\n", data)
}
func main() {
// 创建实例
buffer := &MemoryBuffer{}
// buffer 既是 Reader, 也是 Writer, 也是 ReadWriter
// 1. 使用 ReadWriter 接口
var rw ReadWriter = buffer
rw.Write("Hello, nested interfaces!")
fmt.Printf("Read from ReadWriter: %s\n", rw.Read())
// 2. 也可以作为 Reader 接口使用
var r Reader = buffer
fmt.Printf("Read from Reader: %s\n", r.Read())
// 3. 也可以作为 Writer 接口使用
var w Writer = buffer
w.Write("New data")
fmt.Printf("Read again after writing via Writer interface: %s\n", rw.Read())
}
```
## 接口使用的最佳实践
1. **小接口,大作为**:倾向于定义小而专一的接口(例如 `io.Reader`),而不是一个包含几十个方法的大而全的接口。Go 的名言是:“The bigger the interface, the weaker the abstraction.”(接口越大,抽象能力越弱)。
2. **`er` 命名约定**:对于只包含一个方法的接口,通常在方法名后加上 `er` 后缀来命名,如 `Reader`, `Writer`, `Stringer`
3. **接收接口,返回结构体 (Accept Interfaces, Return Structs)**:这是一个重要的 Go 设计模式。函数参数应尽量使用接口类型,增加灵活性和可测试性;而返回值应尽量是具体的结构体类型,让调用者清楚地知道他们得到了什么。