【Golang开发】快速入门Go——Go语言中的面向对象编程

引言

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),可在其他包访问(如NameSetAge);
  • 首字母小写 :私有(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)和方法(SetAgeGreet),实现了类似 "继承" 的代码复用;
  • 与传统继承不同,组合是 "平级" 关系,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 // 接口仅声明方法签名,无实现
}

不同类型实现接口

任何类型(结构体、基本类型等)只要实现了接口的所有方法,就属于该接口类型。例如,PersonDog都实现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
}
  • PersonDog都实现了SpeakerSpeak()方法,因此都属于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:不追求形式,更注重 "本质功能"------ 用结构体 + 方法实现封装,用组合实现复用,用隐式接口实现多态,去掉了所有非必要的语法糖(如classextendsimplements),让代码更简洁、易读。

如果读者以前比较熟悉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")

相关推荐
bobz9652 小时前
ubuntu install NVIDIA Container Toolkit
后端
绝无仅有3 小时前
Go Timer 面试指南:常见问题及答案解析
后端·算法·架构
绝无仅有3 小时前
Go 语言面试指南:常见问题及答案解析
后端·面试·go
bobz9653 小时前
containerd (管理) 和 runc (执行)分离
后端
bobz9653 小时前
Docker 与 containerd 的架构差异
后端
程序猿阿伟3 小时前
《跳出“技术堆砌”陷阱,构建可演进的软件系统》
后端
就叫飞六吧4 小时前
基于Spring Boot的短信平台平滑切换设计方案
java·spring boot·后端
bobz9654 小时前
NVIDIA Container Toolkit(容器运行时依赖)
后端
bobz9654 小时前
NVIDIA Container Toolkit 架构上下文
后端
爱读源码的大都督5 小时前
小白LLM教程:不训练模型,如何进行微调?
java·人工智能·后端