利用Go中的Layer-cake设计
我喜欢go的多线程支持已经不是什么秘密了。这是一个新线程的单线程:
走 FUNC(){}()
并且与该线程交谈的三个班轮(最小):
信使 := 使(陈 INT, 3) 走 FUNC(){ 对于 信息 := < -信使 { FMT。的println(< -信使) } }() 信使 < - 五
线程和通道在天堂是一个匹配,让你建立一个反应导向的程序,分而治之的递归,以及我最终将在dev.to上讨论的许多更多的事情。
但是让我们来看看第一个。这让我建立了自己的个人小设计理念,我发现自己已经深入到每个项目中。它可能不是描述它的最准确的方式,但这个想法在名称中得到了体现。一切都是一层,而且它们都是结合在一起的。
在我看来,这种方法鼓励我以鼓励可扩展,模块化和可理解的风格的方式构建我的应用程序,服务器等。因此,可以加入新的服务,接口等,而无需集成它们的大量麻烦。话虽这么说,我没有完全按照它调整,但是,嘿,这就是我写这篇文章的原因。我希望将来能够保持这种结构,而不是过去几次我实施它的混乱方式。
此外,由于Go不允许递归导入,这鼓励创建我想称为“基板”的东西。这是您的应用程序中的通信核心。您的基材定义了可以(内部)命令的所有内容,命令格式,参数等。
最初,你需要做几件事情:
- 您的应用可以对哪些接口做出反应?
- 您的应用程序提供哪些服务?
通常,我的界面出来了
- CLI
- RPC(我的剪贴板管理器中没有使用)
- 基于TCP的协议
服务会有所不同,但对于我的剪贴板管理器来说却是如此
- 监控剪贴板
- (很快)客户端加密货币管道
所以让我们列举一下。在Go中有几个主要的枚举结构(我在第一个版本中默认最多,在使用AzCopy之后我明白为什么这可能有问题):
- type-const枚举(如果包中只有一个枚举,我通常会使用它)
类型 地点 UINT8 常量 ( CLI 地点 = 丝毫 TCP 监控 ) //你为什么要用这个? / * - 在调试日志中输出为const名称 - 易于参考* / //你为什么不想用这个? / * - 一个软件包中的多个枚举令人困惑 - 可能会意外地与函数名称冲突等。* /
-type-func枚举(AzCopy使用的)
类型 地点 UINT8 常量 ELocation = 地点(0) FUNC (升 地点) 命令行() 地点 { 返回 地点(1) } FUNC (升 地点) 的TcpClient() 地点 { 返回 地点(2) } FUNC (升 地点) 监控() 地点 { 返回 地点(3) } FUNC (升 地点) 串() 串 { 返回 ... } //你为什么要用这个? / * - 由变量类型而不是点评隔离 - 不能与其他函数,枚举等冲突。* / //你为什么不想用这个? / * - 需要一个Location变量来实际获取枚举值 - 没有简单的方法来打印它的字符串用于调试目的 - 由函数枚举,而不是由任何具体值* /
– 双映射枚举
VAR ELocation = 地图(串)UINT8{...} VAR LocationToString = 地图(UINT8)串{...} //你为什么要用这个? / * - 由变量类型而不是点评隔离 - 不能与其他函数,枚举等冲突。* / //你为什么不想用这个? / * - 不可变 - 需要更多内存 - 不要强制使用单一类型 - 没有简单的方法来打印它的字符串用于调试目的* /
我不是缺乏一个人的忠实粉丝 枚举
go中的结构,但是,你用你拥有的东西做。所有这些都有各自的问题。所有这些都可以混合和匹配,以创造“完美” 枚举
实现。
此时,是时候构建基板了。您的命令结构几乎总是最重要的。您可以随时添加它,但随着时间的推移,它会越来越难以删除。为了简单起见,我通常包括一个命令ID,一个字符串列表作为参数,以及原点位置。
类型 命令 结构 { //每个图层可以有多个命令结构 CommandID UINT8 //可以是枚举 参数 ()串 起源 地点 //这可以是字符串或枚举。你的选择。 }
所以你已经掌握了命令结构,现在是时候设置你的频道了。为每个图层设置一个通道。
VAR ( CLIChannel = 使(陈 命令, 50) //合理的缓冲区大小。 ClientChannel MonitorChannel )
现在,您的基板准备就绪。我通常把它放在“普通”包(或一个字面上称为“基板”)的盘点下。是时候创建你的图层了。为每个图层创建一个包,并为它们创建一个基线goroutine。也许他们将来会产生自己的。我们为我提供一个示例TCP客户端层。
包 客户 类型 extMessage 结构 { CommandID UINT8 参数 ()串 } FUNC 的TcpClient() { ExternalMessages := 使(陈 extMessage, 50) 走 FUNC(){ //这里的TCP侦听器,管道到ExternalMessages }() 对于 { 选择 { 案件 extMsg := < - ExternalMessages: //处理外部消息 案件 intMsg := < - 共同。的TcpChannel: //处理内部消息 } } }
在你的骨架开始运转之后,把那些吸盘扔进去 main.go
因为你很高兴。
包 主要 进口 ( “同步” ) FUNC 主要() { VAR WG 同步。WaitGroup WG。加(3) 走 客户。的TcpClient() 走 命令行。CLI() 走 监控。监控() WG。等待() }
当然,你可以用不同的方式杀死程序。也许你的每个图层都有一个自杀命令,并让他们回调等待组 wg.Done()
安全地终止程序。如果当前活动数据无关紧要,a os.Exit(0)
你会没事的。
所有这些工作的结果是你现在有一个很好的蛋糕层,你可以理解:
- 每个命令都来自哪里
- 谁与什么以及如何相互作用
在此之上,您可以获得模块化,可维护性和多线程的优势。