【从零单排Golang】第八话:通过cache缓存模块示范interface该怎么用

和许多面向对象的编程语言一样,Golang也存在interface接口这样的概念。interface相当于是一个中间层,下游只需要关心interface实现了什么行为,利用这些行为做些业务级别事情,而上游则负责实现interface,把这些行为具象化。本文就来通过一个简单的缓存cache模块的实现,来示范一下Golanginterface该怎么用。

首先,从业务service角度而言,一个cache模块可能需要以下几种方法:

  • 获取缓存中的某个值
  • 缓存数据,加缓存时效
  • 删除缓存内容

那么这些个方法,就可以用一类叫Cacheinterface来表示:

type Cache interface {
    Get(key string) (interface{}, bool)
    Set(key string, value interface{})
    SetExpire(key string, value interface{}, expire time.Duration)
    Delete(key string)
}

其中,Get方法返回一个interface{}value,以及是否存在的bool标识;SetSetExpire表示无时限跟有时限的缓存行为;Delete表示删除缓存内容。整块Cache的接口定义也非常明显。

这样写有什么好处?如果你是下游业务服务的话,你只需要这样写就可以了。这里给一个同package下的测试用例代码:

func TestCache(t *testing.T) {
    k, v := "hello", "world"
    // Current()的实现,在下文慢慢解释
    var curCache Cache = Current()

    // set & get & delete
    curCache.Set(k, v)
    cached, ok := curCache.Get(k)
    if !ok {
        t.Fatalf("cannot cache %s:%s", k, v)
    } else {
        t.Logf("got cached %s:%v (type: %s)", k, cached, reflect.TypeOf(cached).Name())
    }
    curCache.Delete(k)
    _, ok = curCache.Get(k)
    if ok {
        t.Fatalf("cannot delete %s:%s", k, v)
    } else {
        t.Logf("delete cached %s:%s", k, v)
    }

    // set expire
    curCache.SetExpire(k, v, 1*time.Second)
    cached, ok = curCache.Get(k)
    if !ok {
        t.Fatalf("cannot cache %s:%s", k, v)
    } else {
        t.Logf("got cached %s:%v (type: %s)", k, cached, reflect.TypeOf(cached).Name())
    }
    time.Sleep(3 * time.Second)
    _, ok = curCache.Get(k)
    if ok {
        t.Fatalf("cannot expire %s:%s", k, v)
    } else {
        t.Logf("expired %s:%s", k, v)
    }
}

可以看到,我们指定的缓存对象curCache标识为一个Cache,是个接口定义,这样标识起来的话,下面的代码就可以正常使用GetSet之类的方法了。而更重要的是,下面的代码,不会因为Cache的具体实现变化而有所变化。举个例子,你有10个开源的缓存库,想定时切换Current() Cache背后的缓存对象实现,就算你再怎么换,只要用到缓存的代码标注缓存对象为Cache这个interface,并且interface的定义没有变化,那么使用缓存的代码就不需要动。这样,就彻底实现了缓存提供方和使用方的解耦,开发效率也会噌噌噌的上去。

既然提到了提供方Provider的概念,那在缓存的实现上,就可以走依赖注入控制反转的模式。假设某个Web服务有个本地缓存模块,在实现上,就可以考虑提供多个Cache接口的实现,同时在配置里指定默认的一种。这里,就以go-cache为例,做一个实现案例。

import (
    "github.com/patrickmn/go-cache"
    "time"
)

const (
    GoCacheDefaultExpiration = 10 * time.Minute
    GoCacheCleanupInterval   = 15 * time.Minute
)

type GoCache struct {
    c *cache.Cache

    defaultExpiration time.Duration
    cleanupInterval   time.Duration
}

func (g *GoCache) Get(key string) (interface{}, bool) {
    return g.c.Get(key)
}

func (g *GoCache) Set(key string, value interface{}) {
    g.c.Set(key, value, GoCacheDefaultExpiration)
}

func (g *GoCache) SetExpire(key string, value interface{}, expire time.Duration) {
    if expire < 0 {
        expire = g.defaultExpiration
    }
    if expire > g.cleanupInterval {
        expire = g.cleanupInterval
    }
    g.c.Set(key, value, expire)
}

func (g *GoCache) Delete(key string) {
    g.c.Delete(key)
}

func NewGoCache() *GoCache {
    return &GoCache{
        c: cache.New(GoCacheDefaultExpiration, GoCacheCleanupInterval),

        defaultExpiration: GoCacheDefaultExpiration,
        cleanupInterval:   GoCacheCleanupInterval,
    }
}

当我们定义一个GoCachestruct,实现了Cache接口定义的所有行为,那么GoCache的实例,在Golang里,就能够被标识为一个Cache接口实例。NewGoCache方法,不仅是提供了一个GoCache的实例,而在业务层面,更是提供了一个Cache实例。因此,我们可以简单用一个map来管理所有的Cache的构造器,从而标识不同的缓存实现:

func provideGoCache() Cache {
    return NewGoCache()
}

var cacheProviders = map[string]Cache{
    "go-cache": provideGoCache(),
}

const (
    DefaultCacheProvider = "go-cache"
)

func Get(provider string) Cache {
    c, ok := cacheProviders[provider]
    if !ok {
        return nil
    }
    return c
}

func Default() Cache {
    return Get(DefaultCacheProvider)
}

// 上文提到的样例代码,就用了这个方法拿到go-cache实现的Cache接口实例
func Current() Cache {
    return Default()
}

显而易见,通过这样的一个代码组织,不论是go-cache,抑或是其它的Cache实现,都可以集中管理并灵活取用。这,便是interfaceGolang编程中给我们带来的便利了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值