go 程序结构的探讨

51 天前
 yujianwjj

一个项目就是几个模块的组合,假设有 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() 其实跟接口自身的含义没啥关系。

想问问大家,怎么设计程序的模块架构的。

1713 次点击
所在节点    Go 编程语言
5 条回复
GooMS
51 天前
不明白第一个问题,你的外层 run 起来后监听信号不就结束了吗 看代码到服务里去看
yujianwjj
51 天前
run 是小写,外层调用不了
neotheone2333
51 天前
OP 的意思是,struct 的属性类型指定 interface 还是 struct 的问题?

如果我理解的没错的话,我的习惯是:
1. 属性类型用 interface
2. NewXXX 方法接受参数类型用 interface ,返回值用 struct
3. 看 interface 的实现用 IDE 的 go to implementation 功能,具体哪走的一个实现 debug 打断点看
reatang
50 天前
interface 主要用在:
1 、可替换实现的地方
2 、会产生包依赖冲突的地方

其他情况下,不建议使用 interface
CLMan
50 天前
模式 1 ,定义了一堆 interface ,模块执行逻辑在 New 里面,间接在 run 里面。
模式 2 ,定义了一个通用的 Module 接口,模块实现 Module 接口,模块执行逻辑在 Run ,由 Manager 在 run 里面调用。
模式 3 ,定义了一个通用接口 Stage ,以及定义了一堆 interface ,模块实现 Stage 接口以及自身的接口,模块执行逻辑在 Run ,由 Manager 在 run 里面调用。

所以你这 3 种模式,无非就是抽取了一堆接口,有点 Java 了,代码的可读性没有任何区别。

首先,模式 1 的将模块执行触发丢在 New 里面,通常是不建议的,因为多模块系统是由模块组装得到的,最后再走启动的流程,模块提前执行是不合适的。

其次,除非真有必要,Go 是很少上来就抽取一堆 interface ,建议先写下原型,根据实际需要再抽取接口。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1085488

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX