go的封装、继承与多态的使用

发布时间 2023-12-20 14:52:58作者: 善战者求之于势

一、封装

​ 在Go语言中,封装是一种将数据和操作数据的方法组织在一起的概念。封装的目的是隐藏数据的具体实现细节,只向外部提供有限的接口,以防止外部直接访问和修改内部数据,同时可以通过方法来操作这些数据。

1.1 公有封装

package main

import "fmt"

// Person 结构体表示一个人,公有字段可以直接访问
type Person struct {
	Name string
	Age  int
}

func main() {
	// 创建 Person 实例
	person := Person{Name: "John", Age: 30}

	// 直接访问公有字段
	fmt.Println("Name:", person.Name)
	fmt.Println("Age:", person.Age)
}

结果输出:

Name: John
Age: 30

1.2 私有封装

package main

import "fmt"

// person 结构体表示一个人,私有字段需要通过公有方法访问
type person struct {
	name string
	age  int
}

// NewPerson 是一个工厂函数,用于创建 person 实例
func NewPerson(name string, age int) *person {
	return &person{name: name, age: age}
}

// GetName 方法用于获取姓名
func (p *person) GetName() string {
	return p.name
}

// GetAge 方法用于获取年龄
func (p *person) GetAge() int {
	return p.age
}

func main() {
	// 使用工厂函数创建 person 实例
	p := NewPerson("John", 30)

	// 使用公有的方法获取私有字段的值
	fmt.Println("Name:", p.GetName())
	fmt.Println("Age:", p.GetAge())
}

结果输出:

Name: John
Age: 30

1.2.1 工厂函数解析

工厂函数是一种用于创建并返回结构体实例的函数。与普通的结构体实例创建方法不同,工厂函数可以进行一些额外的逻辑,如参数验证、初始化等,并且通常返回指向结构体实例的指针。

【工厂函数】

goCopy code// NewPerson 是一个工厂函数,用于创建 person 实例
func NewPerson(name string, age int) *person {
	return &person{name: name, age: age}
}

在这里,NewPerson 函数接受 nameage 作为参数,执行了结构体 person 的实例化操作,并返回指向该实例的指针。

【普通的结构体与函数绑定】

goCopy codetype Person struct {
	name string
	age  int
}

// 创建一个新的 Person 实例
func CreatePerson(name string, age int) Person {
	return Person{name: name, age: age}
}

在这里,我们直接在 CreatePerson 函数内部实例化 Person 结构体,并返回该结构体实例。这是一种直接创建结构体实例的方法,没有使用指针。

1.2.2 &与*指针使用描述

func (p *person) GetAge() int {
	return p.age
}

(p *person) 描述

  • (p *person) 是一个接收者(receiver)声明,出现在方法定义中,这个接收者声明指定了方法所属的类型,以及方法可以通过什么方式调用
  • (p *person) 表示这个方法属于 person 结构体类型
  • p 是参数名,表示在方法内部可以使用 p 这个名字引用调用该方法的结构体实例。
  • *person 表示这个方法属于 person 结构体的指针类型。

当调用一个指针接收者的方法时,Go 语言会隐式地将指向结构体的实例的地址传递给这个方法。你可以直接传递结构体实例的地址,而不需要显式地取地址(使用 & 操作符)。

例如,在下面的代码中,调用 p.GetAge() 方法时,你可以传递 p&p,Go 语言会在底层进行处理:

goCopy code
age := p.GetAge()  // 传递 p,不需要取地址

或者:

goCopy code
age := (&p).GetAge()  // 传递 &p,可以显式取地址

这两者是等效的。Go 语言会自动将 p 转换为 &p,以适应指针接收者的方法的调用。

1.3 深度封装

package main

import "fmt"

// person 结构体表示一个人,私有字段需要通过公有方法访问
type person struct {
	name string
	age  int
}

// NewPerson 是一个工厂函数,用于创建 person 实例
func NewPerson(name string, age int) *person {
	return &person{name: name, age: age}
}

// GetName 方法用于获取姓名
func (p *person) GetName() string {
	return p.name
}

// GetAge 方法用于获取年龄
func (p *person) GetAge() int {
	return p.age
}

// Student 结构体表示一个学生,嵌套了 person 结构体
type Student struct {
	person  // 嵌套 person 结构体
	studentID string
}

// NewStudent 是一个工厂函数,用于创建 Student 实例
func NewStudent(name string, age int, studentID string) *Student {
	return &Student{
		person:   person{name: name, age: age},
		studentID: studentID,
	}
}

// GetStudentID 方法用于获取学生ID
func (s *Student) GetStudentID() string {
	return s.studentID
}

func main() {
	// 使用工厂函数创建 Student 实例
	student := NewStudent("Alice", 20, "12345")

	// 使用公有的方法获取字段的值
	fmt.Println("Name:", student.GetName())
	fmt.Println("Age:", student.GetAge())
	fmt.Println("Student ID:", student.GetStudentID())
}

在这个示例中,Student 结构体嵌套了 person 结构体,通过这种方式,Student 类型可以访问 person 类型的公有方法和字段。这展示了深度封装的概念,其中一个结构体嵌套了另一个结构体。

二、继承与多态

  • ​ 在 Go 语言中并没有传统面向对象语言中的类和继承的概念。
  • ​ Go 语言通过组合和接口实现代码复用和多态。
  • ​ 可以通过嵌入其他结构体来实现类似继承的效果。这被称为组合。同时,通过接口,你可以实现多态性(多态(Polymorphism)是指通过统一的接口来实现对不同类型对象的统一操作)。

2.1 继承与多态案例

package main

import "fmt"

// Animal 接口定义了一个通用的动物行为
type Animal interface {
	Speak() string
}

// Dog 结构体代表了一个狗,嵌入了 Animal 接口(因为下面func (d Dog) Speak() string{}关系)
type Dog struct {
	Name string
}

// Speak 是 Dog 结构体实现的 Animal 接口的方法
func (d Dog) Speak() string {
	return "Woof!"
}

// Cat 结构体代表了一个猫,嵌入了 Animal 接口
type Cat struct {
	Name string
}

// Speak 是 Cat 结构体实现的 Animal 接口的方法
func (c Cat) Speak() string {
	return "Meow!"
}

func main() {
	// 使用 Dog 结构体
	dog := Dog{Name: "Buddy"}
	// 使用 Cat 结构体
	cat := Cat{Name: "Whiskers"}

	// 调用 Speak 方法,无论是狗还是猫,它们都符合 Animal 接口的定义
	fmt.Println(dog.Name, "says:", dog.Speak())
	fmt.Println(cat.Name, "says:", cat.Speak())
}

2.1.1 继承代码分析

通过定义Dog结构体

type Dog struct {
	Name string
}

与speak()方法进行绑定,func (d Dog) Speak() stringDog 结构体实现了 Animal 接口的 Speak 方法。这种方式被称为 接口方法的实现

func (d Dog) Speak() string {
	return "Woof!"
}

从而继承了Animal特征

type Animal interface {
	Speak() string
}

cat 解析如上一样的。

2.1.2 结构体实例化

	// 使用 Dog 结构体
	dog := Dog{Name: "Buddy"}

dog.Name 意思:

在dog := Dog{Name: "Buddy"}这行代码中,我们创建了一个Dog类型的实例,通过使用结构体字面量给 Name 字段赋值为 "Buddy"。然后,我们用 dog 变量来引用这个新创建的实例。因为 Name 字段是公共字段,所以我们可以通过 dog.Name 来获取并访问这个字段的值。这种方式是通过结构体实例的字段名来访问其属性。

dog.Speak()意思:

通过 Dog{Name: "Buddy"} 实例化后,得到的 dog 变量是 Dog 结构体类型的实例。由于 Speak 方法是与 Dog 结构体绑定的方法,因此我们可以通过这个实例直接调用 Speak 方法,如 dog.Speak()。这是 Go 语言中一种通过实例调用与其类型绑定的方法的常见方式。

2.1.3 多态代码分析

DogCat 结构体都实现了 Animal 接口,通过 Animal 接口的 Speak 方法,可以对不同类型的对象进行相同的操作。

具体来说,在以下代码中体现了多态代码:

goCopy code// Animal 接口定义了一个 Speak 方法
type Animal interface {
	Speak() string
}

// ...

func main() {
	// 使用 Dog 结构体
	dog := Dog{Name: "Buddy"}
	// 使用 Cat 结构体
	cat := Cat{Name: "Whiskers"}

	// 调用 Animal 接口中的 Speak 方法
	fmt.Println("Dog says:", dog.Speak())
	fmt.Println("Cat says:", cat.Speak())
}

在这个案例中,多态体现在 Animal 接口上。DogCat 结构体都实现了 Animal 接口,它们分别定义了自己的 Speak 方法。在 main 函数中,我们创建了 dogcat 两个实例,并通过 Animal 接口调用了它们的 Speak 方法。这种情况下,编译器会根据实际的类型来调用相应的 Speak 方法,即使是通过相同的接口进行调用。