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