【go】gopl学习笔记(3.方法method)

本文介绍了Go语言中的方法声明、指针接收者、组合、方法值和封装等面向对象特性。方法通过接收者归属类型,提供了类似面向对象的行为。指针接收者允许修改对象,组合通过结构体嵌套实现。方法值和方法表达式提供了不同的调用方式。封装则通过字段和方法的可见性控制实现信息隐藏。
摘要由CSDN通过智能技术生成

前言

从之前的特性看下来,很难相信go竟然也是一个面向对象的语言,本篇来see see它的OOP特性,封装和组合体现在哪里~

1.方法声明

OOP的对象通常包含数据和行为,type定义的类型可以包含数据,其行为怎么表示呢?go是通过方法实现的。

go中的method方法与function函数非常类似,区别在于在函数名之前,方法携带了额外的参数(通常是所属类型type e.g. Point),这个参数将这个方法归属于这个参数的类型(表示是这个类型)。如下例,同样的距离方法,函数实现是两个点都在参数列表中,而方法将当前点p放在函数名前,而其它点放在参数列表中。

这个额外的参数(类似于Java中的隐含的this)是这个方法的接收者(receiver)。

// Point类型
type Point struct{ X, Y float64 } 

// 包级函数
func Distance(p, q Point) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// Point类型的方法
func (p Point) Distance(q Point) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}

p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", 函数调用
fmt.Println(p.Distance(q)) // "5", 方法调用

1.2 方法调用

如上代码,调用方法是将p.Distance(q),其中p对应额外参数(即接收者),而q对应声明中参数列表。调用方式与Java如出一辙。

1.3 优势

相对于函数(命名空间是整个包),由于每个类型的方法有着独立的命名空间,这样就不需要担心命名冲突,命名可以更简短;此外,由于调用方式是p.Distance,此处省略了包名

2 指针接收者

由于go传参是值传递,类似于参数列表中如需修改变量或者太大拷贝浪费需要传递其指针,接收者也是一样。如下例,声明时接收者是其类型的指针,调用时括号是必须,由于*和.的优先级问题。

// set方法 修改方法
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

// 调用
(*Point).ScaleBy
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // "{2, 4}"
// go语法糖 隐式取地址&
p.ScaleBy(2)

Go中一个类型的接收者可以是其类型T、其指针*T,而调用时无论是T还是*T都是可以相互兼容的,这样可读性更高。只要简单理解指针的方法是修改方法,类型T的方法是查看方法,调用方式都可以一样。

声明接收者\调用者T *T pptr

T

(p Point) Distance

p.Distance(q)

✔(语法糖)隐式* 取值

pptr.Distance(q)  =  (*pptr).Distance(q)

T*

(p *Point) ScaleBy

✔(语法糖)

隐式& 取地址

p.ScaleBy(2)  =  (&p).ScaleBy(2)

pptr.ScaleBy(2)

3.组合

go的组合是通过结构体嵌套实现的,如下ColoredPoint类型嵌套了类型Point。

type Point struct{ X, Y float64 }
func (p Point) Distance(q Point) float64 {
	dX := q.X - p.X
	dY := q.Y - p.Y
	return math.Sqrt(dX*dX + dY*dY)
}

type ColoredPoint struct {
	Point      // 嵌套
	Color color.RGBA
}

// 匿名字段直接启用其子字段
var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // "1"

// 方法委托
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"

如下例,虽然声明形式像是Java中的组合,但是字段和方法的使用上,又像是继承的特性。

p.Distance(q.Point),其中p、q是ColoredPoint,p可以直接调用Point类型的方法,但是参数却不能直接传入q,

其实是因为Go编译器会根据嵌套类型的方法自动生成包装方法,委托给嵌套的Point执行。当一个类型T嵌入了另一个类型O,则T包含了O的所有方法,如果T有直接的方法,则出现复写(Override)的效果,编译器是先查找当前类型直接声明的方法,再查看嵌套类型的方法,最近原则,如果树深相同有两个类型相同的方法则编译报错(静态加载?)。

// Go编译器自动生成包装方法,委托给Point执行
func (p ColoredPoint) Distance(q Point) float64 {
    return p.Point.Distance(q)
}
func (p *ColoredPoint) ScaleBy(factor float64) {
    p.Point.ScaleBy(factor)
}

4.方法值

p.Distance返回了一个方法值,绑定了一个方法和特定接收值(类似于一个实例),可以直接用这个方法值只传入参数(后面的参数列表)即可调用。用图类似于函数值,可以传入API进行调用,与之不同的是可以指定一个接收者。

distanceFromP := p.Distance // method value
fmt.Println(distanceFromP(q)) // "5"

4.1 方法表达式

类型T有函数表达式T.f 或者 (*T).f,返回的是一个函数,它的接收者在成为第一个参数

distance := Point.Distance // method expression
fmt.Println(distance(p, q)) // "5"

方法表达式与方法值的区别如下:

引用含义
p.Distance

方法值

类型:func(Point) float64

Point.Distance

方法表达式,多了一个receiver参数

类型:func(Point,Point) float64

方法表达式的主要用途是当需要迭代或者其它有多个receiver,也有多种相同类型的方法需要选择。

func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }

func (path Path) TranslateBy(offset Point, add bool) {
	var op func(p, q Point) Point
	if add {
		op = Point.Add
	} else {
		op = Point.Sub
	}
	for i := range path {
		// 迭代,多个receiver,多个Add(offset) Sub(offset)选择
		path[i] = op(path[i], offset)
	}
}

5.封装

封装的含义是指一个对象的字段方法对其客户端不可见,也叫做信息隐藏。之前有说过包级组件可见性控制机制就是大小写,首字母大写导出包外,小写包装在包内。这个机制同样适用于struct或者type的字段和成员,也是包内外可见性的控制。PS:可见性控制的单元是

// 封装,虽然IntSet类型导出到了包外,但是其字段words没有,体现了封装
type IntSet struct {
    words []uint64
}
// 反例,包外直接可以修改底层切片
type IntSet []uint64 
  1. 客户端不能直接修改对象的字段
  2. 隐藏实现细节
  3. 不能随意set对象,防止破会不变式

Go的风格也是提供一些getter和setter,命名风格如下:

func (l *Logger) Flags() int
func (l *Logger) SetFlags(flag int)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值