Interface —— defines the behavior of an object
The Interface only specifies what should do. It's a collection of Method Signature(方法签名)。
As for how to implement the behavior, the object itself determines; When a type defines all the methods in the interface, we call it implement the interface.
package main
import "fmt"
//interface definition, 创建了一个名为 VowelsFinder 的接口,该接口有一个 FindVowels() []rune 的方法。
type VowelsFinder interface {
FindVowels() []rune
}
//MyString implements VowelsFinder
type MyString string
// 方法:在func 和 名儿中间加了一个receiver type,比如这个 MyString 在方法内部可以用ms指代MyString使用。
// 元素类型为rune的切片: []rune
// 给Receiver Type MyString 添加了方法 FindVowels() []rune,现在,我们称 MyString 实现了 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
}
func main () {
name := MyString("Sam Anderson") // name 的类型为 MyString
var v VowelsFinder // v 的类型为 VowelsFinder
v = name // 我们把 name 赋值给了 v。由于 MyString 实现了 VowelFinder,因此这是合法的
fmt.Print("Vowels are %c", v.FindVowels())
}
package main
import "fmt"
// 声明了一个 SalaryCalculator 接口类型,它只有一个方法 CalculateSalary() int。
type SalaryCalculator interface {
CalculateSalary() int
}
type Permanent struct {
empId int
basicpay int
pf int
}
type Contract struct {
empId int
basicpay int
}
//salary of permanent employee is sum of basic pay and pf
func (p Permanent) CalculateSalary() int {
return p.basicpay + p.pf
}
//salary of contract employee is the basic pay alone
func (c Contract) CalculateSalary() int {
return c.basicpay
}
/*
total expense is calculated by iterating though the SalaryCalculator slice and summing
the salaries of the individual employees
*/
func totalExpense(s []SalaryCalculator) {
expense :=0
for _, v := range s {
expense = expense + v.CalculateSalary()
}
fmt.Printf("total expense per month $%d", expense)
}
func main () {
pemp1 := Permanent {
1, 5000, 20}
pemp2 := Permanent {
2, 6000, 30}
cemp1 := Contract {
3, 3000}
employees := []SalaryCalculator{
pemp1, pemp2, cemp1}
totalExpense(employees)
}
上面程序的第 7 行声明了一个 SalaryCalculator
接口类型,它只有一个方法 CalculateSalary() int
。
在公司里,我们有两类员工,即定义的结构体:Permanent
和 Contract
。长期员工(Permanent
)的薪资是 basicpay
与 pf
相加之和,而合同员工(Contract
)只有基本工资 basicpay
。方法 CalculateSalary
分别实现了以上关系。由于 Permanent
和 Contract
都声明了该方法,因此它们都实现了 SalaryCalculator
接口。
声明的 totalExpense
方法体现出了接口的妙用。该方法接收一个 SalaryCalculator
接口的切片([]SalaryCalculator
)作为参数。我们向 totalExpense
方法传递了一个包含 Permanent
和 Contact
类型的切片。通过调用不同类型对应的 CalculateSalary
方法,totalExpense
可以计算得到支出。
这样做最大的优点是:totalExpense
可以扩展新的员工类型,而不需要修改任何代码。假如公司增加了一种新的员工类型 Freelancer
,它有着不同的薪资结构。Freelancer
只需传递到 totalExpense
的切片参数中,无需 totalExpense
方法本身进行修改。只要 Freelancer
也实现了 SalaryCalculator
接口,totalExpense
就能够实现其功能。
该程序输出 Total Expense Per Month $14050
。
我们在讨论方法的时候就已经提到过,使用值接受者声明的方法,既可以用值来调用,也能用指针调用。不管是一个值,还是一个可以解引用的指针,调用这样的方法都是合法的。
package main
import "fmt"
type Describer interface {
Describe()
}
type Person struct {
name string
age int
}
// 结构体Person使用值接受者,实现了Describer接口
func (p Person) Describe() {
fmt.Printf("%s is %d years oldn", 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)
}
// p1的类型是Person,在第 29 行,p1赋值给了d1。
// 由于Person实现了接口变量d1,因此在第 30 行,会打印Sam is 25 years old。
func main() {
var d1 Describer // 定义d1是Describer接口变量
p1 := Person{
"Sam", 25} // P1类型是Person
d1 = p1 // p1赋值给了d1
d1.Describe() // 由于Person实现了接口变量d1,因此会打印Sam is 25 years old。
p2 := Person{
"James", 32}
d1 = &p2
d1.Describe() // 与p1同理,值接受者能用指针调用,因此会打印James is 32 years old
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)
为何会报错呢?
对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用都是合法的。
但接口中存储的具体值(Concrete Value)并不能取到a的地址,于是程序报错。
*/
//d2 = a
d2 = &a // 这是合法的
// 因为在第 22 行,Address 类型的指针实现了 Describer 接口
d2.Describe()
}
package main
import (
"fmt"
)
// 声明接口 SalaryCalculator
type SalaryCalculator interface {
DisplaySalary()
}
// 声明接口 LeaveCalculator
type LeaveCalculator interface {
CalculateLeavesLeft() int
}
// 定义结构体 Employee
type Employee struct {
firstName string
lastName string
basicPay int
pf int
totalLeaves int
leavesTaken int
}
// 结构体Employee实现SalaryCalculator接口的DisplaySalary方法
func (e Employee) DisplaySalary() {
fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}
// 结构体Employee又实现了LeaveCalculator接口的CalculateLeavesLeft方法,于是Employee就实现了SalaryCalculator和LeaveCalculator两个接口。
func (e Employee) CalculateLeavesLeft() int {
return e.totalLeaves - e.leavesTaken
}
func main() {
e := Employee {
firstName: "Naveen",
lastName: "Ramanathan",
basicPay: 5000,
pf: 200,
totalLeaves: 30,
leavesTaken: 5,
}
var s SalaryCalculator = e // s 是 SalaryCalculator类型的接口变量,e被赋值给s
s.DisplaySalary()
var l LeaveCalculator = e // e 的类型 Employee 实现了两种接口,所以是合法的
fmt.Println("nLeaves left =", l.CalculateLeavesLeft())
}
package main
import (
"fmt"
)
type SalaryCalculator interface {
DisplaySalary()
}
type LeaveCalculator interface {
CalculateLeavesLeft() int
}
// 创建了一个新的接口 EmployeeOperations, 它嵌套了2个接口, 如果一个类型定义了它嵌套的所有接口里面包含的方法(SalaryCalculator,LeaveCalculator),我们就称这类型实现了它(EmployeeOperations)
type EmployeeOperations interface {
SalaryCalculator
LeaveCalculator
}
type Employee struct {
firstName string
lastName string
basicPay int
pf int
totalLeaves int
leavesTaken int
}
// 由于Employee结构体定义了DisplaySalary和CalculateLeavesLeft方法,因此它实现了接口EmployeeOperations。
func (e Employee) DisplaySalary() {
fmt.Printf("%s %s has salary $%d", e.firstName, e.lastName, (e.basicPay + e.pf))
}
func (e Employee) CalculateLeavesLeft() int {
return e.totalLeaves - e.leavesTaken
}
func main() {
e := Employee {
firstName: "Naveen",
lastName: "Ramanathan",
basicPay: 5000,
pf: 200,
totalLeaves: 30,
leavesTaken: 5,
}
var empOp EmployeeOperations = e // empOp的类型是EmployeeOperations,e的类型是Employee,我们把empOp赋值为e。empOp调用了DisplaySalary()和CalculateLeavesLeft()方法
empOp.DisplaySalary()
fmt.Println("nLeaves left =", empOp.CalculateLeavesLeft())
}
参考原文:
『GCTT 出品』Go 系列教程 ——第 19 部分:接口(二)mp.weixin.qq.com 『GCTT 出品』Go 系列教程 ——第 18 部分:接口(一)mp.weixin.qq.com