Go - 是面向对象语言吗?
前言
这篇文章代表博主正式开始学习和使用Go,以前的工作中主要使用一门动态的、解释型和面向对象的脚步语言。是的,你没猜错!它是 PHP。
Go 是一门编译静态语言,令人疑惑的它究竟是不是面向对象设计的。所以本文就以 OOP 的三大特性进行两门语言的对比,看看 Go 是不是面向对象的。
正文
受 C 家族语言如PHP、Java等影响,得到一个不成文的结论:没有类(class)设计存在的就不是面向对象语言。
其中这是不对的,面向对象是一种编程思想,而非一种语言特有的技能。例如javascript 也能通过原型的方式实现面向对象编程,那它该如何归宿呢?
从严格意义上来说,Go 不是一门面向对象的编程语言。 但是并不妨碍这门语言大放异彩,如 k8s、docker、微服务、网关等。
本文的宗旨在于分析按常规OOP的特性下,GO 是否支持或者怎么实现面向对象特性。
类
对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象。它是类的实例。
而在 OOP 开发中,有超过90%的代码是写到一个个类中。类定义了一件事物的抽象特点。类的定义包含了数据的形式以及对数据的操作。
一个类的定义中常常包含:成员变量、成员方法等。在 PHP 中是这样定义一个类的:
PHP 中方法是依赖于类存在的,是面向对象中定义的,只能通过对象调用(类的静态方法能够通过类名直接调用);
函数是单独存在的,是面向过程中定义的。目的是解决一类通用的问题,引入后可以在程序的任何地方直接调用。比如
include一个PHP文件,使用里面的函数。
class ParentClass
{
public function __construct()
{
}
}
class MyClass extends ParentClass
{
public $attr;
public function __construct()
{
}
public function handle()
{
}
}
上例代码中体现了PHP类的结构体、继承、属性及可见性、构造方法、普通方法等信息。
前文提到 OOP 是一种编程思想,所以在 Go 中有着类似PHP中类的存在。它就是 结构(struct)。
在 Go 中定义一个结构如下:
type Common struct {
Sex string
}
type MyStruct struct {
*Common
Name string
age int
}
上例代码在结构 MyStruct 中:*Common 体现了继承的特性,属性首字母的大小写体现了可见性。
当然也可以使用如下的方式让 结构 能实现构造函数的功能,通常使用结构体工厂方法创建结构体的实例。
按惯例,工厂的名字以 new 或 New 开头。
type MyStruct struct {
name string
age int
}
func NewPeople(name string, age int) *MyStruct {
// 返回一个指向结构体实例的指针
return &MyStruct{name, age}
}
people := NewPeople("张三", 18)
方法
上一小节中,描述了PHP 和 Go 怎么是实现类似OOP语言中的类定义、继承、封装、可见性性、构造方法等。
这一小节主要讲解 Go 中方法的实现、方法和函数的区别。
可以认为在 Go 语言中,结构体就像是类的一种简化形式,而 Go 的方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。
接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。
但是接收者不能是一个接口类型,因为接口是一个抽象定义,但是方法却是具体实现。所以在接口上定义一个方法是编译失败的。
所以在 Go 中:一个类型加上它的方法等价于面向对象中的一个类,类型可以是结构体或者任何用户自定义类型。 方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。
但是不同于 OOP 语言的是:类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
// 通常形式
func (recv receiver_type) methodName(parameter_list) (return_value_list) {
}
// 举例 求矩形面积
type Rectangle struct {
a int
b int
}
func (re *Rectangle) Area() int {
retun re.a * re.b
}
myStr := new (Rectangle)
myStr.a = 3
myStr.b = 4
myStr.Area()
在 Go 中,方法和函数的区别主要是:
- 函数将变量作为参数:func func1(recv),其实体现为一个定义的含义。
- 方法在变量上被调用:如上例 myStr.Area()。
小结:在 Go 中,类型就是类。Go 拥有类似面向对象语言的类继承的概念。
接口
Go 中有 接口 的存在,可以使用它实现很多面向对象的特性。
接口定义了一组方法(方法集),但是这些方法不包含 实现 逻辑。它们没有被实现(抽象的)。接口里也不能包含变量。
如下定义:
1. 按照约定,只包含一个方法的接口的名字由方法名加 [e]r 后缀组成。
2. 接口都很简短,通常它们会包含 0 个、最多 3 个方法。
// 矩形有面积、周长的方法
type Rectangle interface {
Area(param_list) return_type
Girth(param_list) return_type
...
}
Go 接口有如下的特点:
- 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。
- 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
- 一个类型可以实现多个接口。
- 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。
package main
import "fmt"
1. 一个结构体 Square 和一个接口 Rectangle,接口有一个方法 Area()。
2. 在 main() 方法中创建了一个 Square 的实例。
3. 定义了一个接收者类型是 Square 方法的 Area(),用来计算正方形的面积:结构体 Square 实现了接口 Rectangle 。
4. 将一个 Square 类型的变量赋值给一个接口类型的变量。
// 求面积的方法
type Rectangle interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
// 当然可以继续实现多个结构和方法,从而实现多态的特性。
func main() {
sq1 := new(Square)
sq1.side = 2
var areaIntf Rectangle
areaIntf = sq1
// 简明方式 areaIntf := Shaper(sq1)
fmt.Printf("面积: %f\n", areaIntf.Area())
}
输出: 面积: 4.000000
总结
Go 语言不是一种 “纯正” 的面向对象编程语言。它没有类和继承的设计体现,但是可以通过一些编程思想从而实现。
Go 语言里有非常灵活的 接口 概念,所以可以通过它可以实现很多面向对象的特性。