文章目录
1. interface 是什么?有什么用?
1.1 日常生活中的 interface 举例
- KFC的招牌就是一个接口,挂了KFC的招牌,我们不用进去就知道他家卖哪些食物,就可以直接去点上校鸡块、黄金烤鸡腿堡。没有挂这个招牌,就算卖的东西和KFC一模一样,我们不进去看菜单就不会知道。
- 参考链接:https://www.zhihu.com/question/20111251/answer/16585393
1.2 golang 中interface的定义和用途
- golang 中 interface的定义
- interface 可以表示一种类型(任意一种类型)
- interface 是方法的集合(也就是接口的方法集合)
- 只要实现了接口中的所有方法,那么就认为你实现了这个接口
- interface的实际用途
- 一:多态的实现
- 二:隐藏函数的具体实现
- 三:中间层,解耦上下层依赖
- ps: 往后看从用途和示例去理解interface是什么,为什么要用interface
2. interface-实现多态
2.1 示例一:同理于编程最常见的鸭子说法
- 1,可以看到以下代码,实例化的Dog和Cat都可以传入introduceSelf,所以这里就实现了多态,因为传入的类型是不确定的
- 2,还有一点就是,新手可能会说这么麻烦,我直接实例化Dog后,调用对应要用的函数就好了。其实最开始写代码的时候我也是这么想的。所以我这里示例introduceSelf 是做了两件事,也就是说大家要考虑函数组合使用的,如果分别调用则会要做很多重复的操作,且不方便后期维护
package test
import (
"fmt"
"testing"
)
// 定义一个动物的接口
type animal interface {
Say() string
Color() string
}
// 可以理解cat类
type Cat struct{}
func (c Cat) Say() string { return "i am a cat" }
func (c Cat) Color() string {
return "i am black"
}
// 可以理解为dog类
type Dog struct{}
func (d Dog) Say() string { return "i am a dog" }
func (d Dog) Color() string {
return "i am white"
}
// 可以理解为汽车
type Car struct{}
func introduceSelf(input animal) {
fmt.Println(input.Say() + " and " + input.Color())
}
func TestMain1(t *testing.T) {
c := Cat{}
d := Dog{}
introduceSelf(c)
introduceSelf(d)
//car := Car{}
//printSelf(car) 这两行不注释就会报错,因为car没有实现animal接口
}
2.2 举例二:golang中的排序
- 1,这是一个排序的实现,通过实现Len,Swap,Less函数实现了sort的interface,从而调用sort.Sort然后实现排序(sort.Sort里面通过组合调用这三种方法进行了排序)
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%s: %d", p.Name, p.Age)
}
// ByAge implements sort.Interface for []Person based on
// the Age field.
type ByAge []Person //自定义
func (a ByAge) Len() int { return len(a) }
func (a ByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
people := []Person{
{"Bob", 31},
{"John", 42},
{"Michael", 17},
{"Jenny", 26},
}
fmt.Println(people)
sort.Sort(ByAge(people))
fmt.Println(people)
}
3. interface-隐藏函数具体实现
3.1 隐藏函数具体实现有什么好处?
- 代码可阅读角度:简单易读,代码本来就是写给人看的,那自然是越容易懂越好
3.2 示例一:语言角度-golang中的context包
- withCancel 和 WithValue 返回的第一个参数都是context
- 但是各自返回的Context结构体又不是一样的
- withCancel : 返回结构体为 cancelCtx
- WithValue: 返回的结构体为 valueCtx
- 这样的话尽管返回的都是context,但是具体实现却不一样,实现了功能的多样化
// 这里开始是 withCancel
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
c := newCancelCtx(parent)
propagateCancel(parent, &c)
return &c, func() { c.cancel(true, Canceled) }
}
// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}
// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
Context
done chan struct{} // closed by the first cancel call.
mu sync.Mutex
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
// 这里开始是 WithValue
func WithValue(parent Context, key, val interface{}) Context {
if parent == nil {
panic("cannot create context from nil parent")
}
if key == nil {
panic("nil key")
}
if !reflectlite.TypeOf(key).Comparable() {
panic("key is not comparable")
}
return &valueCtx{parent, key, val}
}
// A valueCtx carries a key-value pair. It implements Value for that key and
// delegates all other calls to the embedded Context.
type valueCtx struct {
Context
key, val interface{}
}
3.3 示例二:设计角度-sql
- 如下图,我们只需要输入sql,后面具体的实现是mysql,还是pg, 还是sqlite我们是不需要关心的
4. interface-解耦上下游的依赖
- 上下游依赖没有那么重,维护起来就很好维护了
4.1 示例一:用户权限校验的实现
package main
// 假设这是redis客户端
type Redis struct {
}
func (r Redis) GetValue(key string) string {
panic("not implement")
}
// 不通过接口实现:检查用户是否有权限的功能
func AuthExpire(token string, rds Redis) bool { // 这里要修改
res := rds.GetValue(token) // 这里可能也要修改
if res == "" {
return false
} else {
// 正常处理
return true
}
}
// 这里如果有其他的函数引用了 rds Redis, 那肯定也全部要改
// .......
func main() {
token := "test"
rds := Redis{} // 这里要修改
AuthExpire(token, rds) // 这里rds这个名字可能要修改
}
- 这是用接口实现检查一个用户的token
package main
type Cache interface {
GetValue(key string) string
}
// 假设这是redis客户端
type Redis struct {
}
func (r Redis) GetValue(key string) string {
panic("not implement")
}
// 假设这是自定义的一个缓存器
type MemoryCache struct {
}
func (m MemoryCache) GetValue(key string) string {
panic("not implement")
}
// 通过接口实现:检查用户是否有权限的功能
func AuthExpire(token string, cache Cache) bool {
res := cache.GetValue(token)
if res == "" {
return false
} else {
// 正常处理
return true
}
}
func main() {
token := "test"
cache := Redis{} // Cache := MemoryCache{},修改这一句即可
AuthExpire(token, cache)
}
- 1, 功能都可以实现,甚至不用接口的代码量更小
- 2,如果我们的缓存(存储用户的组件)从redis换成了MemoryCache,
- 3,首先我们都需要先编写MemoryCache实现原有redis的功能
- 4,不用接口的时候,你的代码还需要更改的地方是所有用到了此缓存的函数。也就是说你需要去更改所有引用了Redis这个结构体的地方。示例中可能只是一个authExpire,但是实际上可能还有很多类似的函数引用了redis做缓存
- 5,如果我们用了接口,那么我们需要修改的地方除开第三步都需要实现具体的功能,还有仅仅可能就是一个初始化的地方
5. interface-最最最常见的使用场景分析
- 一般大家都看到过类似下面这样的代码,接下来我列出来代码中几个疑问点,并一一说出自己的理解
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HIkx0EPy-1626013816563)(https://cdn.nlark.com/yuque/0/2021/png/12416047/1624754574093-7cc05b42-96d7-44f3-9241-98a3814f32b9.png#align=left&display=inline&height=804&id=P0KFw&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1608&originWidth=2490&size=477539&status=done&style=none&width=1245)]
- 我们为什么要提供New方法?
- 因为我们的service struct 是小写开头,所以如果我们不用New方法的话,外部就没办法调用这个struct
- 我们打算对外提供的 service struct那为什么要小写开头?
- 因为如果我们大写开头,那么别人调用的时候,struct中可能部分元素不给赋值,则默认空值。这样会导致service的部分方法调用时会Panic。所以这里相当于是一个强制调用者必须按照我们的定义(也就是New方法)进行传参赋值
- 那我们可以通过New方法对外进行返回service结构体从而提供服务,为什么要返回接口呢?
- 第一个,用接口的话,使用者点进来第一时间就知道你提供了哪些方法(全部暴露在interface),简单明了,不用的话我还得一个个去找你对外提供了哪些服务,你这10个文件,我十个都需要看一下
- 第二个,如果我们不使用接口,那么我们就和golang的基本理念相违背了 包中小写开头类型不对外进行暴露 原则(这里可以编译通过,但是idea会黄色提示),而使用接口则我们对外暴露接口即可
- 第三个,如果返回的是方法不是接口,那我们go doc里面生成的文档其实是不会展示这些方法的,而写成接口,则会在go doc中接口这里进行展示
- *里面有一行代码 var _ Service = (service)(nil) 这句代码是用来做什么的?
- 这个其实在很多开源项目中会这样写,具体用处就是校验*service这个结构体,有没有实现Service这个接口
- 可参考这个github issue:https://github.com/uber-go/guide/issues/25
6. 参考文章
how-to-use-interfaces-in-go:https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-go
empty interface:https://www.calhoun.io/how-do-interfaces-work-in-go/
what, how,why: http://legendtkl.com/2017/06/12/understanding-golang-interface/
go 设计与实现: https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/
why-i-use-interfaces:https://krancour.medium.com/go-pointers-why-i-use-interfaces-in-go-338ae0bdc9e4