35

聊聊 Go 语言中的面向对象编程

 4 years ago
source link: https://studygolang.com/articles/25079
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

我们知道,在 Go 语言中没有类(Class)的概念,但这并不意味着 Go 语言不支持 面向对象编程 ,毕竟面向对象只是一种编程思想。

让我们回忆一下面向对象的三大基本特征:

  1. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
  2. 继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
  3. 多态:不同对象中同种行为的不同实现方式

我们一起来看看 Go 语言是如何在没有类(Class)的情况下实现这三大特征的。

封装

「类」

在 Go 语言中可以使用 结构体Structs )对属性进行封装,结构体就像是类的一种简化形式。

例如,我们要定义一个矩形,每个矩形都有长和宽,我们可以这样进行封装:

type Rectangle struct {
	Length int
	Width int
}
复制代码

方法

既然有了「类」,你可能会问了,那「类」的 方法 在哪呢?

Go 语言中也有 方法Methods ): Go 方法是作用在接收者( receiver )上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。

定义方法的格式如下:

func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
复制代码

上文中我们已经定义了一个矩形 Rectangle ,现在我们要定义一个方法 Area() 来计算它的面积:

package main

import (
	"fmt"
)

// 矩形结构体
type Rectangle struct {
	Length int
	Width  int
}

// 计算矩形面积
func (r *Rectangle) Area() int {
	return r.Length * r.Width
}

func main() {
	r := Rectangle{4, 2}
	// 调用 Area() 方法,计算面积
	fmt.Println(r.Area())
}
复制代码

上面的代码片段输出结果为 8。

访问权限

我们常会说一个类的属性是 公共 的还是 私有 的,在其他编程语言中,我们常用 publicprivate 关键字来表达这样一种访问权限。

在 Go 语言中没有 publicprivateprotected 这样的访问控制修饰符,它是 通过字母大小写来控制可见性 的。

如果定义的常量、变量、类型、接口、结构、函数等的名称是大写字母开头,这表示它们能被 其它包 访问或调用(相当于 public );非大写开头就只能在 包内使用 (相当于 private )。

访问未导出字段

当遇到 只能在包内使用的未导出字段 时,我们又该如何访问呢?

和其他面向对象语言一样,Go 语言也有实现 gettersetter 的方式:

  • 对于 setter 方法使用 Set 前缀
  • 对于 getter 方法 只使用成员名

例如我们现在有一个处于 person 包中的 Person 结构体:

package person

type Person struct {
	firstName string
	lastName  string
}
复制代码

我们可以看到,它的两个成员变量都是 非大写字母开头 ,只能在包内使用,现在我们为其中的 firstName 来定义 settergetter

// 获取 firstName
func (p *Person) FirstName() string {
	return p.firstName
}

// 设置 firstName
func (p *Person) SetFirstName(newName string) {
	p.firstName = newName
}
复制代码

这样一来,我们就可以在 main 包里设置和获取 firstName 的值了:

package main

import (
	"fmt"

	"./person"
)

func main() {
	p := new(person.Person)
	p.SetFirstName("firstName")
	fmt.Println(p.FirstName())
}

/* Output:
firstName
*/
复制代码

继承

在 Go 语言中没有 extends 关键字,它使用 在结构体中内嵌匿名类型 的方法来实现继承。

匿名类型:即这些类型没有显式的名字。

我们定义一个 Engine 接口类型,一个 Car 结构体,让 Car 结构体包含一个 Engine 类型的匿名字段:

type Engine interface {
	Start()
	Stop()
}

type Car struct {
	Engine // 包含 Engine 类型的匿名字段
}
复制代码

此时,匿名字段 Engine 上的方法「 晋升 」成为了外层类型 Car 的方法。我们可以构建出如下代码:

func (c *Car) GoToWorkIn() {
	// get in car
	c.Start()
	// drive to work
	c.Stop()
	// get out of car
}
复制代码

多态

在面向对象中,多态的特征为: 不同对象中同种行为的不同实现方式 。在 Go 语言中可以使用 接口 实现这一特征。

我们先定义一个正方形 Square 和一个长方形 Rectangle

// 正方形
type Square struct {
	side float32
}

// 长方形
type Rectangle struct {
	length, width float32
}
复制代码

然后,我们希望可以计算出这两个几何图形的面积。但由于他们的面积计算方式不同,我们需要定义两个不同的 Area() 方法。

于是,我们可以定义一个包含 Area() 方法的接口 Shaper ,让 SquareRectangle 都实现这个接口里的 Area()

// 接口 Shaper
type Shaper interface {
	Area() float32
}

// 计算正方形的面积
func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

// 计算长方形的面积
func (r *Rectangle) Area() float32 {
	return r.length * r.width
}
复制代码

我们可以在 main() 函数中这样调用 Area()

func main() {
	r := &Rectangle{10, 2}
	q := &Square{10}

	// 创建一个 Shaper 类型的数组
	shapes := []Shaper{r, q}
	// 迭代数组上的每一个元素并调用 Area() 方法
	for n, _ := range shapes {
		fmt.Println("图形数据: ", shapes[n])
		fmt.Println("它的面积是: ", shapes[n].Area())
	}
}

/*Output:
图形数据:  &{10 2}
它的面积是:  20
图形数据:  &{10}
它的面积是:  100
*/
复制代码

由以上代码输出结果可知: 不同对象调用 Area() 方法产生了不同的结果 ,展现了多态的特征。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK