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

相关推荐
worxfr12 小时前
Go 底层心智模型:并发、内存与闭包
go
26519405112648014 小时前
08-告警并发与多Master同步
go·监控
程序员爱钓鱼18 小时前
Go for 循环详解
后端·面试·go
程序员爱钓鱼1 天前
Go switch 详解
后端·面试·go
2651940511264802 天前
07-告警引擎与状态机
go·监控
NutShell Wang2 天前
Wails v3 Beta 实战:用显式对象模型重写你的第一个 Go 桌面应用
前端·人工智能·go·vibe coding
SoStraw2 天前
Go + webrpc 实战:人在外面,远程查家里 NAS 磁盘和目录
go·p2p·nas·cgo·json-rpc·webrpc·无公网ip
名字还没想好☜2 天前
Go 的 strings.Builder 实战:拼接性能、Grow 预分配与何时该用 bytes.Buffer
java·jvm·后端·golang·go
newerp3 天前
Go net/http 标准库基础
后端·程序员·go
2651940511264803 天前
01-整体架构与高可用
go