Go语言面向对象及方法
Go不是面向对象语言,但是却可以借助结构体模拟面向对象的特点。结构体在Go语言中的地位等同其他语言中的class。
GO中这样的设计极大地降低了耦合,包括后面所说的接口,基本都是非侵入式的。
Go不是面向对象语言,但是却可以借助结构体模拟面向对象的特点。结构体在Go语言中的地位等同其他语言中的class。
GO中这样的设计极大地降低了耦合,包括后面所说的接口,基本都是非侵入式的。
一、面向对象
面向对象的基本特征是:继承、封装、多态
Go中的结构体是一种组合式的结构,对于属性的继承可以使用匿名字段的方式。而封装不必多说,对于多态的支持稍后详述。
结构体的匿名字段
结构体中的字段如果没有名字,称为匿名字段。原理是字段名和类型一样的话可以省略不写。
func main() {
a := animal{"Dog", "3", "汪汪"}
fmt.Println(a)
fmt.Printf("匿名字段的%s",a.string)
}
type animal struct {
name string
age string
//匿名字段
string
}
..........................................
//{Dog 3 汪汪}
//匿名字段的汪汪
我们增加一个匿名字段,类型是一个结构体。也就是结构体嵌套了。之前说过访问嵌套的结构体的字段,可以使用
.连续访问。如果嵌套的结构体是匿名的,那么可以直接访问其字段。
func main() {
a := animal{"Dog", "3", "汪汪",Dog{"旺财","female"}}
fmt.Println(a)
fmt.Printf("匿名字段的%s
",a.string)
fmt.Printf("匿名字段的%s
",a.Dog)
fmt.Printf("匿名字段的%s
",a.Dog.sex)
//省略匿名字段名
fmt.Printf("匿名字段的%s
",a.sex)
fmt.Printf("匿名字段的%s
",a.Dog.name)
//名字冲突,指向父结构体
fmt.Printf("匿名字段的%s
",a.name)
}
type animal struct {
name string
age string
//匿名字段
string
Dog
}
type Dog struct {
name string
sex string
}
{Dog 3 汪汪 {旺财 female}}
匿名字段的汪汪
匿名字段的{旺财 female}
匿名字段的female
匿名字段的female
匿名字段的旺财
匿名字段的Dog
继承-(提升字段)
上面的Dog匿名字段变成了提升字段,a.Dog.sex和a.sex都可以访问,这就是模拟的继承。
再写一个Demo:
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
//提升字段
fmt.Printf("学校%s老师的年龄是%d
",school.Teacher.teaName,school.Teacher.teaAge)
fmt.Printf("学校%s老师的年龄是%d
",school.teaName,school.teaAge)
fmt.Printf("学校%s同学的年龄是%d
",school.Student.stuName,school.stuAge)
fmt.Printf("学校%s同学的性别是%s
",school.Student.stuName,school.gender)
}
type School struct {
name string
address string
Teacher
Student
}
type Teacher struct {
teaName string
teaAge int
}
type Student struct {
stuName string
stuAge int
gender string
}
学校张三老师的年龄是23
学校张三老师的年龄是23
学校小明同学的年龄是13
学校小明同学的性别是male
可见性
如果结构体名,字段名等是以小写字母开头,那么代表着private,其他包不可访问,如果大写,代表着public,其他包可访问。
二、方法
结构体就像是类的一种简化形式,那么类的方法在哪里呢?在Go语言中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收器(receiver)上的一个函数,接收器是某种类型的变量。因此方法是一种特殊类型的函数。
方法需要有一个接收器,才可以被执行。
方法定义
方法的定义方式和函数基本相同,只是需要在前方加一个接收器
func (t receiver_type) method_name() [return] {
方法体...
}
以上面的Demo为例,增加一个方法,然后在主函数中执行它。
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
school.display()
}
//方法,接收器类型为School,无返回值
func (s School) display() {
fmt.Println("学校的信息:",s)
}
//学校的信息: {友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
我们可以看到Go语言的方法也是非侵入式的,没有显式的声明是哪个语言的方法,一切由接收器决定。
多态
我们再写一个func (t Teacher) display()方法,这是个同名方法,接收器是Teacher类型,同样可以执行。
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
school.display()
t.display()
}
func (s School) display() {
fmt.Println("学校的信息:",s)
}
func (t Teacher) display() {
fmt.Println("学校的信息:",t)
}
//学校的信息: {友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
//学校的信息: {张三 23}
我们把接收器为子类School的方法删除,只留下一个接收器为父类Teacher的方法,主函数不变。
此时的子类会先寻找接收器为本类型的方法(称为方法重写),如果没有,会寻找接收器为父类的方法,并执行。
由于接收器是父类,父类没有子类的部分字段,所以只会执行父类的内容。
这也是多态的一种体现,在其他语言中称为重载
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
//子类调用父类方法
school.display()
t.display()
}
func (t Teacher) display() {
fmt.Println("学校的信息:",t)
}
//学校的信息: {张三 23}
//学校的信息: {张三 23}
指针接收器与非指针接收器
接收器根据接收器的类型可以分为指针接收器、非指针接收器。两种接收器在使用时会产生不同的效果。根据效果的不同,两种接收器会被用于不同性能和功能要求的代码中。
如果写出以下两种方法的。接收器类型一个是指针,一个非指针。
func (s School) display() {
fmt.Println("学校的信息:",s)
}
func (s *School) display() {
fmt.Println("学校的信息:",s)
}
首先这样写是错的,这并不是多态性,因为这就是一个方法。
对于方法来说,接收器类型指针和非指针,都可以调用方法。两者可以互相转换。
非指针--指针
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
school.display()
}
func (s *School) display() {
fmt.Println("学校的信息:",s)
}
//学校的信息: &{友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
指针--指针
func main() {
school:=new(School)
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school=&School{"友谊小学","安徽省杭埠镇",t,s}
school.display()
}
func (s *School) display() {
fmt.Println("学校的信息:",s)
}
////学校的信息: &{友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
指针--非指针
func main() {
school:=new(School)
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school=&School{"友谊小学","安徽省杭埠镇",t,s}
school.display()
}
func (s School) display() {
fmt.Println("学校的信息:",s)
}
//学校的信息: {友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
指针接收器
指针类型的接收器由一个结构体的指针组成,更接近于面向对象中的 this 或者 self。
由于指针的特性,调用方法时,修改接收器指针的任意成员变量,在方法结束后,修改都是有效的。
我们测试一下:增加一个变量a,对不同类型进行组合。
控制变量:以下每次只对接收器、接收器类型中的一个进行改变。
指针--非指针接收器
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=&School{"友谊小学","安徽省杭埠镇",t,s}
a:=school
school.display()
fmt.Println(school.name)
fmt.Println(a.name)
fmt.Println("学校信息:",a)
}
func (s School) display() {
s.name="爱心小学"
fmt.Println("学校的信息:",s)
}
学校的信息: {爱心小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
友谊小学
友谊小学
学校信息: &{友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
指针--指针接收器
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=&School{"友谊小学","安徽省杭埠镇",t,s}
a:=school
school.display()
fmt.Println(school.name)
fmt.Println(a.name)
fmt.Println("学校信息:",a)
}
func (s *School) display() {
s.name="爱心小学"
fmt.Println("学校的信息:",s)
}
学校的信息: &{爱心小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
爱心小学
爱心小学
学校信息: &{爱心小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
非指针--非指针接收器
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
a:=school
school.display()
fmt.Println(school.name)
fmt.Println(a.name)
fmt.Println("学校信息:",a)
}
func (s *School) display() {
s.name="爱心小学"
fmt.Println("学校的信息:",s)
}
学校的信息: {爱心小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
友谊小学
友谊小学
学校信息: {友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
非指针--指针接收器
func main() {
t:=Teacher{"张三",23}
s:=Student{"小明",13,"male"}
school:=School{"友谊小学","安徽省杭埠镇",t,s}
a:=school
school.display()
fmt.Println(school.name)
fmt.Println(a.name)
fmt.Println("学校信息:",a)
}
func (s *School) display() {
s.name="爱心小学"
fmt.Println("学校的信息:",s)
}
学校的信息: &{爱心小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
爱心小学
友谊小学
学校信息: {友谊小学 安徽省杭埠镇 {张三 23} {小明 13 male}}
已经可以得出结论了,接收器类型指针和非指针之间在方法中可以互相转换。
如果方法的接收器类型为指针,那么方法中改变的值,方法结束后仍然有效。
如果方法的接收器类型为非指针,那么方法中改变的值,方法结束后无效。
方法的调用者类型不会被方法接收器类型修改。