引言
Go 语言与 Java、C++ 等传统面向对象(OOP)语言在实现 OOP 思想的方式上有显著差异。传统 OOP 语言依赖 "类(class)""继承(extends)""显式接口实现" 等语法构建对象模型,而 Go 以 "简洁、灵活、组合优于继承" 为设计理念,通过更轻量的语法实现 OOP 核心思想(封装、继承、多态),避免了传统 OOP 的冗余和复杂性。
通过下面这个例子快速对比一下Java和Go的区别:
Java
java
// 类是属性和方法的统一载体
class Person {
// 属性
private String name;
private int age;
// 方法
public void setAge(int a) {
if (a > 0) age = a;
}
public int getAge() {
return age;
}
}
Go
go
// 结构体仅存储属性(无方法)
type Person struct {
name string // 属性
age int
}
// 方法通过"接收者"绑定到结构体(分离定义)
func (p *Person) SetAge(a int) { // 接收者表明方法属于Person
if a > 0 {
p.age = a
}
}
func (p Person) GetAge() int {
return p.age
}
- 传统 OOP 中,类是 "属性 + 方法" 的强制绑定容器,语法上必须将方法定义在类内部;
- Go 中,结构体与方法是 "松耦合" 的:结构体仅负责数据存储,方法通过接收者关联,可在结构体定义之外(同一包内)添加方法,更灵活
通过这篇文章,你将学会Go语言中的面向对象编程的三个特性:
- 封装
- 继承
- 多态
一、封装
封装的核心是 "将数据和操作数据的方法绑定,并控制访问权限"。在 Go 中,这通过"结构体(数据)+ 结构体方法(行为) + 首字母大小写(访问控制)" 实现。
结构体作为 "类":存储数据(属性)
结构体(struct
)可以类比为 OOP 中的 "类",用于定义一组相关的属性(字段)。例如,定义一个 "人(Person)" 的结构体,包含姓名、年龄等属性:
go
// 定义结构体(类似"类"的属性定义)
type Person struct {
Name string // 首字母大写:公开字段(类似public)
age int // 首字母小写:私有字段(仅当前包可见,类似private)
}
结构体方法作为 "成员方法":定义行为
通过 "结构体方法" 可以为结构体绑定行为(类似类的成员方法),语法是在函数名前加上 "接收者(receiver)"------ 即方法属于哪个结构体。
go
// 为Person结构体定义方法(类似"成员方法")
// 接收者为*Person(指针类型):可以修改结构体内部数据
func (p *Person) SetAge(a int) {
if a > 0 && a < 150 { // 封装校验逻辑
p.age = a
}
}
// 接收者为Person(值类型):仅读取数据,不修改
func (p Person) GetAge() int {
return p.age
}
func (p Person) Greet() string {
return "Hello, I'm " + p.Name
}
- 接收者为指针类型(
*T
) :方法内部可以修改结构体的字段(类似 "引用传递"); - 接收者为值类型(
T
) :方法内部操作的是结构体的副本,无法修改原结构体(类似 "值传递")。
访问控制:通过首字母大小写控制权限
Go 没有public
/private
关键字,而是通过标识符首字母大小写控制访问权限:
- 首字母大写 :公开(
public
),可在其他包访问(如Name
、SetAge
); - 首字母小写 :私有(
private
),仅在当前包内访问(如age
)。
这种设计强制将 "内部实现细节"(私有字段 / 方法)与 "外部接口"(公开字段 / 方法)分离,实现了封装的核心目标。
二、继承
传统 OOP 的 "继承" 是 "is-a" 关系(如 "学生是一个人"),而 Go 通过结构体嵌入(匿名字段) 实现 "has-a" 组合关系(如 "学生包含一个人的属性"),更灵活地复用代码。
结构体嵌入:复用字段和方法
如果一个结构体 A 嵌入了结构体 B(作为匿名字段),那么 A 会 "继承" B 的所有字段和方法(无需显式调用),实现代码复用。
例如,"学生(Student)" 需要复用 "人(Person)" 的属性和方法:
go
// 定义Student结构体,嵌入Person(匿名字段)
type Student struct {
Person // 嵌入Person结构体(组合),Student"拥有"Person的所有字段和方法
School string
Grade int
}
// 使用示例
func main() {
s := Student{
Person: Person{Name: "Alice"}, // 初始化嵌入的Person
School: "Sunny School",
Grade: 3,
}
// 直接访问嵌入结构体的字段和方法(无需显式写s.Person)
s.SetAge(10) // 调用Person的SetAge方法
fmt.Println(s.Name) // 访问Person的Name字段(输出:Alice)
fmt.Println(s.GetAge()) // 调用Person的GetAge方法(输出:10)
fmt.Println(s.Greet()) // 调用Person的Greet方法(输出:Hello, I'm Alice)
}
- 嵌入后,
Student
可以直接使用Person
的字段(Name
)和方法(SetAge
、Greet
),实现了类似 "继承" 的代码复用; - 与传统继承不同,组合是 "平级" 关系,
Student
不是Person
的子类,而是 "包含"Person
的属性,避免了继承的 "类层次臃肿" 问题。
方法重写:覆盖嵌入结构体的方法
如果嵌入结构体的方法不符合需求,可在外部结构体中定义同名方法,实现 "方法重写",类似C++和Java中的 override
:
go
// 重写Person的Greet方法(Student的Greet会覆盖Person的Greet)
func (s Student) Greet() string {
return "Hello, I'm " + s.Name + " from " + s.School
}
func main() {
s := Student{Person: Person{Name: "Alice"}, School: "Sunny School"}
fmt.Println(s.Greet()) // 输出:Hello, I'm Alice from Sunny School(调用的是Student的Greet)
}
三、多态:通过接口实现 "同一接口,不同实现"
多态的核心是 "同一行为,不同实现"。Go 通过接口(interface) 实现多态:只要一个类型实现了接口的所有方法,就 "隐式" 实现了该接口,无需显式声明(如Java的implements
关键字)。
定义接口:声明行为规范
接口是一组方法签名的集合,定义了 "应该有哪些行为",但不包含实现。例如,定义一个 "说话者(Speaker)" 接口,要求实现Speak()
方法:
go
// 定义接口(行为规范)
type Speaker interface {
Speak() string // 接口仅声明方法签名,无实现
}
不同类型实现接口
任何类型(结构体、基本类型等)只要实现了接口的所有方法,就属于该接口类型。例如,Person
和Dog
都实现Speaker
接口:
go
// Person实现Speaker接口(实现Speak方法)
func (p Person) Speak() string {
return "I'm " + p.Name
}
// 定义Dog结构体
type Dog struct {
Breed string
}
// Dog实现Speaker接口(实现Speak方法)
func (d Dog) Speak() string {
return "Woof! I'm a " + d.Breed
}
Person
和Dog
都实现了Speaker
的Speak()
方法,因此都属于Speaker
类型,无需显式声明 "Person implements Speaker
";- 这是 Go 的 "隐式接口" 设计,极大简化了多态的实现。
- 注意 :只有当一个类型实现了接口中所有方法(方法名、参数列表、返回值列表完全匹配),才能说该类型 "实现了这个接口"。哪怕只缺少一个方法,也不构成对接口的实现。
多态调用:用接口变量统一调用
通过接口变量,可以统一调用不同类型的实现,实现 "同一接口,不同行为":
go
// 接收Speaker接口类型的函数(统一调用入口)
func LetThemSpeak(s Speaker) {
fmt.Println(s.Speak()) // 调用的是具体类型的Speak方法
}
func main() {
p := Person{Name: "Bob"}
d := Dog{Breed: "Golden Retriever"}
// 不同类型都可以传给Speaker接口变量
LetThemSpeak(p) // 输出:I'm Bob(调用Person的Speak)
LetThemSpeak(d) // 输出:Woof! I'm a Golden Retriever(调用Dog的Speak)
}
这里,LetThemSpeak
函数无需关心参数是Person
还是Dog
,只需知道它实现了Speaker
接口 ------ 这就是多态的核心价值:统一接口,屏蔽具体实现差异。
传统 OOP 语言(如 Java、C++)在发展中不断 "做加法",引入了抽象类、接口、泛型、注解等复杂特性,试图覆盖所有场景;而 Go 坚持 "做减法",以 "简洁、实用" 为核心,仅保留实现 OOP 思想的必要工具。
四、举个栗子:用Go实现观察者模式
代码:
go
package main
import "fmt"
// 1. 定义观察者接口(所有观察者需实现该接口)
type Observer interface {
Update(message string) // 接收更新的方法
}
// 2. 定义主题接口(被观察的对象需实现该接口)
type Subject interface {
Register(observer Observer) // 注册观察者
Unregister(observer Observer) // 移除观察者
Notify(message string) // 通知所有观察者
}
// 3. 实现具体主题(新闻发布者)
type NewsPublisher struct {
observers []Observer // 维护观察者列表
}
// 注册观察者
func (n *NewsPublisher) Register(observer Observer) {
n.observers = append(n.observers, observer)
}
// 移除观察者
func (n *NewsPublisher) Unregister(observer Observer) {
// 找到观察者并从列表中移除
for i, obs := range n.observers {
if obs == observer {
// 拼接切片,移除目标元素
n.observers = append(n.observers[:i], n.observers[i+1:]...)
break
}
}
}
// 通知所有观察者
func (n *NewsPublisher) Notify(message string) {
// 遍历所有观察者并发送更新
for _, observer := range n.observers {
observer.Update(message)
}
}
// 发布新闻(触发通知的业务方法)
func (n *NewsPublisher) PublishNews(content string) {
fmt.Println("发布新闻:", content)
n.Notify(content) // 发布后通知所有订阅者
}
// 4. 实现具体观察者(新闻订阅者)
// 观察者1:手机用户
type MobileUser struct {
name string
}
func (m *MobileUser) Update(message string) {
fmt.Printf("[手机用户] %s 收到新闻:%s\n", m.name, message)
}
// 观察者2:电脑用户
type PCUser struct {
name string
}
func (p *PCUser) Update(message string) {
fmt.Printf("[电脑用户] %s 收到新闻:%s\n", p.name, message)
}
// 5. 演示使用
func main() {
// 创建主题(新闻发布者)
publisher := &NewsPublisher{}
// 创建观察者(订阅者)
mobileUser := &MobileUser{name: "张三"} // 注意,使用指针类型
pcUser := &PCUser{name: "李四"}
// 注册观察者
publisher.Register(mobileUser)
publisher.Register(pcUser)
// 发布第一条新闻
publisher.PublishNews("Go语言发布新版本!")
fmt.Println("---")
// 移除一个观察者
publisher.Unregister(pcUser)
// 发布第二条新闻
publisher.PublishNews("观察者模式在Go中的应用")
}
运行结果:

总结
传统 OOP 语言(如 Java、C++)在发展中不断 "做加法",引入了抽象类、接口、泛型、注解等复杂特性,试图覆盖所有场景;而 Go 坚持 "做减法",以 "简洁、实用" 为核心,仅保留实现 OOP 思想的必要工具。
- 传统 OOP:依赖 "类 - 继承 - 接口" 的严格层级,更注重 "形式上的完整性"(例如必须用
class
定义对象,用extends
表示继承); - Go:不追求形式,更注重 "本质功能"------ 用结构体 + 方法实现封装,用组合实现复用,用隐式接口实现多态,去掉了所有非必要的语法糖(如
class
、extends
、implements
),让代码更简洁、易读。
如果读者以前比较熟悉C++、Java等传统 OOP 语言,对 OOP 的概念应该很好理解,但是Go没有了类、继承等关键字,还是需要花一些时间适应一下Go中的 OOP 写法。
参考资料
1\] [聊聊 Go 语言中的面向对象编程 - 知乎](https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F94625212 "https://zhuanlan.zhihu.com/p/94625212")