在设计程序的许多应用场景中我们会遇到大体分为三个阶段的任务流。
第一、入口
一个或多个入口,等待阻塞的、或者主动请求方式的。
==============================
比如任务流需要接受来自于 HTTP 和 FTP 的应用请求,后续还有可能增加别的方式的接受请求。
第二、处理
多个入口可以对应一个处理程序,也可以对应多个处理程序。
==================================
比如 HTTP 和 FTP 的多个入口程序需要对应一个和多个处理逻辑,同样也面临着增加处理程序的扩展性问题。
第三、出口
多个处理程序或者一个处理程序对应多个出口或者一个出口。
==================================
比如报警方式有邮件报警、微信报警等等,后续还有可能增加短信报警等等。
事实上可以把绝大部分需求可以抽象成上述思维。
那么对于这类问题我们采取的扩展性要求是,拓展功能时改动少量原有代码或者干脆不改动原有代码。在不考虑软件重构层面,当新入职员工接手上位大哥的代码时,我想最头疼的就是增加需求,因为你不知道更改代码会带来什么隐藏 BUG。
我们总是理想可以用"插件化"的思想来适应需求,写好框架,然后分门别类,每一个抽象出来的角色是一个大的容器,然后每次向这个容器中插一个个的插件,来一个需求,做一个插件插进去。来两个需求,做两个插件插一双进去。
更"过分"的要求是插进去插件必须有一个按钮来控制是不是启用该插件,理由就是拔插一次插件太耗时了(对应代码中的注释 N 多行)。
总之一切的一切我们想要的效果就是每一个插件独立工作,互不相应,增加可拓展性。这样带来的弊端就是可能有一部分的冗余代码,但是这样也比交接出去工作让人家天天在背后黑你要强的多吧。
那首先我们先用 golang 来造出一个需求容器。
package need import ( "fmt" ) var ( Plugins map[string]Need ) func init() { Plugins = make(map[string]Need) } type Need interface { Flag() bool // 必须告诉容器是否启动 Start() //必须有启动方法 } func Start() { for name, plugin := range Plugins { if plugin.Flag() { go plugin.Start() fmt.Printf("启动插件%s ", name) } else { fmt.Printf("不启动插件%s ", name) } } } //每个插件在初始化时必须注册 func Register(name string, plugin Need) { Plugins[name] = plugin }
这个需求容器对插入自己的插件有要求!
- 必须告诉容器插件本身是不是正常的
- 必须提供你插件本身的工作内容
- 必须与容器进行连接
如果缺少 1,那么就无法实现插件生病就放病假。
如果缺少 2,你说你不说你是干啥的容器怎么敢收你,万一是贩毒的咋办~
如果缺少 3,得与容器签订合同,绑定劳动关系。
按照上述要求,我们来实现一个插件一号。
package node import ( "fmt" "go_dev/plugin/need" ) func init() { p1 := plugin1{} need.Register("plugin1", p1) } type plugin1 struct { } func (p plugin1) Flag() bool { return true } func (p plugin1) Start() { fmt.Println("我是插件一号") }
现在是容器也有了,插件也有了,但是没电跑不起来啊。。。,我们现在再用 golang 给他提供个电源。
package main import ( "go_dev/plugin/need" _ "go_dev/plugin/node" ) func main() { need.Start() }