一个项目就是几个模块的组合,假设有 3 个模块,目前我看到以下几种架构:
type InterfaceA interface { MethodA1() MethodA2() } type structA struct { a int } func (a *structA)MedhtodA1() {} func (a *structA)MedhtodA2() {} func (a *structA)run(context) { for { // MethodA1() 和 MethodA2() 的一些调用 } } func NewStructA() structA { a := structA{} go a.run() return a } // 类似的还有 InterfaceB/structB ,特点都是在 New 方法里面开了一个 goroutine ,运行 run 方法。 type InterfaceC interface { MethodC1() MethodC2() MethodC3() } // 这里 c 的运行需要 a type structC struct { a struct c string } func (c *structC)MedhtodC1() {} func (c *structC)MedhtodC2() {} func (c *structC)MedhtodC3() {} func (a *structA)run(context) { for { // MethodC1()、MethodC2()、MethodC3()及 MethodA()的一些调用 } } func NewStructC() InterfaceC { a := structA{} c := structC{ a } go c.run() return c } type manager struct { ib InterfaceB ic InterfaceC } func NewManager() *manager{ ia := NewStructA() ib := NewStructB() ic := NewStructC(ia) return &manager{ ib ic } } func (m *manager)run(ch) { for { select { case <- ch: return } } } func main() { m := NewManager() m.run() }
这种架构的话,很清晰的表明了程序模块,比如直接从项目的目录结构就可以看到这个程序的几个模块(一个模块对应一个目录),每个模块的接口功能表达也很清晰。但是他的问题是,在阅读代码的时候,隐藏了程序里面的几个模块的执行和交互逻辑。比如我从 main 看到 manager 的时候,再看到 manager.run 方法就结束了,完全不知道程序内部的模块是怎么运行。
// 另外一种架构 type Module interface { Run() } type StructA struct { } func (a *StructA) Run() { a.methodA1() a.methodA2() } type StructB struct { } func (a *StructB) Run() { b.methodB1() b.methodB2() } type StructC struct { a StructA } func (a *StructC) Run() { a.Run() c.MethodC1() c.MethodC2() } type manager struct { b StructB c StructC } func NewManager() *manager{ a := NewStructA() b := NewStructB() c := NewStructC(a) return &manager{ b, c } } func (m *manager)run(ch) { go m.b.Run() go m.c.Run() for { select { case <- ch: return } } } func main() { m := NewManager() m.run() }
这样的话,阅读代码的时候,可以快速的知道程序的执行逻辑。但是吧,每个模块对外只有一个 Run 方法,模块本身的含义又没了。
这里,估计有人会提出下面的方案。
type Stage interface { Run() } type InterfaceA interface { Stage MethodA1() MethodA2() } type InterfaceB interface { Stage MethodB1() MethodB2() } type InterfaceC interface { Stage MethodC1() MethodC2() MethodC3() }
这个也是可行的,但是吧,看着别扭,因为这个 Run() 其实跟接口自身的含义没啥关系。
想问问大家,怎么设计程序的模块架构的。
![]() | 1 3IOhG7M0knRu5UlC 344 天前 via Android 不明白第一个问题,你的外层 run 起来后监听信号不就结束了吗 看代码到服务里去看 |
2 yujianwjj OP run 是小写,外层调用不了 |
3 neotheone2333 343 天前 OP 的意思是,struct 的属性类型指定 interface 还是 struct 的问题? 如果我理解的没错的话,我的习惯是: 1. 属性类型用 interface 2. NewXXX 方法接受参数类型用 interface ,返回值用 struct 3. 看 interface 的实现用 IDE 的 go to implementation 功能,具体哪走的一个实现 debug 打断点看 |
4 reatang 343 天前 interface 主要用在: 1 、可替换实现的地方 2 、会产生包依赖冲突的地方 其他情况下,不建议使用 interface |
5 CLMan 343 天前 模式 1 ,定义了一堆 interface ,模块执行逻辑在 New 里面,间接在 run 里面。 模式 2 ,定义了一个通用的 Module 接口,模块实现 Module 接口,模块执行逻辑在 Run ,由 Manager 在 run 里面调用。 模式 3 ,定义了一个通用接口 Stage ,以及定义了一堆 interface ,模块实现 Stage 接口以及自身的接口,模块执行逻辑在 Run ,由 Manager 在 run 里面调用。 所以你这 3 种模式,无非就是抽取了一堆接口,有点 Java 了,代码的可读性没有任何区别。 首先,模式 1 的将模块执行触发丢在 New 里面,通常是不建议的,因为多模块系统是由模块组装得到的,最后再走启动的流程,模块提前执行是不合适的。 其次,除非真有必要,Go 是很少上来就抽取一堆 interface ,建议先写下原型,根据实际需要再抽取接口。 |