Go面向对象详解

今天我们来了解一下 Go 中函数的另一种形态,带有接收者的函数,我们称为 method

method 是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在 func 后面增加了一个 receiver (也就是 method 所依从的主体)。

用 Rob Pike 的话来说就是:

"A method is a function with an implicit first argument, called a receiver."

method 的语法如下:
func (r ReceiverType) funcName(parameters) (results)

例如下面的 method 来实现面积的求和:

Go 复制代码
package main

import (
    "fmt"
)

type Rectangle struct {
    width, height float64
}

// 这样就实现了method
func (r Rectangle) area() float64 {
    return r.width * r.height
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}

    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
}

使用 method 有下面的几个特点:

  1. 虽然 method 的名字一模一样,但是如果接收者不一样,那么 method 就不一样。
  2. method 里面可以访问接收者的字段。
  3. 调用 method 通过 . 访问,就像 struct 里面访问字段一样。

Receiver 还可以是指针,两者的差别在于,指针作为 Receiver 会对实例对象的内容发生操作,而普通类型作为 Receiver 仅仅是以副本作为操作对象,并不对原实例对象发生操作。

后文对此会有详细论述。

method 只能作用在 struct 上面吗? 当然不是,他可以定义在任何你自定义的类型、内置类型、struct 等各种类型上面。

struct 只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。

type typeName typeLiteral

请看下面这个申明自定义类型的代码。

Go 复制代码
type ages int
type money float32
type months map[string]int

实际上只是一个定义了一个别名,有点类似于 c 中的 typedef,例如上面 ages 替代了 int

指针作为 receiver

Go 复制代码
package main

import "fmt"

type Tree struct {
    Value int
    Left  *Tree
    Righ  *Tree
}

func (t Tree) setValue1(v int) {
    t.Value = v
}
func (t *Tree) setValue2(v int) {
    t.Value = v
}

func main() {
    t1 := Tree{
        Value: 0,
        Left:  nil,
        Righ:  nil,
    }
    t1.setValue1(1)
    fmt.Println(t1) // {0 <nil> <nil>} 并没有修改原来的值
    t1.setValue2(2)
    fmt.Println(t1) // {2 <nil> <nil>} 对原来的Tree修改了

    t2 := &Tree{
        Value: 0,
        Left:  nil,
        Righ:  nil,
    }
    t2.setValue1(1)
    fmt.Println(t2) // &{0 <nil> <nil>} // 并没有对原来的值进行修改
    t2.setValue2(2)
    fmt.Println(t2) // &{2 <nil> <nil>}
}

从上面的例子可以看出来:

  1. 如果一个 methodreceiver*T, 你可以在一个 T 类型的实例变量 V 上面调用这个 method,而不需要 &V 去调用这个 method

  2. 如果一个 methodreceiverT,你可以在一个 T 类型的变量 P 上面调用这个 method,而不需要 *P 去调用这个 method

这种特性:让开发者不必关注,调用的指针的 method 还是不是指针的 method,Go 能推断出你的目的,这对于有多年 C/C++ 编程经验的同学来说,真是解决了一个很大的痛苦。

如果你的方法想修改值就将 receiver 改为指针类型,否则就使用实例变量类型。

method 继承

如果匿名字段实现了一个 method,那么包含这个匿名字段的 struct 也能调用该 method

Go 复制代码
package main

import "fmt"

type Human struct {
    name  string
    age   int
    phone string
}

type Student struct {
    Human  // 匿名字段
    school string
}

type Employee struct {
    Human   // 匿名字段
    company string
}

// 在 human 上面定义了一个 method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

method 重写 上面的例子中,如果 Employee 想要实现自己的 SayHi, 怎么办?

简单,和匿名字段冲突一样的道理,我们可以在 Employee 上面定义一个 method,重写了匿名字段的方法。

Go 复制代码
package main
import "fmt"

type Human struct {
    name  string
    age   int
    phone string
}

type Student struct {
    Human  // 匿名字段
    school string
}

type Employee struct {
    Human   // 匿名字段
    company string
}

// Human 定义 method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Employee 的 method 重写 Human 的 method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
		e.company, e.phone) //Yes you can split into 2 lines here.
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

上面的代码设计的是如此的美妙,让人不自觉的为 Go 的设计惊叹!

通过这些内容,我们可以设计出基本的面向对象的程序了。

但是 Go 里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现 (大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。

相关推荐
研究司马懿19 小时前
【云原生】Gateway API高级功能
云原生·go·gateway·k8s·gateway api
梦想很大很大1 天前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰2 天前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘2 天前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤2 天前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt112 天前
AI DDD重构实践
go
Grassto4 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo