前言

这篇文章代表博主正式开始学习和使用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 中,方法和函数的区别主要是:

  1. 函数将变量作为参数:func func1(recv),其实体现为一个定义的含义。
  2. 方法在变量上被调用:如上例 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 接口有如下的特点:

  1. 类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。
  2. 实现某个接口的类型(除了实现接口方法外)可以有其他的方法。
  3. 一个类型可以实现多个接口。
  4. 接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

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 语言里有非常灵活的 接口 概念,所以可以通过它可以实现很多面向对象的特性。