文章目录
封装
基本介绍
基本介绍
- 封装(Encapsulation)是面向对象编程(OOP)中的一种重要概念,封装通过将数据和相关的方法组合在一起,形成一个称为类的抽象数据类型,只暴露必要的接口供外部使用。
- 封装可以隐藏数据的实际实现细节,外部只能通过公共(public)接口来访问和修改数据,使得代码更加模块化和结构化,同时可以防止不恰当的访问和操作,提高数据的安全性。
- 封装将相关的数据和方法组织在一起,形成了一个独立的单元,外部使用者只需关心公共接口,无需了解内部实现细节,简化了使用方式,提高了代码的可读性和可维护性。
- 封装使得内部实现可以独立于外部接口进行修改,如果内部实现发生了变化,只需要确保公共接口的兼容性,而不会影响使用该类的其他代码,提供了更好的灵活性和可扩展性。
封装的实现
封装的实现
- Go中的封装是通过命名约定和访问控制来实现的,而不像一些其他面向对象语言那样使用访问修饰符(如public、private、protected),因此开发者需要自觉遵守约定来保持封装的效果。
- Go中通过结构体将相关的字段和方法组合在一起,并通过首字母大小写来控制其可访问性。结构体中的字段和方法使用大写字母开头表示公共的(可导出的),可以被其他包访问,而使用小写字母开头表示私有的(不可导出的),只能在当前包内使用。
- Go中的封装更加宽泛,其封装的基本单元不是结构体而是包(package),包内的所有标识符(变量、函数、结构体、方法等)都通过首字母大小写来控制其可访问性
封装案例如下:
package model
import"fmt"type Student struct{
name string
age int
gender string}// 访问name字段func(stu Student)GetName()string{return stu.name
}func(stu *Student)SetName(name string){
stu.name = name
}// 访问age字段func(stu Student)GetAge()int{return stu.age
}func(stu *Student)SetAge(age int){
stu.age = age
}// 访问gender字段func(stu Student)GetGender()string{return stu.gender
}func(stu *Student)SetGender(gender string){
stu.gender = gender
}// Student的其他方法func(stu Student)Print(){
fmt.Printf("student info: <name: %s, age: %d, gender: %s>\n",
stu.name, stu.age, stu.gender)}
使用上述包内结构体的案例如下:
package main
import("go_code/OOP2/Encapsulate/model")funcmain(){// var stu = model.Student{"Alice", 12, "女"} // 隐式赋值var stu model.Student
stu.SetName("Alice")
stu.SetAge(12)
stu.SetGender("女")
stu.Print()// student info: <name: Alice, age: 12, gender: 女>}
注意: Go中无法对结构体中不可导出的字段进行隐式赋值,可以通过给结构体绑定对应的setter和getter方法对不可导出的字段进行访问和赋值。
工厂函数
工厂函数
如果结构体类型的首字母小写(不可导出),那么其他包将无法直接使用该结构体类型来创建实例,这时可以在包内提供对应可导出的工厂函数来创建实例并返回。如下:
package model
import"fmt"type student struct{
name string
age int
gender string}// 工厂函数funcNewStudent(name string, age int, gender string)*student {return&student{
name: name,
age: age,
gender: gender,}}func(stu student)Print(){
fmt.Printf("student info: <name: %s, age: %d, gender: %s>\n",
stu.name, stu.age, stu.gender)}
其他包通过调用包中可导出的工厂函数,即可创建对应不可导出的结构体实例。如下:
package main
import("go_code/OOP2/Encapsulate/model")funcmain(){var stu = model.NewStudent("Alice",12,"女")
stu.Print()// student info: <name: Alice, age: 12, gender: 女>}
继承
基本介绍
基本介绍
- 继承是面向对象编程中的一个重要概念,其允许一个类(子类/派生类)继承另一个类(父类/基类)的属性和方法,子类继承父类后可以直接访问和使用父类中字段和方法,同时可以添加自己的字段和方法。
- 继承的主要优势在于代码复用,继承可以在不重复编写相似代码的情况下扩展现有的类,使代码更具可维护性和可扩展性。
继承示意图如下:
继承的实现
继承的实现
- Go中的继承是通过嵌套匿名结构体的方式来实现的,如果一个结构体中嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体中的字段和方法,从而实现了继承的效果。
继承案例如下:
package main
import("fmt")// 基类type Person struct{
Name string
Age int}func(per Person)PrintInfo(){
fmt.Printf("name = %s, age = %d\n", per.Name, per.Age)}// 派生类type Student struct{
Person // 嵌套匿名结构体
StudentId int}func(stu Student)Study(){
fmt.Printf("student %d is studying...\n", stu.StudentId)}// 派生类type Teacher struct{*Person // 嵌套匿名结构体指针
TeacherId int}func(tch Teacher)Teach(){
fmt.Printf("teacher %d is teaching...\n", tch.TeacherId)}funcmain(){var stu = Student{Person{"Alice",12},100}
stu.PrintInfo()// name = Alice, age = 12
stu.Study()// student 100 is studying...var tch = Teacher{&Person{"Bob",22},200}
tch.PrintInfo()// name = Bob, age = 22
tch.Teach()// teacher 200 is teaching...}
说明一下:
- 在嵌套匿名结构体时,可以通过
Type
的方式嵌套匿名结构体,也可以通过*Type
的方式嵌套匿名结构体指针。 - 在创建结构体变量时,如果要通过字段名的方式初始化结构体字段,那么匿名结构体的字段名由匿名结构体的类型名充当。
- 在结构体中嵌套匿名结构体后,可以通过结构体实例访问匿名结构体的字段和方法,但在访问时仍然遵循Go的命名约定和访问控制。如果被嵌套的匿名结构体的定义在其他包,那么通过结构体实例只能访问匿名结构体可导出的字段和方法。
- 结构体中嵌入的匿名字段也可以是基本数据类型,在访问结构体中的匿名基本数据类型字段时,以对应基本数据类型的类型名作为其字段名。比如结构体中嵌入了一个匿名int字段,则通过
结构体变量名.int
的方式对其进行访问。
组合
在结构体中嵌套有名结构体属于组合关系,在访问组合的结构体字段和方法时,必须带上结构体的字段名。如下:
package main
import("fmt")// 车轮type Wheel struct{
Color string
price int}// 自行车type Bicycle struct{
FrontWheel Wheel // 组合
RearWheel Wheel // 组合// ...}func(bc Bicycle)Print(){
fmt.Printf("前轮<color:%s, price:%d元> 后轮<color:%s, price:%d元>\n",
bc.FrontWheel.Color, bc.FrontWheel.price, bc.RearWheel.Color, bc.RearWheel.price)}funcmain(){var bc = Bicycle{
FrontWheel: Wheel{"black",100},
RearWheel: Wheel{"blue",200},}
bc.Print()// 前轮<color:black, price:100元> 后轮<color:blue, price:200元>}
字段和方法访问细节
字段和方法访问细节
结构体字段和方法的访问流程:
- 先查看当前结构体类型中是否有对应的字段或方法,如果有则访问。
- 再查看结构体中嵌入的匿名结构体中是否有对应的字段或方法,如果有则访问。
- 继续查找更深层次嵌入的匿名结构体中是否有对应的字段或方法,如果有则访问,否则产生报错。
案例如下:
package main
import("fmt")// 基类type Person struct{
Name string
Age int}func(per Person)PrintInfo(){
fmt.Printf("name = %s, age = %d\n", per.Name, per.Age)}// 派生类type Student struct{
Person
StudentId int}func(stu Student)Study(){
fmt.Printf("student %d is studying...\n", stu.StudentId)}func(stu Student)PrintInfo(){
fmt.Printf("name = %s, age = %d, id = %d\n", stu.Name, stu.Age, stu.StudentId)}funcmain(){var stu = Student{Person{"Alice",12},100}
fmt.Printf("name = %s\n", stu.Name)// name = Alice
stu.PrintInfo()// name = Alice, age = 12, id = 100}
代码说明:
- 在访问字段时,由于Student结构体中没有Name字段,因此
Student变量.Name
访问的是嵌套的匿名结构体Person中的Name字段。 - 在访问方法时,由于Student结构体有PrintInfo方法,因此
Student变量.PrintInfo()
访问的是Student结构体的PrintInfo方法,而不是Person结构体的PrintInfo方法。
多继承
多继承
多继承指的是一个结构体中嵌套了多个匿名结构体,如果嵌套的多个匿名结构体中有相同的字段名或方法名,那么在访问时需要通过匿名结构体的类型名进行指明访问。如下:
package main
import("fmt")type Cat struct{
Name string
Age int}type Dog struct{
Name string
Age int}// 多继承type Pet struct{
Cat
Dog
}funcmain(){var pet = Pet{
Cat: Cat{
Name:"小猫",
Age:2,},
Dog: Dog{
Name:"小狗",
Age:3,},}
fmt.Printf("cat name = %s\n", pet.Cat.Name)// cat name = 小猫
fmt.Printf("dog name = %s\n", pet.Dog.Name)// dog name = 小狗}
版权归原作者 2021dragon 所有, 如有侵权,请联系我们删除。