Golang的面向对象编程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
Go语言的面向对象之所以与C++,Java以及(较小程度上的)Python这些语言不同,是因为它不支持继承,仅支持聚合(也叫组合)和嵌入。接下来我们一起来学习一下Go语言的面向对象编程吧。
一.面向对象编程思想
面向对象编程刚流行的时候,继承是它首先被吹捧的最大优点之一。但是历经几十载的实践之后,事实证明该特性也有些明显的缺点,特别是当用于维护大系统时。Go语言建议的是面向接口编程。 常见的编程方式: 面向过程(面向函数式编程): 典型代表: C语言 优点: 流程清晰,代码易读。 缺点: 耦合度太高,不利于项目迭代。 面向对象编程: 典型代表: C++,Java,Python,Golang等。 优点: 解耦。 缺点: 代码抽象度过高,不易读。 面向对象三要素: 封装 组装:将数据和操作组装到一起。 隐藏数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解其中的机动原理。 继承 多复用,继承来的就不用自己写了。 多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性。 多态 面向对象编程最灵活的地方,动态绑定。 与其它大部分使用聚合和继承的面向对象语言不同的是,Go语言只支持聚合(也叫做组合)和嵌入。
二.结构体的定义及初始化
package main import ( "fmt" ) type Person struct { Name string Age int Gender string } type Student struct { Person //通过匿名组合的方式嵌入了Person的属性。 Score float64 } type Teacher struct { Person //通过匿名组合的方式嵌入了Person的属性。 Course string } type Schoolmaster struct { Person //通过匿名组合的方式嵌入了Person的属性。 CarBrand string } func main() { /** 第一种初始化方式:先定义后赋值 */ s1 := Student{} s1.Name = "Jason Yin" fmt.Println(s1) fmt.Printf("%+v ", s1) //"+v表示打印结构体的各个字段" /** 第二种初始化方式:直接初始化 */ s2 := Teacher{Person{"尹正杰", 18, "boy"}, "Go并发编程"} fmt.Println(s2) fmt.Printf("%+v ", s2) /** 第三种赋值方式:初始化赋值部分字段 */ s3 := Schoolmaster{CarBrand: "丰田", Person: Person{Name: "JasonYin最强王者"}} fmt.Println(s3) fmt.Printf("%+v ", s3) }
三.结构体的属性继承及变量赋值
package main import ( "fmt" ) type Animal struct { Age int } type People struct { Animal Name string Age int Gender string } type IdentityCard struct { IdCardNO int Nationality string Address string Age int } /* 此时的Students以及是多重继承 */ type Students struct { IdentityCard People //多层继承 Age int Score int } func main() { /** 如果子类和父类存在同名的属性,那么以就近原则为准 */ s1 := Students{ Score: 150, IdentityCard: IdentityCard{ IdCardNO: 110105199003072872, Nationality: "中华人民共和国", Address: "北京市朝阳区望京SOHO", Age: 8, }, People: People{Name: "Jason Yin", Age: 18, Animal: Animal{Age: 20}}, Age: 27, } /** 如果子类和父类存在同名的属性(如果父类还继承了其它类型,我们称之为多层继承),那么就以就近原则为准; 但是如果一个子类如果继承自多个父类(我们称之为多重继承),且每个字段中都有相同的字段,此时我们无法直接在子类调用该属性; */ fmt.Printf("学生的年龄是:[%d] ", s1.Age) s1.Age = 21 fmt.Printf("学生的年龄是:[%d] ", s1.Age) //给People类的Age赋值 fmt.Printf("People的年龄是:[%d] ", s1.People.Age) s1.People.Age = 5000 fmt.Printf("People的年龄是:[%d] ", s1.People.Age) //给IdentityCard类的Age赋值 fmt.Printf("IdentityCard的年龄是:[%d] ", s1.IdentityCard.Age) s1.IdentityCard.Age = 80 fmt.Printf("IdentityCard的年龄是:[%d] ", s1.IdentityCard.Age) }
四.匿名组合对象指针使用案例
package main import ( "fmt" "time" ) type Vehicle struct { Brand string Wheel byte } type Car struct { Vehicle Colour string } type Driver struct { *Car DrivingTime time.Time } func main() { /** 对象指针匿名组合的第一种初始化方式: 定义时直接初始化赋值。 */ d1 := Driver{&Car{ Vehicle: Vehicle{ Brand: "丰田", Wheel: 4, }, Colour: "红色", }, time.Now(), } //打印结构体的详细信息,注意观察指针对象 fmt.Printf("%+v ", d1) //我们可以直接调用对象的属性 fmt.Printf("品牌:%s,颜色:%s ", d1.Brand, d1.Colour) fmt.Printf("驾驶时间:%+v ", d1.DrivingTime) time.Sleep(1000000000 * 3) /** 对象指针匿名组合的第二种初始化方式: 先声明,再赋值 温馨提示: 遇到指针的情况一定要避免空(nil)指针,未初始化的指针默认值是nil,可以考虑使用new函数解决。 */ var d2 Driver /** 由于Driver结构体中有一个对象指针匿名组合Car,因此我们需要使用new函数申请空间。 */ d2.Car = new(Car) d2.Brand = "奔驰" d2.Colour = "黄色" d2.DrivingTime = time.Now() fmt.Printf("%+v ", d2) fmt.Printf("品牌:%s,颜色:%s ", d2.Brand, d2.Colour) fmt.Printf("驾驶时间:%+v ", d1.DrivingTime) }
五.结构体成员方法案例
package main import ( "fmt" ) //定义一个结构体 type Lecturer struct { Name string Age uint8 } //我们为Lecturer结构体封装Init成员方法 func (l *Lecturer) Init() { l.Name = "Jason Yin" l.Age = 20 } /** 我为Lecturer结构体起一个别名 我们可以为Instructor类型添加成员方法, 通过别名和成员方法为原有类型赋值新的操作 */ type Instructor Lecturer /** 温馨提示: (1)我们为一个结构体创建成员方法时,如果成员方法有接收者,需要考虑以下两种情况: 1>.如果这个接收者是对象的时候,是值传递; 2>.如果这个接收者是对象指针,是引用传递; (2)只要函数接收者不同,哪怕函数名称相同,也不算同一个函数哟; (3)不管接收者变量名称是否相同,只要类型一致(包括对象和对象指针),那么我们就认为接收者是相同的,这时候不允许出现相同名称函数; (4)给指针添加方法的时候,不允许给指针类型添加操作(因为Go语言中指针类型是只读的); */ func (i *Instructor) Init() { i.Name = "尹正杰" i.Age = 18 } func main() { var ( l Lecturer i Instructor ) //可以使用对象调用成员方法 i.Init() fmt.Printf("%+v ", i) //可以用对象指针调用成员方法 (&l).Init() fmt.Printf("%+v ", l) }
六.结构体的方法继承和重写
package main import ( "fmt" ) type Father struct { Name string Age int } func (f *Father) Init() { f.Name = "成龙" f.Age = 66 } //定义父类的Eat成员方法 func (f *Father) Eat() { fmt.Println("Jackie Chan is eating...") } //重写父类的Eat成员方法 func (s *Son) Eat() { fmt.Println("FangZuming is eating...") } //我们让Son类继承Father父类 type Son struct { Father //匿名组合能够继承父类的属性和方法 Score int } func main() { var s Son s.Init() fmt.Printf("%+v ", s) s.Eat() s.Name = "房祖名" s.Age = 38 s.Score = 100 fmt.Printf("%+v ", s) }
七.方法值和方法表达式
package main import ( "fmt" ) /** 定义函数,函数的返回值是函数类型 */ func CallBack(a int) func(b int) int { return func(c int) int { fmt.Println("调用了CallBack这个回调函数...") return a + c } } type BigData struct { Name string } func (this *BigData) Init() { this.Name = "Hadoop" } func (this *BigData) PrinfInfo() { fmt.Printf("%v是大数据生态圈的基石。 ", this.Name) } func (this BigData) SetInfoValue() { fmt.Printf("SetInfoValue : %p,%v ", &this, this) } func (this *BigData) SetInfoPointer() { fmt.Printf("SetInfoPointer : %p,%v ", this, this) } func main() { /** 调用回调函数的返回值为函数类型 */ result := CallBack(10) fmt.Printf("result的类型是:[%T],result的值是:[%v] ", result, result) res1 := result(20) //我们对返回的函数再次进行调用 fmt.Printf("res1的类型是:[%T],res1的值是:[%d] ", res1, res1) var hadoop BigData hadoop.Init() //调用hadoop的初始化方法 info := hadoop.PrinfInfo //我们可以声明一个函数变量info,我们称之为方法表达式 /** 我们对info函数变量进行调用,这样可以起到隐藏调用者hadoop对象的效果哟~(类似于回调函数的调用效果) 方法值可以隐藏调用者,我们称为隐式调用。 */ info() /** 方法表达式可以显示调用调用,必须传递方法调用者对象,在实际开发中很少使用这种方式,我们了解即可。 */ elk := BigData{"Elastic Stack"} fmt.Printf("main:%p,%v ", &elk, elk) s1 := (*BigData).SetInfoPointer s1(&elk) //显示把接收者传递过去 s2 := (BigData).SetInfoValue s2(elk) //显示把接收者传递过去 }
八.面向接口编程(多态案例)
博主推荐阅读: https://www.cnblogs.com/yinzhengjie2020/p/12542435.html