在学习接口时,已经接触了一些面向对象的概念,在这个章节中,我们来重点学习一下面向对象的相关概念。
面向对象程序设计(OOP:Object-oriented programming)是一种具有对象概念的编程典范,同时也是一种程序开发的抽象方针,包含数据、特性、代码与方法。主要涉及两个核心概念。
- 类(Class): 根据某类事物的共同特征高度抽象出一种类型。例如动物、人类等
- 对象(Object): 是类的一个具体实现,即类的实例。例如:人类是一个类,而具体的一个人(例如:小明)则是人类的一个实例,一个类可以拥有很多个实例
面对对象的核心三要素如下所示:
- 封装:将属性(数据)和方法(操作)封装,提供访问控制,隐藏实现的细节,仅对外暴露必要的接口
- 继承:子类可以从父类直接获取属性和方法,减少重复定义。若子类与父类有不同,可以自己重新定义新的属性和方法,也可以覆盖父类的同名属性和方法
- 多态:父类和子类虽使用同一个方法,但不同子类表现却不一样,就是不同的态。可以简单理解为龙生九子,但各有所好
只有实现了以特征的语言,才能算是面向对象编程范式的语言。所以从严格意义上来讲,Go并不是面向对象编程范式的语言,但却以借鉴了面向对象的一些特征,Go语言通过组合的方式实现了类似的功能。
10.1 封装
通过结构体,可以将数据字段封装在里面,也可以为结构体绑定方法。还可以进行访问控制。
- 字段、方法标识符首字母大写,实现包外可见的访问控制
- 字段、方法标识符首字母小写,仅包内可见
通过首字母的大小写,实现类似于Java中的public和private访问控制
10.2 继承
Go语言没有提供继承语法,但可以结构体嵌套来达到类似效果。
10.3 多态
Go语言不能像Java语言那样使用多态,但可以通过引入接口来达到类似的效果,示例代码如下所示:
package main
import "fmt"
type Run interface {
Run() string
}
type Animal struct {
Name string
}
func (a *Animal) Run() string {
return fmt.Sprintf("Animal:%s is running", a.Name)
}
type Cat struct {
// 结构体嵌套
Animal
Color string
}
func (c *Cat) Run() string {
fmt.Printf("%s is running,color is: %s\n", c.Name, c.Color)
return c.Animal.Run()
}
type Dog struct {
Animal
Type string
}
func (d *Dog) Run() string {
fmt.Printf("%s is running,type is: %s\n", d.Name, d.Type)
return d.Animal.Run()
}
func test(a Run) {
// 多态
fmt.Println(a.Run())
}
func main() {
d := &Dog{Animal: Animal{Name: "小黄"}, Type: "柯基"}
c := &Cat{Animal: Animal{Name: "小花"}, Color: "黄色"}
test(d)
test(c)
}
运行结果如下所示:
小黄 is running,type is: 柯基
Animal:小黄 is running
小花 is running,color is: 黄色
Animal:小花 is running
方法test使用同一个类型的同一接口却表现不同,这就是多态。
10.4 覆写
即子类通过定义一个与父类同名的方法,达到重写的功能,示例代码如下所示:
package main
import "fmt"
type Animal struct {
Name string
}
func (a *Animal) Run() string {
return fmt.Sprintf("Animal:%s is running", a.Name)
}
type Cat struct {
// 结构体嵌套
Animal
Color string
}
func (c *Cat) Run() string {
return fmt.Sprintf("Cat:%s is running", c.Name)
}
func main() {
c := &Cat{Animal: Animal{Name: "小花"}, Color: "黄色"}
// 调用子类自身的Run方法
fmt.Println(c.Run())
// 调用父类的Run方法
fmt.Println(c.Animal.Run())
}
运行结果如下所示:
Cat:小花 is running
Animal:小花 is running
在上面代码通过为Cat增加一个Run方法,从而覆盖了父类的Run方法,从而达到不依赖父类结构体方法。