-
博文分类专栏
- Jquery基础教程
-
- 文章:(15)篇
- 阅读:46569
- shell命令
-
- 文章:(42)篇
- 阅读:154247
- Git教程
-
- 文章:(36)篇
- 阅读:234885
- leetCode刷题
-
- 文章:(76)篇
- 阅读:131875
-
go语言中是如何实现面对对象的继承重载多态2018-05-05 17:15 阅读(11332) 评论(0)
一、简介
在go语言中是没有类的概念,不像java或是php,提供class关键字来定义一个类,通过extend关键字来实现继承。但是,这并不意味着go语言不灵活,因为go语言更加灵活,你可以为结构体类型,添加属性和方法。结构体是go语言中自定义类型,可以参考“go语言的内置类型和内置函数以及自定义类型”,是不是很刺激。如下:
type Persion struct { name string age int } func (p * Persion) showtName() { fmt.Println("persion show :", p.name) }
类,说白了也就是属性和方法的集合。所以很多时候,我们可以把go语言里面的结构体理解为类。
当然,如果想对go语言默认的类型添加方法,我们只需要把它定义为新的类型即可,如下:
type Integer int func (a *Integer) setValue(a1 Integer) { *a = a1 }
但是这种方式定义的类型,是没法添加属性的。
既然,我们可以把go语言中结构体当做类来使用,那么go语言中结构体是否可以实现继承、重载、重写、多态,以及是单继承还是多继承,是否有接口,如何实现接口呢等等。
二、go语言中结构体的继承
go语言虽然支持面向对象,但并不支持完全的面向对象。在go语言中,通过匿名组合的方式,实现继承,匿名组合本质上来说,相当于以基类的类型名称(去掉包名部分)作为派生类成员变量的名字,这也是为什么类型名称相同,就算在不同的包中,也不能组合在一个结构体中。组合的案例如下:
type Persion struct { name string age int } func (p * Persion) showtName() { fmt.Println("persion show :", p.name) } type Student struct { num string Persion }
Student通过匿名组合的方式继承了Person的所有属性和方法。测试如下:
s := Student{} s.name ="zhangsan" s.showtName()
go语言中的组合式继承,优点是看到内存的布局。但是也是有弊端的,即Student和Persion类型没有任何关系,当然,也不能将Student类型的变量赋值给Persion。案例如下:
type A struct { } func (a * A) f1() { a.f2() } func (a * A) f2() { fmt.Println("A f2") } type B struct { A } func (a * B) f2() { fmt.Println("B f2") } //测试 b := B{} b.f1()
其实,我希望在f1()里面调用的f2方法来自B,而不是A,但是由于组合的局限性,在f1方法里面,是A调用了f2()方法,也注定就算A被继承,若不重写f1方法,调用的f2方法,就一直来源于A。所以上面执行的结果如下:
由于go语言里面,通过组合方式实现的继承,若派生类和基类含义相同的方法和属性,是都可以访问的。如下B继承A,若A中包含f1和f2方法,B中包含f2方法,那么,针对B的实力b而言:
b.f1() // 访问基类f1的方法 b.f2() // 访问派生类f2的方法 b.A.f2() // 访问基类f2的方法
在上面说的组合继承,都是从结构体类型开始派生,其实我们还可以从结构体指针进行派生如下:
type A struct { } type B struct { *A }
其实,通过指针方式组合和非指针方式实现的组合没有本质的区别, 采用指针方式的组合,依然具有派生的效果,只是派生类创建实例的时候需要外部提供一个基类实例的指针。否则,在使用的时候就会报错:panic: runtime error: invalid memory address or nil pointer dereference。
1、go语言中的“继承”是否支持重载和重写?
重载:即在同一个类中,拥有超过一个的同名方法,他们的区别在于参数个数或是参数类型不同。
重写:指的是子类在继承父类的方法时候,重写了方法的实现。
在go语言中,支持重写,但不支持重载。
三、go语言中的接口
go语言里面多态是通过接口来实现的。接口是对类的一种封装,当然我们需要明确一点,go里面是没有类的概念,那go里面的接口是什么呢?
在go语言中,接口是定义方法的结合,也可以说是包含了抽象方法的集合。如下:
type persion interface { say(p []byte) () do() (bool) }
由于,go的接口有如下特性:(1)实现了接口的定义方法的实例,可以赋值给接口;(2)若接口B定义的方法,在A接口中都有,那么也可以将接口B赋值给接口A。
将对象赋值给接口的案例:
type Persion interface { say(p []byte) do() (bool) } type Worker struct { } func (w Worker) say(p []byte) { fmt.Println("worker has some words to say", p) } func (w Worker) do() (bool) { fmt.Println("just do it ") return true } func (w Worker) who() { fmt.Println("I am worker") } //测试 var p Persion w := Worker{} p = w //将对象赋值给接口 // p = &w //将对象对应的指针赋值给该接口 p.say([]byte("i have too many jobs")) p.do() //p.who() //将会报错,因为接口中没有定义该方法
执行结果如下:
注意事项:1、对象对应的类(结构体)必须实现接口中定义的所有方法;2、我们可以将对象或是对象对应的指针赋值给接口。
关于go语言的接口,如果仅仅是理解到这个程度还是不够的,因为“method has pointer receiver”会经常来困扰你,比如将上面案例Worker的do方法进行简单的修改:
func (w *Worker) do() (bool) { fmt.Println("just do it ") return true }
此时,在测试上面案例,发现:
p = &w //正常执行 p = w //报错
如果,p=w的时候,就报错:
cannot use w (type Worker) as type Persion in assignment:
Worker does not implement Persion (do method has pointer receiver)
为什么呢?
主要还是因为go里面是类型的天下,没有类的概念。虽然,我们可以通过值类型和指针类型两种方式定义方法,如下:
func (w Worker) say(p []byte) { fmt.Println("worker has some words to say", string(p)) } func (w *Worker) do() (bool) { fmt.Println("just do it ") return true }
但是两种方式定义的方法还是有区别的。在说区别的之前,先来了解一个概念“方法集”
1、go中的方法集
①什么是go的方法集呢?
简单的理解就是包含的方法集合,方法集是为类型服务的,通过方法集,我们能够知道该类型的变量有哪些方法可以使用。
②如何查看一个类型的方法集里面有哪些方法?
通过反射,案例如下:
针对接口:
type Human interface { do() say(str string) } var h *Human elem := reflect.TypeOf(h).Elem() n := elem.NumMethod() for i := 0; i < n; i++ { fmt.Println(elem.Method(i).Name) }
针对结构体:
③什么时候会用到go的方法集呢?
比如在interface变量做动态赋值、嵌入struct或interface、type给类型定义别名等。
我们可以通过反射获取对应类型的方法集。
如下:
方法定义方式 拥有的方法 值类型(T) 值类型的对象仅仅拥有(T)类型的方法 指针类型(*T) 指针类型的对象,可以拥有(T)和(*T)方法 The rule about pointers vs. values for receivers is that value methods can be invoked on pointers and values, but pointer methods can only be invoked on pointers.
---待续---