golang方法和接口

时间:2024-11-01 16:36:56

一.  go方法

go方法:在函数的func和函数名间增加一个特殊的接收器类型,接收器可以是结构体类型或非结构体类型。接收器可以在方法内部访问。创建一个接收器类型为Type的methodName方法。

func (t Type) methodName(parameter list) {
}

go引入方法的原因:

1)go不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。

2)相同名字的方法可以定义在不同的类型上,而相同名字的函数不被允许。

方法调用

t.methodName(parameter list)

指针接收器与值接收器

区别:指针接收器的方法内部的改变对外可见,而值接收器不会改变方法外部的变量。

对于指针接收器&T Type而言,(&T).methodName与T.methodName等价。

匿名字段的方法

属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。

在方法中使用值接收器 与 在函数中使用值参数

当一个函数有一个值参数,它只能接受一个值参数。

当一个方法有一个值接收器,它可以接受值接收器和指针接收器。

package main

import "fmt"

type rectangle struct {
length int
width int
} func area(r rectangle){
fmt.Printf("Area Function result: %d\n", (r.length * r.width))
} func (r rectangle)area(){
fmt.Printf("Area method result: %d\n", (r.length * r.width))
} func main(){
r := rectangle{
length: ,
width: ,
} area(r)
r.area() p := &r
// area(p) // cannot use p (type *rectangle) as type rectangle in argument to area
p.area() //通过指针调用接收器
}

在方法中使用指针接收器 与 在函数中使用指针参数

函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。

在非结构体上的方法

为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。

对于内建类型,如int,应该在文件中创建一个类型别名,然后创建一个以该类型别名为接收器的方法。

二.  go接口

接口是方法(方法签名,method signature)的集合。当一个类型定义了接口中的所有方法,就称它实现了该接口。与OOP类似,接口定义了一个类型应该具有的方法,由该类型决定如何实现这些方法。

type myInterface interface{
method1()
method2()
}

接口调用

//interface definition
type VowelsFinder interface {
FindVowels() []rune
} type MyString string //MyString implements VowelsFinder
func (ms MyString) FindVowels() []rune {
var vowels []rune
for _, rune := range ms {
if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
vowels = append(vowels, rune)
}
}
return vowels
} name := MyString("Sam Anderson")
var v VowelsFinder
v = name // possible since MyString implements VowelsFinder
fmt.Printf("Vowels are %c", v.FindVowels())

如果一个类型包含了接口中声明的所有方法,那么它就隐式地实现了 Go 接口

接口的内部表示

可以把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。

type Test interface {
Tester()
} type MyFloat float64 func (m MyFloat) Tester() {
fmt.Println(m)
} func describe(t Test) {
fmt.Printf("Interface type %T value %v\n", t, t)
} func main() {
var t Test
f := MyFloat(89.7)
t = f
describe(t)
t.Tester()
}

输出:

Interface type main.myFloat value 89.7
89.7

空接口

没有包含方法的接口称为空接口。空接口表示为 interface{}。由于空接口没有方法,因此所有类型都实现了空接口。

当指定参数为空接口时,可以接收任意类型,那如何获取参数的值呢?  通过类型断言。 v, ok := p.(int),判定参数是否为int并获取参数值。

类型断言

类型断言用于提取接口的底层值(Underlying Value)。

在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。

v, ok := i.(T)

如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。

如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错

类型选择(Type Switch)

类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。

类型断言的语法是 i.(type),获取接口的类型

还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。

type Describer interface {
Describe()
} type Person struct {
name string
age int
} func (p Person) Describe(){
fmt.Printf("%s is %d years old\n", p.name, p.age)
} func findType(i interface{}){
switch v := i.(type){
case Describer:
v.Describe()
default:
fmt.Printf("unknown type\n")
}
} func main(){
findType("wang")
p := Person{
name: "qing",
age: ,
} findType(p)
}
unknown type
qing is years old

在上面程序中,结构体 Person 实现了 Describer 接口。在第 19 行的 case 语句中,v 与接口类型 Describer 进行了比较。p 实现了 Describer,因此满足了该 case 语句,于是当程序运行到第 32 行的 findType(p) 时,程序调用了 Describe() 方法。

实现接口:指针接受者与值接受者

使用值接受者声明的方法,接口既可以用值来调用,也能用指针调用。

对于使用指针接受者的方法,必须用一个指针或者一个可取得地址的值(&method)来调用。但接口中存储的具体值(Concrete Value)并不能取到地址,对于编译器无法自动获取 a 的地址,于是程序报错。

type Describer interface {
Describe()
}
type Person struct {
name string
age int
} func (p Person) Describe() { // 使用值接受者实现
fmt.Printf("%s is %d years old\n", p.name, p.age)
} type Address struct {
state string
country string
} func (a *Address) Describe() { // 使用指针接受者实现
fmt.Printf("State %s Country %s", a.state, a.country)
} func main() {
var d1 Describer
p1 := Person{"Sam", }
d1 = p1
d1.Describe()
p2 := Person{"James", }
d1 = &p2
d1.Describe() var d2 Describer
a := Address{"Washington", "USA"} /* 如果下面一行取消注释会导致编译错误:
cannot use a (type Address) as type Describer
in assignment: Address does not implement
Describer (Describe method has pointer
receiver)
*/
//d2 = a d2 = &a // 这是合法的
// 因为在第 22 行,Address 类型的指针实现了 Describer 接口
d2.Describe()
}

接口的嵌套

type SalaryCalculator interface {
DisplaySalary()
} type LeaveCalculator interface {
CalculateLeavesLeft() int
} type EmployeeOperations interface {
SalaryCalculator
LeaveCalculator
}

接口的零值

接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。

Go接口最佳实践

1)倾向于使用小的接口定义,很多接口只包含一个方法。    如Reader,Writer,便于类型实现接口,方法太多,类型实现越麻烦。

2)较大的接口定义,可以由多个小接口定义组合而成。  即接口的嵌套。

3)只依赖于必要功能的最小接口。方法或函数的接口参数的范围或方法越小越好,这样便于参数的调用,和方法或函数被其他程序调用。

如func StoreData(reader Reader) error{},能传递Reader就不传递ReadWriter。

参考:Go 系列教程 —— 17. 方法     Golang tutorial series  英文原版