目录
一、封装
在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
函数接受 name
和 age
作为参数,执行了结构体 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() string
是 Dog
结构体实现了 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 多态代码分析
Dog
和 Cat
结构体都实现了 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
接口上。Dog
和 Cat
结构体都实现了 Animal
接口,它们分别定义了自己的 Speak
方法。在 main
函数中,我们创建了 dog
和 cat
两个实例,并通过 Animal
接口调用了它们的 Speak
方法。这种情况下,编译器会根据实际的类型来调用相应的 Speak
方法,即使是通过相同的接口进行调用。