方法声明
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加在函数前面的参数会将该函数附加到参数对应的类型上,即相当于为这种类型定义了一个独占的方法。
在能够给任意类型定义方法这一点上,Go和很多其它的面向对象的语言不太一样。因此在Go语言里,我们为一些简单的数值、字符串、slice
、map
来定义一些附加行为很方便。我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型不是指针或者interface
。
type Num struct {
X, Y float64
}
func (s Num) getSubtraction(t Num) float64 {
return s.X - t.Y
}
n1 := Num{1, 2}
fmt.Println(n1.getSubtraction(n1)) // -1
上面就是为Sub
类型定义了一个getSubtraction
方法,后续所有声明为Sub
类型的变量都可以直接调用该方法,而不用考虑包级别,并且方法的调用者s
中的值也可以参与到这个函数中来。
一般来说,函数在传参的时候都会对参数的值进行复制,对于接收函数值的类型来说,如果要更新一个其中的一个变量,只需要用到该类型的指针就好了,避免对该类型的进行不必要的复制,特别是变量比较多的情况下。
func (s *Num) getPlus(t Num) float64 {
return s.X + t.Y
}
n2 := &Num{10, 20}
fmt.Println(n2.getPlus(n1)) // 12
封装
一般来说,封装就是指一个对象的变量和方法的具体实现内容对方法的调用方是不可见的,隐藏不必要展示的细节,只暴露需要的参数和返回结果。
在Go中,我们知道是通过首字母的大小写来确定可见性的,对于变量、方法、以及结构体来说都是适用的,结构体内的变量首字母小写的话是无法在包外被访问到的,因而如果想封装一个对象,可以将其定义为一个struct
封装提供了三方面的优点。
- 因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
- 隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的
api
情况下能得到更大的自由。 - 阻止了外部调用方对对象内部的值任意地进行修改
使用标准包中的bytes.Buffer
类型来看下:
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
buf []byte // contents are the bytes buf[off : len(buf)]
off int // read at &buf[off], write at &buf[len(buf)]
lastRead readOp // last read operation, so that Unread* can work correctly.
}
struct
内的三个变量外面是访问不到的,你也不知道有这个三个变量的存在,但是在Buffer类型提供的各种方法中却是由着这个三个变量的和一些其他的变量来实现的
func (b *Buffer) String() string {
if b == nil {
// Special case, useful in debugging.
return "<nil>"
}
return string(b.buf[b.off:])
}
// empty reports whether the unread portion of the buffer is empty.
func (b *Buffer) empty() bool { return len(b.buf) <= b.off }
// Len returns the number of bytes of the unread portion of the buffer;
// b.Len() == len(b.Bytes()).
func (b *Buffer) Len() int { return len(b.buf) - b.off }
// Cap returns the capacity of the buffer's underlying byte slice, that is, the
// total space allocated for the buffer's data.
func (b *Buffer) Cap() int { return cap(b.buf) }
对象内部变量只可以被同一个包内的函数修改,所以包的作者可以让这些函数确保对象内部的一些值的不变性。