Go基础之interface
1 定义
interface 类型可以定义一组方法,用来表示一个对象的行为特征。interface不能包含任何变量。接口是抽象的,是行为规范,不是实例。
package main import( "fmt" ) //接口规范两个方法 type Animal interface{ Eat() Talk() } // 定义Dog结构体,并实现Animal接口中的所有方法 type Dog struct {} func (d *Dog) Eat() { fmt.Println("dog eating...") } func (d *Dog) Talk() { fmt.Println("dog talking...") } // 定义Cat结构体,并实现Animal接口中的所有方法 type Cat struct {} func (c *Cat) Eat() { fmt.Println("cat eating...") } func (c *Cat) Talk() { fmt.Println("cat talking...") } func main() { var a Animal var d Dog d.Eat() a = &d //Dog实现了Animal中的所有方法,因此Animal实例可被Dog实例赋值 a.Eat() var c Cat c.Eat() a = &c a.Talk() }
注:interface是引用类型。
(1)go中的接口不需要显式的实现。只要一个对象实现了一个接口类型中的所有方法,那么这个对象就实现了这个接口。
(2)如果一个对象实现了多个interface类型的方法,那么这个对象就实现了多个接口。
(3)多态:一种事物的多种形态都可以按照统一的接口进行操作。
比如我们要统一对动物调用某个方法,都可以通过接口的多态性来做:
package main import( "fmt" ) //接口规范两个方法 type Animal interface{ Eat() Talk() } // 定义Dog结构体,并实现Animal接口中的所有方法 type Dog struct { Name string } func (d *Dog) Eat() { fmt.Printf("%s is eating...\n", d.Name) } func (d *Dog) Talk() { fmt.Printf("%s is talking...\n", d.Name) } // 定义Cat结构体,并实现Animal接口中的所有方法 type Cat struct { Name string } func (c *Cat) Eat() { fmt.Printf("%s is eating...\n", c.Name) } func (c *Cat) Talk() { fmt.Printf("%s is talking...\n", c.Name) } func main() { var myAnimal []Animal d1 := &Dog{ Name:"阿黄", } myAnimal = append(myAnimal, d1) d2 := &Dog{ Name:"旺财", } myAnimal = append(myAnimal, d2) c1 := &Cat{ Name:"小七", } myAnimal = append(myAnimal, c1) for _, v := range myAnimal{ v.Eat() } }
这段代码的输出结果是:
阿黄 is eating... 旺财 is eating... 小七 is eating...
这里我们可以和原生支持多态的Python对比一下,因为Python中声明变量的时候不需要声明其类型,原生支持多态,写起来确实很爽。
2 空接口
一个方法都没有的接口就是空接口:
interface {}
所有类型都实现了空接口,也就是任何类型都可以赋值给空接口。这有点像弱类型语言了。
package main import( "fmt" ) func main() { var a interface {} var b int b = 1000 a = b fmt.Println(a) }
3 接口嵌套
形如:
type ReadWrite interface { Read(b Buffer) bool Write(b Buffer) bool } type Lock interface { Lock() Unlock() } type File interface { ReadWrite Lock Close() }
嵌套之后的File interface包含5个方法,若要实现这个接口需要实现这5个方法。
4 类型断言
如果我们要知道这个接口变量里面实际存储的是哪个类型的对象可以采用以下方法进行转换。
方法一:强制转换,如果失败,则挂掉
package main import ( "fmt" ) func typeRaise() { var t int = 100 var x interface{} x = t y := x.(int) // 强制转换 fmt.Println(y) } func main() { typeRaise() // 强制转换 }
方法二:先判断再转换(推荐)
package main import ( "fmt" ) func typeRaiseJustify() { var t int = 100 var x interface{} x = t y, ok := x.(string) // 强制转换 if !ok { fmt.Println("convert faild!\n") } fmt.Println(y) } func main() { typeRaiseJustify() //带判断类型转换 }
例子,获取变量类型:
package main import ( "fmt" ) func justify (items ...interface{}) { for i, v := range items { switch v.(type) { case bool: fmt.Printf("第%d个参数是bool\n", i) case int32: fmt.Printf("第%d个参数是int32\n", i) case string: fmt.Printf("第%d个参数是string\n", i) } } } func main() { var a int32 var b string var c bool justify(a, b, c) }
判断一个变量是否实现了指定接口:
package main import( "fmt" ) //接口规范两个方法 type Animal interface{ Eat() Talk() } // 定义Dog结构体,并实现Animal接口中的所有方法 type Dog struct { Name string } func (d *Dog) Eat() { fmt.Printf("%s is eating...\n", d.Name) } func (d *Dog) Talk() { fmt.Printf("%s is talking...\n", d.Name) } // 定义Cat结构体,并实现Animal接口中的所有方法 type Cat struct { Name string } func (c *Cat) Eat() { fmt.Printf("%s is eating...\n", c.Name) } func (c *Cat) Talk() { fmt.Printf("%s is talking...\n", c.Name) } func justify() { d := &Dog { Name:"BUCKER", } var v interface{} = d if dog, ok := v.(Animal); ok { dog.Eat() } } func main() { justify() }
5 实战:实现负载均衡接口
我们可以写一个具有随机、轮询算法的负载均衡接口。
package main import ( "fmt" "math/rand" ) // [1]定义负载均衡接口,及方法 type LoadBalance interface { DoBalance ([]string) string } // [2] 定义随机算法结构体 RandBalance,并实现LoadBalance接口 type RandBalance struct {} func (r *RandBalance) DoBalance(addrList []string) string { // 此处省略判断ip是否是活的等业务逻辑 l := len(addrList) index := rand.Intn(l) return addrList[index] } // [3] 定义轮询算法结构体 PollBalance,并实现LoadBalance接口 type PollBalance struct { curIndex int } func (p *PollBalance) DoBalance(addrList []string) string { // 此处省略判断ip是否是活的等业务逻辑 index := p.curIndex % (len(addrList)) // 取余,防止index溢出 addr := addrList[index] p.curIndex++ return addr } // [4] 通过接口实现多态 func doBalance(balance LoadBalance, addrList []string) (addr string) { return balance.DoBalance(addrList) } func main() { // 随机生成IP地址切片 var addrList []string for i:=0; i<5; i++ { addr := fmt.Sprintf("%d.%d.%d.%d:80", rand.Intn(255), rand.Intn(255), rand.Intn(255), rand.Intn(255)) addrList = append(addrList, addr) } fmt.Println("addrList:", addrList) // 通过配置balanceFunc变量,调用不同的算法 var balanceFunc string = "poll" var balance LoadBalance if balanceFunc == "random" { balance = &RandBalance{} } else if balanceFunc == "poll" { balance = &PollBalance{} } // 10次调用负载均衡算法算出IP for i:=0; i<10; i++ { addr := doBalance(balance, addrList) fmt.Printf("Index %d <--> Addr %s\n", i, addr) } }
生产环境中,可以将“random” /“poll”等存到K/V存储中,负载均衡代码定期读取K/V拿到值,实现负载均衡算法切换。