求求你们,不要再往接口里面乱加方法了
source link: https://www.zenlife.tk/stop-add-method-to-interface.md
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
求求你们,不要再往接口里面乱加方法了
2020-03-09
真是受不了项目里面,一些接口被搞得越来越庞大,所以来吐糟了。rob pike 教导我们说,接口应该尽量地小:
The bigger the interface, the weaker the abstraction – Rob Pike
只有小的接口,才是可组合的。否则去写 java 好了,为啥要来写 Go 呢?
最糟糕的一类是这样子写的:
type I interface {
...
XX()
}
type T1 struct {}
func (t T1) XX() {
}
type T2 struct {}
func (t T2) XX() {
panic("unsupported")
}
如果定义了一个接口中,某些实现需要做,而另一些实现又不支持的,说明此方法根本不通用!即使不是通用的方法,为什么要往接口里面塞呢?Don't panic,反面教材。
其次糟糕的一类:
func f(t I) {
switch t.(type) {
case T1:
case T2:
}
}
都定义了接口了,却需要用 type switch 去看底层是哪种实现,这说明这个接口抽象的不好呀!依赖于接口而不是实现。
还一类常见的糟糕的地方,冗余的方法全部往接口里面塞,这里一个反面教材:
type Table interface {
...
AllocHandle(ctx sessionctx.Context) (int64, error)
AllocHandleIDs(ctx sessionctx.Context, n uint64) (int64, int64, error)
Allocator(ctx sessionctx.Context, allocatorType autoid.AllocatorType) autoid.Allocator
AllAllocators(ctx sessionctx.Context) autoid.Allocators
}
Table 接口里面为什么有这么多 Alloc
相关的方法?而且,其中一些还是可以用另一些推导出来的。难道直接定义返回一个 allocator,不香么?难道把各个 table 各个实现里面都加几个这样的函数很有意思么?
对冗余的处理,我们可以简单的只保留一个。剩下的去包装一下就好。比如,下面都是等价的写法。
func AllocHandle(ctx sessionctx.Context, t Table) (int64, error) {
allocator := t.Allocator(ctx)
allocator.Alloc(id)
}
AllocHandle(ctx, t)
func (t *tableImpl) AllocHandle(ctx sessionctx.Context) (int64, error) {
...
}
t.AllocHandle(ctx)
区别是前一种不会把冗余的方法丢到接口定义里面,多个不同的 Table 实现,不会出现代码冗余:
func (t1 T) AllocHandle() { ...}
func (t2 T) AllocHandle() { ...}
func (t3 T) AllocHandle() { ...}
有些接口有一个默认实现,算是基类吧,然后派生类都是继承这个基类的,通常使用的结构体嵌入的形式。
type base struct {}
type derive struct {
base
...
}
随着代码的腐烂,基类里面的方法越来越多了。然后这个接口也就越来越大,越来越恶心了。比如说这里有一个坏的例子。
type Executor interface {
Open(context.Context) error
Next(ctx context.Context, req *chunk.RecordBatch) error
Close() error
Schema() *expression.Schema
// 这里就把基类的一些细节在往接口里面加,接口就被搞大了
retTypes() []*types.FieldType
newFirstChunk() *chunk.Chunk
}
这就涉及到对共同部分的处理。既然基类是所有继承类都是带的,我们可以在接口里面定义一个方法把基类返回出去。这样往基类加东西就不会影响接口的方法:
type Executor interface {
base() *baseExecutor
Open(context.Context) error
Next(ctx context.Context, req *chunk.Chunk) error
Close() error
Schema() *expression.Schema
}
好的例子。
再说一个场景,即想调用基类的公共函数,又想派生类中重载,要怎么搞?
type base struct{}
func (b base) Method() {}
type derived struct {
base
}
var d derived
d.Method() // 这个会调用到 base 的 Method()
如果用 derived
重写掉 Method()
,每个派生类都要把这一段代码重写一遍:
type derivd1 struct{}
func (d derived1) Method() {
d.base.Method()
...
}
type derived2 struct{}
func (d derived2) Method() {
d.base.Method()
...
}
答案是,用函数包装:
func Method(b derived) {
f.Base()
...
}
多写写函数,把接口对象当参数传进去,效果一样的,而不必在接口添加方法,好好体会一下。
随便你们怎么写代码,总之,求你们别再往接口里面乱加方法了!
Recommend
-
55
稳住,今天是周末,本文属于节假日不定期抽风推送。 之前回复大家学不动的留言,说一定要做一个学不动的漫画,来啦~
-
57
程序员 - @SoulSleep - 你女朋友需要的是,你把七夕做网页这精湛的技术拿来赚钱,然后给她买个包、口红、sk2、lamer....你老婆需要的是,你把七夕做网页这精力剩下,然后给她买一身衣服、一件首饰....做一
-
44
最近一段时间,微博、朋友圈都被程序员刷屏了。
-
29
全球工单系统 - @co3site - 一大早自动更新了,定位权限现在不给不行,还有这耗电量,5 分钟可以掉 2%,你们非得赶尽杀绝吗? 很多联系人已经被你们栓牢了,不用你们的产品还不行,算我求求你们了
-
31
平时我们在切换 Widget 的时候是怎样的? 有没有动画效果?是不是直接改变了一个 Widget? 类似于这样的: 如果是的话,那...
-
48
点击上方“ 腾讯科技 ”,“星标或置顶公众号” 关键时刻,第一时间送达
-
34
最近公司来了一批实习生,阿粉负责带一个。这位小师弟说实话,基本功很扎实,做事也非常靠谱,深得阿粉真传。 不过最近给其 Review 代...
-
50
背景软件开发过程中,不可避免的是需要处理各种异常,就我自己来说,至少有一半以上的时间都是在处理各种异常情况,所以代码中就会出现大量的try {...} catch {...} finally {...} 代码块,不仅有大量的冗余代码,而且还影响代码的可读性。比较下面两张图,看看您...
-
2
求求你们,别用这“羞羞”恋爱软件了凡事得多留个心眼说起“网恋”这件事,不知道在座的小伙伴们,有没有亲身实践过的经历呢?在小雷年纪尚小那会儿,网恋算得上是个新鲜事物,还没有现在这么多负面评价。那时候大家...
-
4
日本电车沙雕广告,求求你们不要打了 作者: 源泉 ...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK