golang学习笔记21-面向对象(三):封装与继承【重要】
本节也是GO核心部分,很重要。
注:本人已有C,C++,Python基础,只写本人认为的重点。由于导包语句已经在19讲(笔记19:面向对象的引入)展示过了,所以这里就不展示了。
一、封装
【1】什么是封装:
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部程序的其它包只有通过被授权的操作方法,才能对字段进行操作。
【2】封装的好处:
1)隐藏实现细节
2)提可以对数据进行验证,保证安全合理
【3】Golang中如何实现封装:
1)建议将结构体、字段(属性)的首字母小写(其它包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格)
2)给结构体所在包提供一个工厂模式的函数,首字母大写(类似一个构造函数)
3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值func (var结构体类型名)SetXxx(参数列表){//加入数据验证的业务逻辑var.Age =参数
}
4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值func (var结构体类型名)GetXxx()(返回值列表){
return var.字段;
}
示例:
在utils中定义结构体和方法:
type person struct {
Name string
age int //其他包不能直接访问
}
//相当于构造器
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
func (p *person) SetAge(age int) {
if age < 0 || age > 150 {
fmt.Println("请输入合理的年龄!")
return
}
p.age = age
}
func (p *person) GetAge() int {
return p.age
}
然后在main中调用:
func main() {
// 使用构造器创建一个新的 person 实例
p := utils.NewPerson("李华")
// 设置年龄
p.SetAge(30)
// 获取年龄并打印
fmt.Printf("姓名: %s, 年龄: %d\n", p.Name, p.GetAge())
// 尝试设置不合理的年龄
p.SetAge(200) // 输出: 请输入合理的年龄!
// 再次获取年龄并打印
fmt.Printf("姓名: %s, 年龄: %d\n", p.Name, p.GetAge())
}
二、继承
继承的概念和C++一样,就不做赘述了,但GO其实是弱化了这个概念,没有专门的机制来实现继承,具体来说,没有父类子类的概念,没有继承关键字,但可以通过匿名结构体实现。首先,在utils中定义结构体和方法:
type Animal struct {
Age int
Weight float64
}
func (a *Animal) ShowInfo() {
fmt.Println("年龄=", a.Age, "体重=", a.Weight)
}
func (a *Animal) Shout() {
fmt.Println("我是动物,我会喊叫")
}
//用Animal是为了提高复用性,显然Cat是Animal的子类
type Cat struct {
Animal
}
func (c *Cat) Scratch() {
fmt.Println("我是小猫,我可以挠人")
}
这里cat中的animal显然就是匿名结构体,即没有名字,只有类型的结构体,属于cat的父类。
定义好子类和父类后,在main中调用:
func main() {
c := &utils.Cat{}
//方式1
c.Animal = utils.Animal{Age: 3, Weight: 10.6}
// 方式2:c.Animal.Age, c.Animal.Weight = 3, 10.6
c.Animal.ShowInfo()
c.Animal.Shout()
c.Scratch()
}
一些细节:
【1】结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。读者可自行尝试,这里不做赘述。
【2】匿名结构体字段访问可以简化。例:
func main() {
c := &utils.Cat{}
c.Age, c.Weight = 3, 10.6
c.ShowInfo()
c.Shout()
c.Scratch()
}
上述程序输出和通过animal访问是一样的,这证明了子类完全继承了父类的方法和字段,这样使用起来就方便很多。
【3】当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。这句话说白了就是子类和父类变量冲突,比如cat也有一个Age,优先用子类的,如果仍然想用父类的,那么字段访问就不能简化,要显示调用父类。读者可自行尝试,这里不做赘述。
【4】 Golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。多重继承的示例如下:
type A struct {
Aa int
Ab string
}
type B struct {
Bc int
Bd string
Aa int
}
type C struct {
A
B
}
C的创建语句是c:=C{A{},B{}}
显然,C有两个父类,更多测试代码就不写了。
【5】如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。比如上一点的A和B中有相同字段,那么和第三点一样,显示调用父类即可。
【6】结构体的匿名字段可以是基本数据类型。也就是说,可以在子类里面写int,float等基本数据类型,调用时就类似c.int,c.float64,…,这个了解即可。
【7】嵌入匿名结构体的指针也是可以的。如果嵌入指针,比如cat里面嵌入*animal
,那么创建cat时,要在animal前加取地址符&(与号),使用时用*解引用即可。
【8】结构体的字段可以是结构体类型的(组合模式)。这个其实就是二重结构体,除了访问时还可以再“点”一下,和其他类型没什么区别,当然,这不是继承。示例:
utils内的定义:
type D struct {
Da int
Db string
Dc B //组合模式
}
main中的调用:
func main() {
d := utils.D{10, "ooo", utils.B{66, "ppp", 99}}
fmt.Println(d)
fmt.Println(d.Dc.Bd)
}
程序输出为:
{10 ooo {66 ppp 99}}
ppp