464 lines
13 KiB
Markdown
464 lines
13 KiB
Markdown
# 第 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 // 匿名内嵌 Animal,Dog 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 设计模式。函数参数应尽量使用接口类型,增加灵活性和可测试性;而返回值应尽量是具体的结构体类型,让调用者清楚地知道他们得到了什么。
|