我们一直学习的是 Go 不支持面向对象,取而代之的是组合的方式。Go 利用接口可以实现类似面对对象的功能,比如多态。虽然 Go 语言并非面向对象的语言,但它还是包含了一些特征可模拟面向对象的编程语言。
面向对象三大特征
我们知道,面向对象编程语言有三大关键特征:封装、继承和多态。
所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。多态其实 Go 已经帮我们实现了。通过利用 Go 接口生成同一类的两个或多个元素对象。这一点在 Go 接口的文章中已经提过。接下来着重关注于其他两个特征。
所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
所谓继承,是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。
Go 实现封装
Go 如何实现封装。其实只要实现类,类是面向对象编程语言是很关键的概念,类包含成员变量和成员函数。那么 Go 如何模拟类呢?使用某种方法将函数和类型关联,函数和类型构建一个对象。
package main import ( "fmt" ) type ca struct { XX int YY int } type cb struct { XX string YY int } type cc struct { A ca B cb } func (A ca) A() { fmt.Println("Function A() for A") fmt.Println(A.XX) } func (B cb) A() { fmt.Println("Function A() for B") fmt.Println(B.XX) } func main() { var ci cc ci.A.XX = 2022 ci.B.XX = "你好,2022" ci.A.A() ci.B.A() }
执行上述代码,将得到如下结果。
[Running] go run "/Users/yuzhou_1su/GoProjects/GoOOP/main.go" Function A() for A 2022 Function A() for B 你好,2022
代码解释,这里我们先通过定义两个结构体 ca
和 cb
,然后通过结构体 cc
将 ca
和 cb
组合起来,这样就类似绑定了两个成员,然后定义两个方法包含相同的名称 A()
,因为它们拥有不同的函数头,第一个方法适合变量 ca
, 第二个方法适用于变量 cb
。
这样我们就通过结构体和同名函数实现了成员变量和成员函数的功能,虽然与面向对象语言相比要简单很多,但是通过这一功能实现了类的概念。
Go 实现继承
Go 如何实现继承。其他面向对象的语言,是使用 extends
关键字来表示继承的,而go语言中继承的设计非常简单,不需要额外的关键字。
package main import "fmt" type Person struct { name string age int } type Student struct { Person id int score int } func (person *Person) showInfo() { fmt.Printf("My name is %s , age is %d ", person.name, person.age) } func (person *Person) setAge(age int) { person.age = age } func (student *Student) showInfo() { fmt.Println("I am a student ...") } func (student *Student) read() { fmt.Println("read book ...") } func main() { student := Student{Person{"Etta", 24}, 1001, 100} student.showInfo() student.setAge(22) student.read() }
运行该代码:
[Running] go run "/Users/yuzhou_1su/GoProjects/GoOOP/v2/main.go" I am a student ... read book ...
可以看到,在学生结构体中,我们使用到了Person
作为匿名变量,意思就是,Student继承自Person。我们除了继承了成员变量和成员方法之外,还可以为学生结构体增加成员方法,重写成员方法。
Go 实现重载
Go 如何实现重载,是通过将类型嵌入新的结构类型中,形成一种层次关系。Go 接口可以在不同元素间定义公共行为,以使这些不同的元素可共享某个对象的特征。
package main import "fmt" type father struct{} func (a father) F() { a.shared() } func (a father) shared() { fmt.Println("This is shared() from father!") } type son struct { father } func (a son) shared() { fmt.Println("This is shared() from son!") } func main() { father{}.F() son{}.shared() i := son{} j := i.father j.F() }
此处代码通过 son 类型嵌入了 father 类型,而这两种类型共享 shared()
函数。运行该代码,得到:
[Running] go run "/Users/yuzhou_1su/GoProjects/GoOOP/v2/main.go" This is shared() from father! This is shared() from son! This is shared() from father!
虽然 father{}.F()
和 son{}.shared()
函数生成了期望的结果,但 j.F()
函数仍然调用了 father.shared()
函数而不是 son.shared()
函数,尽管 son 类型改变了 shared() 函数实现。
总结
面向对象语言的类和对象能实现更多功能。Go 只能通过结构体和接口去实现了类似的效果,但 Go 创建者并不推荐大家以此方式去工作,合理利用组合的概念才是 Go 更合理的开发模式。