嗨👋,我是后端有道,专注于分享高效、实用的后端开发技术与最佳实践,深度聚焦Go
语言在后端开发中的应用与实现。
我们通常认为Go
语言是一种面向对象的语言。面向对象编程有三个核心特性:封装、继承和多态,Go
语言在这三个特性上的表现如何呢?接下来,我们将详细探讨Go
语言如何实现这些特性,看看它是如何处理封装、继承和多态的。
Go
是面向对象的语言吗?
Go
语言并不是传统意义上的面向对象(OOP
)语言,但它支持某些面向对象的编程特性。Go
的设计目标之一是简洁和高效,因此它的面向对象特性与其他语言(如Java
或C++
)的实现有所不同。
Go
语言通过struct
(结构体)实现部分面向对象特性,允许为结构体和其他自定义类型(包括基本类型的自定义扩展)定义方法,方法与函数类似,但需要指定接收者来表示调用该方法的实例。Go
语言使用组合而非继承来实现代码复用。在Go
中,通过嵌套结构体,可以在结构体中直接访问另一个嵌入结构体的字段和方法。这种机制允许灵活地复用和扩展功能,但Go
语言不支持传统的类继承层次结构。- 在
Go
中,结构体本身并不支持多态。实现多态的关键在于接口(interface
)。当一个结构体实现了接口中定义的所有方法时,它就自动实现了该接口。这样,结构体对象可以被赋值给接口变量,从而实现多态的行为。
Go
实现面向对象编程
在Go
语言中,封装、继承和多态的实现方式与传统面向对象语言有所不同。Go
没有类的概念,而是通过结构体(struct
)和接口(interface
)来实现这些特性。
封装
在Go
语言中,封装是通过结构体字段的首字母大小写来实现的。字段首字母大写表示公共的,外部可以访问;首字母小写则表示私有的,只能在包内访问。虽然我们可以将结构体字段设置为私有,并通过公开方法来实现封装,但在Go
中通常不会像Java
那样编写Setter
和Getter
方法,这里仅作示例:
type Person struct {
name string // 小写表示私有成员
age int
}
// 相当于Person结构体的构造函数
func NewPerson(name string, age int) *Person {
return &Person{name: name, age: age}
}
// Getter方法示例
func (p *Person) GetAge() int {
return p.age
}
func main() {
person := NewPerson("John", 25)
fmt.Println("Name:", person.name) // 这里会报错,因为name是私有的
fmt.Println("Age:", person.GetAge()) // 通过Getter方法访问私有字段
}
想看更多后端开发技术相关内容,可以关注我的技术笔记。其详细涵盖了
Go
语言的语法、框架和第三方库使用方法,以及数据库、DevOps
、中间件和计算机基础等相关知识。笔记中的所有内容均经过实际操作和验证,确保其准确性和实用性。
访问该网址查看详细内容:https://gitee.com/mundo-wang/technical-notes
继承
在Go
语言中,缺少类似于extends
关键字的继承机制。Go
采用的是组合(结构体嵌套)来实现代码重用:
type Animal struct {
name string
}
func (a *Animal) Speak() string {
return "Generic animal sound"
}
type Dog struct {
Animal // Dog 结构体嵌套Animal,实现了“继承”
breed string
}
func main() {
dog := &Dog{
Animal: Animal{name: "Buddy"},
breed: "Golden Retriever",
}
fmt.Println(dog.name) // Dog结构体的实例可以访问“继承”自Animal结构体的字段
fmt.Println(dog.Speak()) // Dog结构体的实例可以调用“继承”自Animal结构体的方法
}
同样,Dog
结构体可以重写Animal
结构体中定义的方法,如下所示:
func (d *Dog) Speak() string {
return "Generic dog sound"
}
这样,调用dog.Speak()
时,实际调用的是子结构体重写后的方法。
为了方便理解,这里我们把被嵌套的结构体称为父结构体,把包含嵌套结构体的结构体称为子结构体。
在上面代码中,可以理解为
Animal
为父结构体,Dog
为子结构体。
虽然组合在功能上类似于继承,但有一个显著的区别:Go
的组合不能实现多态。举例来说,如果我们在面向对象编程语言中定义一个Dog
类继承自Animal
类,那么一个Dog
对象可以被直接赋值给一个Animal
类型的变量。例如Java
代码如下:
public class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
Animal myAnimal = myDog; // Dog对象可以赋值给Animal类型的变量
myAnimal.makeSound();
}
}
然而,在Go
语言中,不能将子结构体的对象赋值给父结构体的变量:
var animal Animal = dog // cannot use dog (variable of type Dog) as Animal value in variable declaration
亦或是定义一个以父结构体类型为参数的函数:
func AnimalSpeak(a *Animal) {
a.Speak()
}
子结构体的实例无法作为参数传递给该函数:
AnimalSpeak(dog) // annot use dog (variable of type *Dog) as *Animal value in argument to AnimalSpeak
多态
我们上面讲到过,虽然在Go
语言中可以通过组合来实现类似继承的效果,但Go
并不支持传统的多态。
在Go
语言中,可以通过接口来实现多态。接口定义了一组方法,任何结构体只要实现了这些方法,就被视为实现了该接口。例如下面代码中,结构体Dog
和结构体Cat
实现了接口Animal
的所有方法,视为实现了该接口:
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
这种情况下,结构体的对象可以传递给接口类型变量:
var animal1 Animal = Dog{}
var animal2 Animal = Cat{}
我们定义一个以接口类型作为参数的函数:
func PerformSpeak(animal Animal) {
fmt.Println(animal.Speak())
}
结构体的实例可以作为函数参数传递到该函数中:
dog := Dog{}
cat := Cat{}
PerformSpeak(dog) // Woof!
PerformSpeak(cat) // Meow!