当前位置: 首页 > news >正文

第五章:Go的“面向对象”编程

在这里插入图片描述


上一章:《第四章:基础语法》


文章目录

  • 1.核心概念:与Java的初步对比
  • 2.结构体(Struct):替代类的数据载体
    • 2.1 定义与初始化
    • 2.2 访问与修改字段
  • 3.方法(Method):为类型添加行为
  • 4.封装与可见性:命名的大小写规则
  • 5.“继承”与组合:类型嵌入
  • 6.接口(Interface):多态的灵魂
    • 6.1 接口的定义与实现
    • 6.2 空接口与类型断言
  • 7.实战案例:一个简单的图形计算程序

对于来自Java等传统OOP语言的开发者来说,这一章将是思维转换的关键。

Go语言的OOP没有“类”(class)的概念,而是通过​​结构体(struct)​​、​​方法(method)​​ 和​​接口(interface)​​ 以一种更简洁、更灵活的方式来实现。让我们来深入探索。

1.核心概念:与Java的初步对比

在开始具体语法之前,了解Go语言OOP的设计哲学至关重要。下表清晰地展示了Go与Java在OOP核心概念上的主要区别:

特性Java (传统OOP)Go (简化OOP)
基本构建块类(Class)结构体(Struct)
封装public, private, protected等访问修饰符字母大小写(大写导出,小写私有)
方法定义在类内部定义与类型关联的接收者(Receiver)
继承extends关键字(单继承)通过组合(Composition)和类型嵌入
多态通过implements显式实现接口通过接口隐式实现(鸭子类型)
核心原则一切皆对象,围绕类构建组合优于继承,面向接口编程

​​核心思维转换​​:在Go中,请暂时忘记“类”和“继承”。思考的重点将变为​​“组合”​​ 和​​“行为”​​。一个类型只要实现了接口的所有方法,它就自动实现了该接口,无需显式声明

2.结构体(Struct):替代类的数据载体

结构体是Go中组合不同类型数据以形成新类型的核心方式,相当于类的数据部分

2.1 定义与初始化

// 定义一个Person结构体(类似一个简单的类)
type Person struct {Name string // 字段(属性)Age  int
}func main() {// 多种初始化方式// 1. 声明后赋值var p1 Personp1.Name = "Alice"p1.Age = 30// 2. 使用结构体字面量(推荐)p2 := Person{Name: "Bob", // 显式指定字段名,顺序可变Age:  25,}// 3. 按字段顺序初始化(不推荐,易错)p3 := Person{"Charlie", 28}fmt.Println(p1, p2, p3)
}

2.2 访问与修改字段

使用点号(.)操作符

p2.Name = "Bobby" // 修改字段值
fmt.Println(p2.Name) // 访问字段值

3.方法(Method):为类型添加行为

在Go中,方法不是定义在结构体内部,而是通过与特定类型​​绑定​​来实现的。这个绑定通过​​接收者(Receiver)​​ 完成

func sayHello(p Person) {fmt.Printf("Hello, my name is %s,I am %d years old\n", p.name, p.age)
}
func addAge(p *Person) {p.age++
}func (p *Person) addAge1() {p.age++
}func main() {person := Person{name: "张三",age:  20,}sayHello(person)addAge(&person)sayHello(person)person.addAge1()sayHello(person)if person.age > 18 {fmt.Println("adult")}
}

在这里插入图片描述

  • 值接收者(如 (p Person))​​:当方法不需要修改接收者的字段,或者操作结构体的副本即可时使用。适用于小型结构体或基础类型。
  • ​​指针接收者(如 (p *Person))​​:当方法需要修改接收者的字段,或者结构体很大为避免复制的开销时使用。​​在需要修改接收者状态时,必须使用指针接收者

4.封装与可见性:命名的大小写规则

Go没有访问修饰符关键字。它通过标识符(类型名、字段名、方法名)的​​首字母大小写​​来控制可见性。

  • ​​首字母大写​​:​​公开的(Public)​​,可以被其他包中的代码访问。
  • ​​首字母小写​​:​​包内私有的(Private)​​,只能在当前包内访问。
// person.go (在包mylib中)
type Person struct { // 类型名大写,其他包可访问Name string // 字段名大写,其他包可访问和修改age  int    // 字段名小写,仅在mylib包内可访问
}// 方法名大写,其他包可调用
func (p *Person) SetAge(newAge int) {if newAge > 0 && newAge < 150 { // 可在此添加验证逻辑p.age = newAge}
}// 方法名小写,仅在mylib包内可调用
func (p *Person) validateAge(a int) bool {return a > 0
}

这种基于命名规则的封装鼓励你设计清晰的公开API,并将实现细节隐藏起来

上面只是一个简单的书写示例,下面我们新增一个包person,并添加person.go文件,同时在person包外部新增test_visibility.go来验证可见性
在这里插入图片描述

包结构:

go_ai_agent/                    # 项目根目录
├── person/                     # person包目录
│   └── person.go              # 包名:package person
├── test_visibility.go         # 主程序文件,包名:package main
package person
// Person 结构体演示真正的包外可见性
type Person struct {Name string // 公开字段age  int    // 私有字段
}// NewPerson 构造函数
func NewPerson(name string, age int) *Person {return &Person{Name: name,age:  age,}
}// GetAge 获取年龄的公开方法
func (p *Person) GetAge() int {return p.age
}// SetAge 设置年龄的公开方法
func (p *Person) SetAge(age int) {p.age = age
}// UpdateName 更新姓名的公开方法
func (p *Person) UpdateName(name string) {p.Name = name
}// updateName 更新姓名的私有方法
func (p *Person) updateName(name string) {p.Name = name
}
  • 目录名:person
  • 包名:package person(通常与目录名相同)

这里其实已经有面向对象的概念了,通过公开的方法设置对象的私有属性值是我们Java中常做的事情

package mainimport ("fmt""go_ai_agent/person" // 导入person包
)func main() {fmt.Println("=== 真正的包外可见性测试 ===")// 创建Person实例p := person.NewPerson("张三", 25)// 测试公开字段和方法fmt.Println("姓名:", p.Name)        // ✅ 可以访问p.UpdateName("李四")                // ✅ 可以调用fmt.Println("更新后姓名:", p.Name)// 测试私有字段和方法// fmt.Println("年龄:", p.age)        // ❌ 编译错误:cannot refer to unexported field 'age'// p.updateName("王五")              // ❌ 编译错误:cannot refer to unexported method 'updateName'// 通过公开方法访问私有字段fmt.Println("年龄:", p.GetAge())    // ✅ 通过公开方法访问p.SetAge(30)                       // ✅ 通过公开方法修改fmt.Println("更新后年龄:", p.GetAge())fmt.Println("\n=== 总结 ===")fmt.Println("✅ 大写开头的字段和方法:包外可见")fmt.Println("❌ 小写开头的字段和方法:包外不可见")fmt.Println("💡 通过公开方法可以间接访问私有字段")
}

5.“继承”与组合:类型嵌入

Go推崇​​组合优于继承​​。它通过​​类型嵌入​​(在结构体中嵌入匿名结构体)来实现代码复用,这有时被称为“匿名组合”

// 基础结构体
type Animal struct {Name string
}// 为Animal定义方法
func (a Animal) Speak() {fmt.Println(a.Name, "发出声音")
}// Dog 组合了 Animal(类似于Dog继承自Animal)
type Dog struct {Animal // 类型嵌入(匿名字段)。Dog“拥有”Animal的所有字段和方法Breed  string
}// 可以重写(Override)嵌入类型的方法
func (d Dog) Speak() {fmt.Println(d.Name, "在汪汪叫!")
}func main() {dog := Dog{Animal: Animal{Name: "旺财"}, // 初始化嵌入的结构体Breed:  "柯基",}dog.Speak()           // 输出:旺财 在汪汪叫! (调用Dog自己的方法)dog.Animal.Speak() // 输出:旺财 发出声音 (显式调用嵌入Animal的方法)// Dog可以直接访问Animal的字段fmt.Println("狗的名字是:", dog.Name)
}

通过上面这个简单的例子我们可以发现,所谓类型嵌入(匿名组合),就是:

  • 在结构体中嵌入另一个结构体,但不给字段名
  • 嵌入的结构体称为"匿名字段"
  • 外层结构体自动获得内层结构体的所有字段和方法

方法提升(Method Promotion):

  • 嵌入结构体的方法自动提升到外层结构体
  • 可以直接调用:dog.Eat() 而不是 dog.Animal.Eat()
  • 也可以显式调用:dog.Animal.Eat()

多重嵌入:

  • 可以嵌入多个结构体
  • 支持链式嵌入:A嵌入B,B嵌入C

6.接口(Interface):多态的灵魂

接口是Go语言实现多态的核心,也是Go最强大的特性之一。它的设计哲学是​​鸭子类型(Duck Typing)​​:“如果某个东西走起来像鸭子,叫起来像鸭子,那么它就可以被当作鸭子”

6.1 接口的定义与实现

// 定义一个Speaker接口
type Speaker interface {Speak() string // 只声明方法签名,不实现
}// Person 类型实现了 Speaker 接口(隐式实现)
func (p Person) Speak() string {return "我是" + p.Name
}// Cat 类型也实现了 Speaker 接口
type Cat struct {Name string
}func (c Cat) Speak() string {return c.Name + "说:喵喵喵"
}// 多态的体现:一个函数可以处理任何实现了Speaker接口的类型
func Introduce(s Speaker) {fmt.Println(s.Speak())
}func main() {p := Person{Name: "小明"}c := Cat{Name: "咪咪"}Introduce(p) // 输出:我是小明Introduce(c) // 输出:咪咪说:喵喵喵// 接口变量可以持有任何实现该接口的值var sp Speakersp = pfmt.Println(sp.Speak())sp = cfmt.Println(sp.Speak())
}

按照我们正常的书写习惯,我们应该先定一个service层,专用用于存放接口,并针对person和cat实现Speak()方法
目录结构如下:

go_ai_agent/                    # 项目根目录
├── go.mod                      # Go模块文件 (module go_ai_agent)
├── dto/                        # dto包目录
│   ├── cat.go                  # 包名:package dto
│   └── person.go               # 包名:package dto
├── service/                    # service包目录
│   └── speak.go                # 包名:package service
├── interface_demo.go           # 主程序文件,包名:package main
  • speakService.go
package service// Speaker 接口定义了说话的能力
type Speaker interface {Speak() string    // 说话方法,返回说话内容
}
  • person.go
package dtoimport "fmt"type Person struct {Name stringage  int
}// 构造函数
func NewPerson(name string, age int) *Person {return &Person{Name: name,age:  age,}
}func (p *Person) SetAge(age int) {p.age = age
}func (p *Person) GetAge() int {return p.age
}func (p *Person) SetName(name string) {p.Name = name
}func (p *Person) GetName() string {return p.Name
}// 实现Speaker接口的Speak方法
func (p *Person) Speak() string {return fmt.Sprintf("你好,我是:%s", p.Name)
}
  • cat.go
package dtoimport "fmt"type Cat struct {Name string
}func NewCat(name string) *Cat {return &Cat{Name: name,}
}func (cat *Cat) GetName() string {return cat.Name
}func (cat *Cat) SetName(name string) {cat.Name = name
}// 实现Speaker接口的Speak方法
func (cat *Cat) Speak() string {return fmt.Sprintf("%s 说:喵喵喵!", cat.Name)
}

下面新增一个测试类:

  • interface_demo.go
package mainimport ("fmt""go_ai_agent/dto""go_ai_agent/service"
)func introduce(s service.Speaker) {fmt.Println(s.Speak())
}
func main() {p := dto.NewPerson("Alice", 25)c := dto.NewCat("Whiskers")introduce(p)introduce(c)
}

在这里插入图片描述

在Go中,​​实现接口是隐式的​​。一个类型不需要像Java那样用implements关键字声明它要实现某个接口。它只需要实现了接口中规定的所有方法,那么就自动实现了该接口。这种设计极大地降低了耦合度,使得代码非常灵活

6.2 空接口与类型断言

空接口interface{}不包含任何方法,因此​​所有类型都实现了空接口​​。这常常用于需要处理未知类型数据的场景,类似于Java中的Object

// 可以接受任何类型的参数
func describe(i interface{}) {fmt.Printf("值: %v, 类型: %T\n", i, i)// 使用类型断言来判断接口变量的具体类型if str, ok := i.(string); ok {fmt.Println("这是个字符串,长度是:", len(str))} else if num, ok := i.(int); ok {fmt.Println("这是个整数,乘以2是:", num*2)} else {fmt.Println("是其他类型")}// 更简洁的写法:类型选择(type switch)switch v := i.(type) {case string:fmt.Printf("是字符串: %s\n", v)case int:fmt.Printf("是整数: %d\n", v)default:fmt.Printf("未知类型: %v\n", v)}
}func main() {describe("Hello")describe(42)describe(Person{Name: "Test"})
}

7.实战案例:一个简单的图形计算程序

让我们用一个综合案例来巩固本章知识,实现一个可以计算不同图形面积的程序

大家可以先自己尝试一下,然后在对比一下我实现的差异

目录结构:

geometry/                    # 几何图形包(比calArea更专业)
├── shapes/                  # 图形定义包
│   ├── circle.go           # 圆形
│   ├── rectangle.go        # 矩形  
│   └── shape.go            # 基础图形接口
├── calculator/             # 计算器包
│   └── area.go             # 面积计算逻辑
└── examples/               # 使用示例└── main.go
  • 基础图形接口(shape.go)
    • 每个图形都有自己的名称(三角形,圆,长方形等)
package shapestype Shape struct {Name string
}func NewShape(name string) *Shape {return &Shape{Name: name,}
}
  • 面积计算接口(area.go)
package calculatortype ShapeService interface {Name() stringArea() float64Perimeter() float64
}

里面有三个方法,分别是:图形信息,面积以及周长

下面就是每个图形的实现

  • 圆(circle.go)
package shapesimport ("math"
)type Circle struct {Shaperadius float64
}func NewCircle(radius float64) *Circle {return &Circle{radius: radius,Shape:  *NewShape("圆"),}
}func (c *Circle) GetRadius() float64 {return c.radius
}func (c *Circle) SetRadius(radius float64) {c.radius = radius
}func (c Circle) Area() float64 {return math.Pi * c.radius * c.radius
}func (c Circle) Perimeter() float64 {return math.Pi * c.radius * 2
}// 实现calculator.ShapeService接口
func (c Circle) Name() string {return c.Shape.Name
}
  • 矩形(rectangle.go)
package shapestype Rectangle struct {ShapeWidth  float64Height float64
}func NewRectangle(width float64, height float64) *Rectangle {return &Rectangle{Width:  width,Height: height,Shape:  *NewShape("矩形"),}
}func (r Rectangle) Area() float64 {return r.Width * r.Height
}func (r Rectangle) Perimeter() float64 {return 2 * (r.Width + r.Height)
}// 实现calculator.ShapeService接口
func (r Rectangle) Name() string {return r.Shape.Name
}
  • 测试工具(main.go)
package mainimport ("fmt""go_ai_agent/geometry/calculator""go_ai_agent/geometry/shapes"
)func getShapeAreaInfo(shape calculator.ShapeService) string {return fmt.Sprintf("面积:%.2f,周长:%.2f", shape.Area(), shape.Perimeter())
}func printShapeAreaInfo(shape calculator.ShapeService) {fmt.Printf("图形:%s,%s\n", shape.Name(), getShapeAreaInfo(shape))
}func main() {circle := shapes.NewCircle(10)printShapeAreaInfo(circle)rectangle := shapes.NewRectangle(10, 20)printShapeAreaInfo(rectangle)
}

PS:大脑被Java已经污染,一时半会写的还有些Java面向对象的设计风格,后面会逐渐纠正

通过上面的这一套设计,我们来感受一下Go语言的接口设计思想以及和Java设计的差异

1.隐式实现 (Implicit Implementation)

// 定义接口
type ShapeService interface {Name() stringArea() float64Perimeter() float64
}// 类型自动实现接口(无需显式声明)
type Circle struct {Shaperadius float64
}func (c Circle) Name() string      { return c.Shape.Name }
func (c Circle) Area() float64     { return math.Pi * c.radius * c.radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.radius }
// Circle 自动实现了 ShapeService 接口

Java语言:

// 必须显式声明实现接口
public class Circle implements ShapeService {// 必须实现所有接口方法
}

2.“鸭子类型” (Duck Typing)

Go遵循"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子"的原则:

// 只要有这些方法,就是ShapeService
type ShapeService interface {Name() stringArea() float64Perimeter() float64
}// 任何类型只要有这三个方法,就自动实现了接口

Go vs Java 接口设计对比

方面Go语言Java语言
实现方式隐式实现显式实现 (implements)
设计原则接口发现接口契约
依赖方向使用者定义接口提供者定义接口
接口大小小接口(1-3个方法)大接口(多个方法)
组合方式接口嵌入接口继承
http://www.dtcms.com/a/464955.html

相关文章:

  • 【实用工具】mac电脑计算文件的md5、sha1、sha256
  • 数据结构算法学习:LeetCode热题100-矩阵篇(矩阵置零、螺旋矩阵、旋转图像、搜索二维矩阵 II)
  • CAD文件处理控件Aspose.CAD教程:在 Python 中将 SVG 转换为 PDF
  • Go语言游戏后端开发9:Go语言中的结构体
  • 网页网站作业制作郑州企业网站排名
  • C4D域的应用之鞋底生长动画制作详解
  • C语言自学--文件操作
  • 免费小程序网站网站建设优劣的评价标准
  • Kubernetes(K8S)全面解析:核心概念、架构与实践指南
  • 软件测试分类指南(上):从目标、执行到方法,系统拆解测试核心维度
  • 李宏毅机器学习笔记18
  • 深圳做网站优化工资多少长沙官网seo分析
  • 深入理解SELinux:从核心概念到实战应用
  • W5500接收丢数据
  • 【深度学习新浪潮】大模型推理实战:模型切分核心技术(下)—— 流水线并行+混合并行+工程指南
  • 烟台建站价格推荐门户网站建设公司
  • Node.js/Python 实战:编写一个淘宝商品数据采集器​
  • 网站html模板贵州网站开发流程
  • 【分布式训练】分布式训练中的资源管理分类
  • 重生归来,我要成功 Python 高手--day24 Pandas介绍,属性,方法,数据类型,基本数据操作,排序,算术和逻辑运算,自定义运算
  • 如何在关闭浏览器标签前,可靠地发送 HTTP 请求?
  • http cookie 与 session
  • Asp.net core appsettings.json` 和 `appsettings.Development.json`文件区别
  • ICRA-2025 | 机器人具身探索导航新策略!CTSAC:基于课程学习Transformer SAC算法的目标导向机器人探索
  • ManipulationNet:开启真实世界机器人操作基准测试新时代
  • 物流公司网站模版网页设计与制作做网站
  • 北京网站 百度快照单位如何建设网站
  • 英语文章工具: 提取、过滤文章单词在线工具
  • 良策金宝AI:为光伏工程师打造专属“智能外脑”
  • 《C++ STL list 完全指南:从基础操作到特性对比,解锁链表容器高效用法》