1. 面向对象介绍
1.1 如何理解面向对象?
面向对象中有2个概念:类和实例。
(1)类(type):同一类事物的共同特征的抽象概念。
(2)实例(instance):某一种类型的某一个具体的实体(看得见摸得着)。比如说:人类,就属于类这个抽象概念。我这个人,属于人类吧,但同时也是人类的具体的一个实例。
1.2 面向对象三要素
(1)封装
将属性(数据)和方法(操作)封装,提供访问控制(标识符大小写),隐藏实现细节,暴露该暴露的类似下面的代码:
type User struct { // 这就是属性 id int name, addr string score float32 } func getName(u User) string { // 这就是方法 return u.name }
(2)继承
子类可以从父类直接获得属性和方法,减少重复定义(类似于结构体嵌套)。子类如果不满意父类的属性和方法,可以自己定义新的属性和方法,也可以覆盖同名的属性和方法
(3)多态
前提是继承和覆盖,使得子类中虽然使用同一个方法,但是不同子类表现不同,就是不同的态实现了以上特征的语言,才能成为面向对象编程范式语言。
严格意义来说,Go语言就是不想实现面向对象编程范式。但是面向对象又有一些不错的特性,Go语言 通过组合的方式实现了类似的功能。
只能说,Go语言实现了一种非常有自我特征的面向对象。
2. 封装
2.1 什么是封装
通过结构体,可以把数据字段封装在内,还可以为结构体提供方法。
2.2 访问控制
(1)包外可见
属性、方法标识符首字母大写。
(2)包内可见
属性、方法标识符首字母小写。
这些一定程度上实现了public、private的访问控制。
3. 构造函数
Go没有提供类似C++、Java一样的构造函数、析构函数。
在Go中,用构造结构体实例的函数,这个函数没有特别的要求,只要返回结构体实例或其指针即可(建议返回指针,不然返回值会拷贝),就相当于实现了构造函数。
习惯上, 构造函数命名是New或new开头。如果有多个构造函数,可以使用不同命名函数,因为Go也没有函数重载。
type Animal struct { name string age int } // New开头,返回结构体指针。变相实现了构造函数 func NewDefaultAnimal() *Animal { return &Animal{"Tom", 18} } func NewAnimal(name string, age int) *Animal { return &Animal{name, age} }
4. 继承
Go语言没有提供继承的语法,实际上需要通过匿名结构体嵌入(组合)来实现类似效果。
package main
import "fmt"
// 动物类(所有动物的父类)
type Animal struct {
name string
age int
}
func (*Animal) run() {
fmt.Println("这里是Animal动物类的run方法")
}
// 猫类
type Cat struct {
Animal // 嵌入父类,所以猫类(Cat)是动物类(Animal)的子类
color string
}
func main() {
cat := new(Cat)
// 由于Cat类本身没有实现run()方法,所以只能去父类Animal中调用,因为父类实现了run()方法
cat.run()
cat.Animal.run()
}
============调试结果============
这里是Animal动物类的run方法
这里是Animal动物类的run方法
5. 覆盖(override)
也称重写。注意不是重载overload。
package main
import "fmt"
// 动物类(所有动物的父类)
type Animal struct {
name string
age int
}
func (*Animal) run() {
fmt.Println("这里是Animal动物类的run方法")
}
// 猫类
type Cat struct {
Animal
color string
}
// 我觉得父类的run()方法,不满足我的需求,于是我重新定义了一个run()方法
// 因为Cat类中嵌入了父类Animal,所以我们重新定义run,就相当于覆盖了父类的run
func (a *Cat) run() {
fmt.Println("这里是Cat猫类的run方法")
}
func main() {
cat := new(Cat)
cat.run() // 这里调用run方法的时候,就会直接调用Cat类的run方法
cat.Animal.run() // 这里因为指定调用父类的run方法,所以结果不变
}
============调试结果============
这里是Cat猫类的run方法
这里是Animal动物类的run方法
为Cat增加一个run方法,这就是覆盖。特别注意 cat.run() 和 cat.Animal.run() 的区别。
上例增加run方法是完全覆盖,就是不依赖父结构体方法,重写功能。
如果是依赖父结构体方法,那就要在子结构体方法中调用它。
func (c *Cat) run() {
c.run() // 可以吗?不可以,cat.run() 这是无限递归调用,小心!
c.Animal.run() // 可以吗? 可以,这就调用了父结构体中的方法
fmt.Println("Cat run+++")
}
6. 多态
实现多态的前提:先实现继承和覆盖,使得子类中虽然使用同一个方法,但是不同子类表现不同,就是不同的态。
Go语言不能像Java语言一样使用多态。可以通过引入interface接口来解决。
package main
import "fmt"
// 动物类(所有动物的父类)
type Animal struct {
name string
age int
}
func (a *Animal) run() {
fmt.Printf("%s 这里是Animal动物类(父类)的run方法", a.name)
}
// 猫类
type Cat struct {
Animal
color string
}
func (a *Cat) run() {
fmt.Println("这里是Cat猫类的run方法")
}
// 狗类
type Dog struct {
Animal
color string
}
func main() {
d := new(Dog)
d.name = "旺财"
// 这里由于Dog本身没有实现run()方法,所以只能去父类Animal中调用run()方法
d.run()
}
============调试结果============
旺财 这里是Animal动物类(父类)的run方法
首先我们有3个类
Animal(父类)
Cat(子类)
Dog(子类)
假设小花,是猫类的一个具体的实例,属于Cat猫类,同时也属于Animal动物类。
假设旺财,是狗类的一个具体的实例。属于Dog狗类,同时也属于Animal动物类。
那上述代码跟多态有什么关系呢?继续看下面的代码:
package main
import "fmt"
// 动物类(所有动物的父类)
type Animal struct {
name string
age int
}
func (a *Animal) run() {
fmt.Printf("%s 这里是Animal动物类(父类)的run方法", a.name)
}
// 猫类
type Cat struct {
Animal
color string
}
func (a *Cat) run() {
fmt.Printf("%s 这里是Cat猫类的run方法\n", a.name)
}
// 狗类
type Dog struct {
Animal
color string
}
func (a *Dog) run() {
fmt.Printf("%s 这里是Dog狗类的run方法\n", a.name)
}
func main() {
var c = new(Cat)
c.name = "三花"
c.run()
d := new(Dog)
d.name = "旺财"
d.run()
}
============调试结果============
三花 这里是Cat猫类的run方法
旺财 这里是Dog狗类的run方法
注意看上述代码的结果,c和d都调用的run方法,并输出了对应的结果。
但有没有办法,调用一次run,实现输出不同的结果呢?看下面:
6.1 基于接口实现多态
package main
import "fmt"
// 定义用来实现多态的接口
type Runner interface {
run()
}
type Animal struct {
name string
age int
}
func (a *Animal) run() {
fmt.Printf("%s 这里是Animal动物类(父类)的run方法", a.name)
}
type Cat struct {
Animal
color string
}
func (a *Cat) run() {
fmt.Printf("%s 这里是Cat猫类的run方法\n", a.name)
}
type Dog struct {
Animal
color string
}
func (a *Dog) run() {
fmt.Printf("%s 这里是Dog狗类的run方法\n", a.name)
}
// 实现多态
func foo(a Runner) {
a.run()
}
func main() {
var c = new(Cat)
c.name = "三花"
d := new(Dog)
d.name = "旺财"
// 调用foo函数实现多态
foo(c)
foo(d)
}
============调试结果============
三花 这里是Cat猫类的run方法
旺财 这里是Dog狗类的run方法