代码组织
client plugin的代码组织如下:
这里提供了一种容器类代码的组织方式。pluginContainer
作为 plugin
的容器,其中的方法有两类:第一类就是 Add
, Remove
和 All
这类的方法,作为容器的基本方法;第二类就是剩下的方法,其中逻辑大致就是遍历 plugin
并调用每一个 plugin
的方法。
Plugin
接口没有定义任何方法。而是又额外的定义了一堆 interface
像 PreCallPlugin interface
。这样做的好处就是可以在自己实现 plugin
接口的时候,不用实现所有已经定义的函数,只需定义自己需要的就可以。其他接口不一定是一个 interface
只有一个函数,可以有多个,作为一组,成为一个约束(如果要实现,就得实现一组全部函数,可以参考rpcx server plugin)。
可以参考下图,对这两种实现方式考虑下优缺点:
技术细节
1 接口和实现的关系
pluginContainer
实现了 PluginContainer
接口。因为在具体实现中的方法的接口者一般都是指针类型(因为要修改成员变量plugin []Plugin
的值)。所以一般&pluginContainer
和PluginContainer
的类型是一致的。
2 struct 的相等判定条件
在 pluginContainer.Remove()
函数中,有如下代码:
var plugins []Plugin
for _, pp := range p.plugins {
if pp != plugin {
plugins = append(plugins, pp)
}
}
p.plugins = plugins
这里有两个点,第一点就是 struct
的相等判定条件,就是只有值和类型都相等的时候,相等判定条件才为 true
,具体看下面代码:
package main
import "fmt"
type pluginContainer struct {
plugins []Plugin
}
type Plugin interface {
}
type APlugin struct {
A int
}
type BPlugin struct {
B int
}
func main() {
var a Plugin
var b Plugin
//类型相同,实现类型相同,值相同
a = APlugin{A: 1}
b = APlugin{A: 1}
if a == b {
fmt.Println("APlugin{A: 1} == APlugin{A: 1}")
}
//类型相同,实现类型相同,值不同
a = APlugin{A: 1}
b = APlugin{A: 2}
if a == b {
fmt.Println("APlugin{A: 1} == APlugin{A: 2}")
}
//类型相同,实现类型不同,值相同
a = APlugin{A: 1}
b = BPlugin{B: 1}
if a == b {
fmt.Println("APlugin{A: 1} == BPlugin{B: 1}")
}
//类型相同,实现类型不同,值不同
a = APlugin{A: 1}
b = BPlugin{B: 2}
if a == b {
fmt.Println("APlugin{A: 1} == BPlugin{B: 2}")
}
}
3 删除操作的一般写法
如上述 pluginContainer.Remove()
函数,第二点就是从 slice
删除元素的一般写法。这里我们是对 plugin []Plugin
进行删除,判定等于传入的 plugin
的元素就删除掉。一般如果我们知道要删除的index,我们可以这样删除 plugin = append(plugin[:index], plugin[index:])
。如果要删除特定元素,就按照源码中写的删除就可以了,记得要重新定义个变量var plugins []Plugin
,之后再赋值p.plugins = plugins
。
4 slice append是线程不安全的
源码中 pluginContainer.Add()
函数,直接 p.plugins = append(p.plugins, plugin)
这样如果并发访问的话,会丢数据。所以要额外注意。一般 slice
并发 append
会丢数据,map
并发赋值会 panic
。
这里额外说下 map
容易 panic
的情况,以及我们如何在实际写代码的时候避免。
-
map
声明后是个nil
类型,直接赋值会panic
。所以一般最好用make
初始化。 - 用别人的包的时候如果有
confutil.GetConfMap()
获取一个外部map
这种代码,要注意不要直接把这个直接放在并发环境中。因为它可能是个全局的map
并且GetConfMap()
函数中有delete
或者赋值这些导致并发panic
的操作。 -
map[int]map[int]int
这种嵌套的map
,深层的map
注意也要make
。
有疑问加站长微信联系(非本文作者)