概述
go的面向对象和其它的面向语言并不太相同。首先,go是没有类的概念的,在go中数据是数据,方法是方法,二者比较独立。其次go对继承的实现更像是“组合”。go的接口是鸭子类型:只要你像个鸭子,那么我就认为你是个鸭子,而不需要显式的实现接口,像java的implements XXX在go是不存在的。
面向对象的三个特征:继承、封装、多态 go都可以实现。(多态需要基于接口实现)
基础语法
type User struct {
name string
}
func (u User) introduceMe() {
fmt.Println("hello, my name is " + u.name)
}
u := User{"momo"}
u.introduceMe()
比较结构体会进行值比较。比较指针则是地址比较,因此u1=u2但是 &u1 != &u2
u1 := User{"momo"}
u2 := User{"momo"}
fmt.Println(u1 == u2)
fmt.Println(&u1 == &u2)
go 的方法是独立的。因此可以通过这种方式调用:
var f func()
f = u.introduceMe
f()
f2 := (User).introduceMe
f2(u)
这种写法和java的方法引用有些相似。但是go没有java那样的历史包袱,不需要通过接口方法来适配,写法更加的优雅方便
继承
通过将目标类匿名引入结构体即可实现继承:
// Admin 匿名引入实现继承
type Admin struct {
User
}
fmt.Println(admin.name)
admin.introduceMe()
fmt.Println(admin.User.name)
admin.User.introduceMe()
go实现了语法糖,可以直接调用内部匿名结构体的方法、属性,通过这种方式我们可以实现继承。
封装
在go语言的结构体中,大写字母开头的属性是包外可用的,小写字母开头的属性是只有包内可用的。相比java,没有protected的概念。java的私有变量,大部分情况还需要写getter、setter方法去暴露出来,这其实造成了很多的代码冗余,其实大可不必。
go的包内声明的变量、结构体、类型、函数等都是一样的规则,大写字母开头则对外可见,反之对外不可见。
接口和多态
// Animal 动物接口
type Animal interface {
// Eat 吃食物
Eat(food string)
// Run 跑
Run()
}
type Chicken struct {
}
func (c Chicken) Eat(food string) {
fmt.Println("鸡吃 " + food)
}
func (c Chicken) Run() {
fmt.Println("鸡在跑")
}
type Dog struct {
}
func (d Dog) Eat(food string) {
fmt.Println("狗吃 " + food)
}
func (d Dog) Run() {
fmt.Println("狗在跑")
}
var animal Animal
animal = Chicken{}
animal.Eat("鸡饲料")
animal.Run()
dog := Dog{}
animal = dog
animal.Eat("骨头")
animal.Run()
上例实现了动物接口,动物都会吃、跑,只要实现了吃和跑,我们就认为其是动物,因此鸡是动物,狗也是动物,因为它们都会吃和跑。
打印结果:
鸡吃 鸡饲料
鸡在跑
狗吃 骨头
狗在跑
go如何多继承?
在java中,一个对象只能继承自一个类,但是可以实现多个接口。
在go中可以通过引入多个匿名结构体以实现多继承,至于实现多个接口,只需要把这些接口的方法都实现即可。
下例分别实现了多继承和实现多个接口,鸟实现了飞和叫的接口。鸭子继承了鸟和卵生动物。因此鸭子也拥有了飞、叫、下蛋的行为。
type Flyer interface {
Fly()
}
type Speaker interface {
Speak()
}
type Bird struct {
}
func (b Bird) Fly() {
fmt.Println("飞啊飞...")
}
func (b Bird) Speak() {
fmt.Println("鸟叫")
}
// Waterfowl 水禽
type Waterfowl interface {
// Swim 水禽都会游泳
Swim()
}
// Oviparous 卵生动物
type Oviparous struct {
}
func (o Oviparous) LayEggs() {
fmt.Println("下蛋了")
}
// Duck 鸭子继承了鸟和卵生动物
type Duck struct {
Bird
Oviparous
}
重载
go允许覆盖匿名结构体的方法以实现重载,例如上例中,鸭子的叫声应该是“嘎嘎嘎”:
func (d Duck) Speak() {
fmt.Println("鸭子叫:嘎嘎嘎")
}
对鸭子增加的Speak方法会覆盖鸟的Speak方法。
duck := new(Duck)
duck.Fly()
duck.LayEggs()
duck.Speak()
输出:
飞啊飞...
下蛋了
鸭子叫:嘎嘎嘎
抽象类
java中抽象类是实现了部分方法,但是部分方法没有实现的虚类,无法对齐进行实例化,但是可以进行继承。go 没有抽象类,但是可以部分继承某个结构的方法,再补充额外的方法以实现接口:
// Animal 动物接口
type Animal interface {
// Eat 吃食物
Eat(food string)
// Run 跑
Run()
}
type Eater struct {
}
func (Eater) Eat(food string) {
fmt.Println("吃" + food)
}
type Fox struct {
Eater
}
func (f Fox) Run() {
fmt.Println("狐狸跑啊跑")
}
狐狸继承了Eater吃的方法,又实现了Run跑的方法,因此狐狸是动物,可以将其赋值给动物接口类型的变量:
fox := Fox{}
var animal Animal
animal = fox
animal.Run()
animal.Eat("野兔")
结果:
狐狸跑啊跑
吃...东西:野兔
接口嵌套
嵌套得到的接口的使用与普通接口一样,只要实现包含的所有方法即可实现接口。
// Sayer 接口
type Sayer interface {
say()
}
// Mover 接口
type Mover interface {
move()
}
// Human 接口嵌套
type Human interface {
Sayer
Mover
}
空接口
在go中,interface{}代表空接口,任何类型都可以看作空接口。类似于java中的Object,是所有类的父类。
func testEmptyInterface(t interface{}) {
switch t.(type) {
case int:
fmt.Println("is int")
case float64:
fmt.Println("is float64")
case string:
fmt.Println("is string")
default:
fmt.Println("unknown")
}
}
// 空接口
testEmptyInterface(1)
testEmptyInterface(1.23232323)
testEmptyInterface("adsadsad")
testEmptyInterface(false)
结果:
is int
is float64
is string
unknown
需要注意的
一个对象,无论其是指针还是实例,都可以引用指针方法和实例方法:
type Student struct{}
func (stu *Student) Speak(name string) (talk string) {
talk = "您好," + name
return
}
func (stu Student) Study() {
fmt.Println("我在学习")
}
下边分别用指针和实例访问方法都是可以的:
stu1 := new(Student)
stu1.Speak("momo")
stu1.Study()
stu2 := Student{}
stu2.Speak("momo")
stu2.Study()
但是对象如果是通过指针实现的接口,例如Student通过指针实现的这个接口:
type People interface {
Speak(string) string
}
// student 的speak只作用于指针,因此下例编译不通过
var peo People = Student{}
fmt.Println(peo.Speak("momo"))
将会语法报错:Cannot use 'Student{}' (type Student) as the type People Type does not implement 'People' as the 'Speak' method has a pointer receiver
而使用指针则是可以的:
var peo People = &Student{}
fmt.Println(peo.Speak("momo"))
面向对象的一大滥用就是过分抽象,千万不要为了抽象而抽象。过度抽象会导致理解代码变得困难。反而适得其反。当可复用的逻辑不多时就没有必要使用接口去做抽象。
版权声明
本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途
评论区#
还没有评论哦,期待您的评论!
引用发言