Go 面向对象编程的实现
Go 语言没有类(class)的概念,但这并不意味着 Go 语言不支持面向对象编程,毕竟面向对象只是一种编程思想。面向对象有三大基本特征:
- 封装:隐藏对象的属性和实现细节,仅对外提供公共访问的方式
- 继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
- 多态:不同对象中同种行为的不同实现方式
一、封装
1. 属性
Go 语言中可以使用结构体对属性进行封装。结构体就像是类的一种简化方式。例如,我们要定义一个三角形,每个三角形都有底和高,可以这样进行封装:
type Triangle struct {
Bottom float32
Height float32
}
2. 方法
既然有了“类”,那方法呢?Go 语言也有方法(Methods),方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此,方法是一种特殊类型的函数。
定义方法的格式如下:
func (recv recv_type) methodName (param_list) (return_value_list) {...}
我们已经定义了一个三角形类,现在我们为其定义一个Area()
方法计算其面积:
package main
import "fmt"
// Triangle 三角形的结构体
type Triangle struct {
Bottom float32
Height float32
}
// Area 计算三角形的面积
func (t *Triangle) Area() float32 {
return (t.Bottom * t.Height) / 2
}
func main() {
r := Triangle{
Bottom: 6,
Height: 8,
}
fmt.Println(r.Area())
}
运行结果为 24
3. 访问权限
在 C++ / Java 等语言中,使用 public 和 private 等关键字来表达访问权限,在 Go 语言中,是通过字母的大小写来控制可见性。
- 如果定义的常量、变量、类型、接口、结构体、函数等的名称是大写字母打头,则表示他们能被其他包访问或调用(相当于 public);
- 非大写开头就只能在包内使用(相当于 private)
例如,定义一个学生结构体来描述名字和分数:
type Student struct {
name string
score float32
Age int
}
在以上结构体中,Age
属性可以直接被其他包访问,而 name
就不行。
和其他面向对象语言一样,Go 语言也有实现获取和设置属性的方式:
package main
type Student struct {
name string
score float32
}
// 获取 name
func (s *Student) GetName() string {
return s.name
}
// 设置 name
func (s *Student) SetName(newName string) {
s.name = newName
}
二、继承
Go 语言没有 extends 关键字,而是使用结构体中内嵌匿名类型的方法来实现继承。例如定义一个 Engine 接口类型和一个 Bus 结构体,让 Bus 结构体包含一个 Engine 接口的匿名字段:
package main
type Engine interface {
Run()
Stop()
}
type Bus struct {
Engine // 包含 Engine 类型的匿名字段
}
func (c *Bus) Working() {
c.Run()
c.Stop()
}
此时,匿名字段 Engine 上的方法“晋升”为外层类型 Bus 的方法。
三、多态
在面向对象中,多态的特征是不同对象中的同种行为的不同实现方式。在 Go 语言中,可以通过使用接口来实现这个特征。
先定义一个正方形 Square 和一个三角形 Triangle:
// Square 正方形
type Square struct {
sideLen float32
}
// Triangle 三角形
type Triangle struct {
Bottom float32
Height float32
}
然后,希望可以计算出这两个几何图形的面积。但由于他们的面积计算方式不一样,所以需要定义两个不同的Area()
方法。
于是定义一个包含 Area()
方法的接口 Shape,让 Square 和 Triangle 都实现这个接口里的 Area() 方法:
// Area 计算三角形的面积
func (t *Triangle) Area() float32 {
return (t.Bottom * t.Height) / 2
}
// Shape 接口
type Shape interface {
Area() float32
}
// Area 计算正方形的面积
func (sq *Square) Area() float32 {
return sq.sideLen * sq.sideLen
}
func main() {
t := &Triangle{6, 8}
s := &Square{8}
shapes := []Shape{t, s}
for i, _ := range shapes {
fmt.Println("图形数据:", shapes[i])
fmt.Println("它的面积是:", shapes[i].Area())
}
}
以上代码的运行结果为:
图形数据: &{6 8}
它的面积是: 24
图形数据: &{8}
它的面积是: 64
由以上代码输出结果可知,不同对象调用 Area()
方法产生了不同的结果,展现了多态的特征。