Golang 面向对象全解
Posted Lejeune
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang 面向对象全解相关的知识,希望对你有一定的参考价值。
Golang 面向对象全解
golang的面向对象是不完备的面向对象,大部分人员都是从java转换而来,造成了很多的坑,这一篇文章记录我已知的golang面向对象性质和坑,希望后来者注意。
此文仅是个人在学习过程中摸索的自我总结,无法保证完全理解正确,如有错误,欢迎指正,共同学习!
基础
go中没有class的概念,只有与之类似的struct结构体(很像c语言),还有接口interface
接下来简单对比一下,他与普通面向对象语言的概念上的区别。
类/结构 | 接口 | |
go | struct 结构体 定义阶段只能描述成员变量 后面通过在函数前面增加标识符来完成方法的描述,可读性不如java | interface 接口 只能描述方法接口 |
java | class 类 可以描述成员变量、方法 | interface 接口 可以描述成员与方法接口 |
定义上go的interface只用作一种类型定义,可以认为是方法的集合,我们不需要显式的去声明struct继承了什么接口 然后去实现它,而是先去实现那个方法,所有实现了接口中所描述方法的结构体都认为其实现了这个接口。
这个特性非常有趣,但是貌似也容易出错,无法显式地告诉我们,我们哪些方法还未实现。
而还有一个特殊的接口interface被叫做空接口,里面有0个方法,认为所有对象都实现了空接口
在做代码对比之前,我们需要先引入接口类型判断的方法,才能说明问题。
类型判断/转换
-
转换
a:=3 float32(a)
所有类型转换(包括之后的类型转换,和函数参数调用中所声明的类型)严格遵守类型转换后的类型,比如someInterface(Object),这个新对象无法调用原对象当中的首字母大写的成员变量,同时也无法调用没有在someInterface中记录的方法,也就是只能调用someInterface中所记录的方法。
-
枚举判断(在已知类型中判断)
switch interface(float32(3)).(type) case float32: fmt.Prntln("this is float32") interface(3)//所有对象都实现了空接口
注意 ,Object.(type),Object需要是interface类型,interface指的是空接口,所有对象都实现了这个接口,从而可以使用上面的类型转换使对象变成interface类型。这个语法需要与switch配合。
-
类型尝试转换(常用于判断是否实现接口)
语法
newObject,ok:=Object.(someType)
其中Object对象要求是interface类型,ok是转换结果,true or false,newObject是转换之后的对象。
golang中的接口和结构体
-
struct结构体
-
结构体定义时只能描述成员变量
-
成员变量以首字母大小写区分公有与私有,首字母大写为公有变量,小写反之。
-
结构体类型声明后,其对象存储的是对象实例
type Person struct Name string var p Person=Person"Jack"
-
结构体定义完成后,可以通过对正常函数增加前缀的方式完成方法的定义与实现。
//Person类定义同上 //方式一 func (p Person) say() fmt.Println(p.Name) //这样就定义了一个对象方法,其中p是Person对象的实例 // //方式二 func (p *Person) say() fmt.Println(p.Name) //此时p是Person对象的指针,此时p.Name也可以等价为(*p).Name,go自动做了转换,所以两个代码等价 //看起来两者都可以完成类方法的定义与实现,但是在使用上会有不同,将会在坑点里面讲
-
-
Interface 接口
-
接口只能留出方法描述,不做具体实现
-
接口不能描述成员变量
-
Go的面向对象实现
首先面向对象具有三大特性,封装、继承、多态,go均不直接支持这三种特性,但都可以通过别的方式来实现这三种特性。
封装
golang中没有class只有struct结构体,类比c语言。go中成员变量没有权限修饰符,public等,其通过成员变量的变量名开头大小写来控制访问。
type Person struct
Name string//公有变量
ago int//私有变量
继承
go中继承是通过包含来实现的。
包含有两种情况,一种是和java一样,父亲有一个儿子对象,儿子和父亲各自拥有自己的名字,此时的儿子和父亲相当于两个不同的类,只是父亲有一个儿子对象而已。
package main
import "fmt"
type Person struct
Name string
type Say interface
say()
func (p *Person)say()
fmt.Println("My name is"+p.Name)
type Father struct
son Person
Name string
func (f Father)teach()
fmt.Println("I am "+f.Name+", teaching my son"+f.son.Name)
func main()
p:=Person"Jack"
f:=Fatherp,"Tom"
f.teach()
上面是java意义下的包含,下面这一种是通过包含实现go的继承,通过传入匿名对象,使得父类的所有成员变量、方法都变为子类的。
package main
import "fmt"
type Person struct
Name string
type Say interface
say()
func (p *Person)say()
fmt.Println("My name is "+p.Name)
type Teacher struct
Person
func (f Teacher)teach()
fmt.Println("I am a teacher! And My name is "+f.Name)
func main()
t:=TeacherPerson"Tom"
t.say()
t.teach()
多态
同一个接口不同实现,这个方法我认为在go中是比较鸡肋的。
package main
import "fmt"
type Person struct
Name string
type Say interface
say()
func (p Person)say()
fmt.Println("My name is "+p.Name)
type Teacher struct
Name string
func (f Teacher)say()
fmt.Println("I am a teacher! And My name is "+f.Name)
func main()
var t,p Say =Teacher"Tom",Person"Jack"
t.say()
p.say()
坑点
-
接口实现无法被程序员直接知道,只能依赖于ide来告诉程序员这个接口中的方法没有被实现。
-
go在接口类型对象赋值的时候,可以接受其引用或者实例。
package main import ( "fmt" ) type Person struct Name string type Say interface say() func (p Person)say() fmt.Println("My name is "+p.Name) func main() //以下两种都可以,但是在大多数情况下,对于接口直接用指针赋值一定不会错 //这两者的不同请看下一个坑点 //var p Say=&Person"Tom" var p Say=Person"Tom" p.say()
-
ide判断接口实现与否,严格按照定义的前缀
之前提到函数名前面的类型加不加星号,看起来没有区别。但是当我们将前缀加上星号时,
//上面不变 func (p *Person)say() fmt.Println("My name is "+p.Name) func main() //这句话报错 //var p Say=Person"Tom" //这句话正常 var p Say=&Person"Tom"//new(Person)返回的也是指针,也可以使用 p.say()
这是因为,ide在检测接口是否实现时,严格按照上面的定义类型,现在方法被定义在Person类的指针上,Person实例就无法调用,所以在使用时仅能通过指针调用,加上接口对象能接受指针或实例,所以需要加一个取地址符号。如果上面是没有星号的,说明方法定义在实例上,实例和指针都能对其进行调用。但是不管怎么说,对于接口实例对象加一个取地址符号&,是一定能运行的。建议对于接口类型都直接加上&
-
子类调用父类方法上的异常
package main import ( "fmt" ) type Person struct Name string //type Say interface // say() // func (p *Person)call() p.say() func (p *Person)say() fmt.Println("My name is "+p.Name) type Teacher struct Person func (t Teacher)say() fmt.Println("I'm a teacher,"+t.Name) func main() var t Teacher=TeacherPerson"Tom" t.call()
这段代码去调用了person的say,和java上的结果是不同的,这是反直觉的。
猜测实质是因为go的函数调用严格遵守类型,call函数的形式参数是person 那么他就不知道存在teacher的say函数,所以直接去调用了person的say。
那么如何解决?之前说接口是一类方法的集合,我们调用方法时,先想一想,我们到底需要多少方法,将这个函数剥离出结构体之外 ,单独成为一个函数,形式参数传递所需要的方法组成的接口对象,如下:
package main import ( "fmt" ) type Person struct Name string type Say interface say() func call(s Say) s.say() func (p *Person)say() fmt.Println("My name is "+p.Name) type Teacher struct Person func (t Teacher)say() fmt.Println("I'm a teacher,"+t.Name) func main() var t Teacher=TeacherPerson"Tom" call(t)
但是每一次子类方法重载后都需要写一个外部函数去调用,有时候属实麻烦,我们希望通过继承能够完成像java一样的直接调用重载的接口,可以考虑在实现方法的时候,不去调用他原本所属类的实例,而是通过传入一个方法接口实例,从而达到写一次能继承的效果。
package main import ( "fmt" ) type Person struct Name string type Say interface say() //注意此处 前缀只用作限制其方法所属,真正实例由方法调用时传入 func (Person)call(s Say) s.say() func (p Person)say() fmt.Println("My name is "+p.Name) type Teacher struct Person func (t Teacher)say() fmt.Println("I'm a teacher,"+t.Name) func main() var t Teacher=TeacherPerson"Tom" t.call(t)
不管是哪种方法都对程序员的抽象能力做了更高的要求,程序员需要清晰的知道,方法内部需要调用哪些方法,从而抽象出其接口。如果怕出错,不如在有继承关系的结构体实现时,都通过参数来传递对象。
golang的坑点大多数都在interface上,这是我们学习常规面向对象语言之后所遗留下来的情况,听说go官方并不是很建议用go去面向对象,但是面向对象确实能提高效率。。。我们在go中不能以之前的面向对象思维去看待它。
以上是关于Golang 面向对象全解的主要内容,如果未能解决你的问题,请参考以下文章