当前位置:首页 > 科技  > 软件

Go 包循环引用及对策,你学会了吗?

来源: 责编: 时间:2024-03-18 09:37:21 279观看
导读引言从 Java 转到 Go 的开发同学,大概都会踩到第一个“坑”:Go 的包循环引用。Go 的包循环引用是什么意思呢?有一定经验的开发者都知道循环依赖,比如 A 依赖了 B, B 依赖了 C ,C 又依赖了 A。这就构成了一个循环依赖(有环图)

引言

从 Java 转到 Go 的开发同学,大概都会踩到第一个“坑”:Go 的包循环引用。Em928资讯网——每日最新资讯28at.com

Go 的包循环引用是什么意思呢?有一定经验的开发者都知道循环依赖,比如 A 依赖了 B, B 依赖了 C ,C 又依赖了 A。这就构成了一个循环依赖(有环图)。Em928资讯网——每日最新资讯28at.com

在 Java 里面,循环依赖是类级别的;但 Go 里要更严格一些:Go 的循环引用判定是 包级别的。举个例子,包 A 下的类 A 依赖了包 B 下的类 B,类 B 又依赖了包 C 下的类 C, 类 C 又依赖了包 A 下的 D。在 Java 里面,这里并没有构成循环依赖。但在 Go 里面,这导致了包循环引用:包 A => 包 B = > 包 C => 包 A。Go 会编译不通过:报 import circle not allowed。Em928资讯网——每日最新资讯28at.com

对包依赖不太重视的人,初期会感到不适应。本文举几个自己踩过的坑,作一说明。Em928资讯网——每日最新资讯28at.com

包循环引用释例

对象导致的包循环引用

哎呀呀,初来乍到,一下子给我来了八个包循环引用,打击得我有点不知所措了。这是怎么回事呢 ?Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

第一个循环引用,是因为上报的包 agent 下对象 DetectionBase 依赖了包 denoise 下的降噪模型结果对象 HitDenoiseModel,而在某一处,HitDenoiseModel  又引用了包 agent 下的另一个对象。Em928资讯网——每日最新资讯28at.com

为什么会发生这个事情?这是因为,图省事,我直接把输出对象放到了输入对象的包里,不想复制一份。正确的做法是,输入的对象只能放输入对象,不能放输出对象,否则依赖就会扩大出去。Em928资讯网——每日最新资讯28at.com

如何解决?有两种方案:Em928资讯网——每日最新资讯28at.com

  • 把 HitDenoiseModel 放在 agent 包下。然后定义另一个对象 denoise/DenoiseResultModel,将 FillHitDenoiseModel 方法移到包 denoise 下,HitDenoiseModel 赋值给 DenoiseResultModel。这样形成了包 denoise => agent 的单向依赖,保证“输出 =>输入” 的单向依赖。不过,这种方法还是存在“篡改”原始数据的小罪行。
  • 不对 DetectionBase 做任何变更,新创建一个包和对象,把 HitDenoiseModel 放在新创建对象里,同时将 FillHitDenoiseModel 方法移到新创建对象的包下。3

启示:避免篡改输入对象。应用中的对象往往是很多的,不太重视包依赖,很容易造成对象的循环引用。即使你自己能够小心确保不出问题,也会和别人添加的类造成循环引用。Em928资讯网——每日最新资讯28at.com

发送消息与接收消息循环引用

梅开二度。又来了一个包循环引用。Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

这又是怎么回事?有了第一次经验,第二次就不那么慌了。先提个 MR ,看看引入了哪些类。尤其是循环引用的那条链路。我们看到 cdc/msg 这个地方作为起点开始循环。为什么会有循环呢?因为我本来打算把 msg 相关的消息对象和消息处理都放在一起。但这种方式很容易导致 循环引用。为什么呢?因为 引入消息对象的时候, 就会把包下的所有类引入的所有包都引进来,这样就会把 /cdc/msg/XXXReceiver 引入,进而引入 /cdc/handler/XXXhandler, 而 msg/handler/XXXhandler 又会引入 msg/XXXSender。就导致了包循环引用依赖。Em928资讯网——每日最新资讯28at.com

看来之前还是随意惯了。Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

怎么解决?把 XXXReceiver 放在包 receiver 下,把 XXXSender 放在包 sender 下即可。Em928资讯网——每日最新资讯28at.com

启示:Em928资讯网——每日最新资讯28at.com

  • 引入类的包要小,这样就很类似 Java 的全限定性包,减少循环引用的可能性。
  • 简单的消息对象与复杂的消息处理不要放在一起,因为引入简单的就会把复杂的引入,复杂的又会递归引入更多的依赖。

internal 引用了 share

一键三连。Em928资讯网——每日最新资讯28at.com

下图又是怎么回事?借鉴业界最佳实践,咱们把一个工程下的代码分为了 internal 和 share。其中 internal 的代码不能被其它模块访问,只有 share 下的代码作为桥梁,为其它模块提供服务。Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

这个是因为 internal/detect_config/AService 引用了 share/detect_config/BService ,然后 share/detect_config/CService 又引用了 internal/detect_config/DService。internal 包怎么能够引用 share 下的包呢 ?内部模块怎么能够依赖外部模块 ?世界似乎不那么美好了。Em928资讯网——每日最新资讯28at.com

与同事讨论,他们认为,helper 不应该依赖 service ,而应该依赖 repository, 而我一直认为 helper 是对 service 提供服务的一种高层封装, helper 依赖 service 是很正常。helper 依赖 repository, 看上去说得也很有道理。不过 internal 模块依赖 share 模块,看上去总是感觉有点违反单向依赖的设计原则。Em928资讯网——每日最新资讯28at.com

经过讨论后,我和同事各做了修改。我的修改是让 helper 依赖 repository, 同事的修改是,把原来 service 拆分成公共的 service 和内部的 service,保证 share 对 internal 的单向依赖。Em928资讯网——每日最新资讯28at.com

运行时循环依赖

即使你幸运地逃过了包循环引用的检测,但存在运行时循环依赖,Go 会直接卡住。Em928资讯网——每日最新资讯28at.com

一个例子如下图所示:有若干个流程组件 A, B, C,加载到一个工厂 F 下,由一个 执行器 E 依次从 F 中取出执行。但是呢,在流程组件C 中,又依赖了 E 来执行任务。这样会导致循环依赖。在 Java 中,Spring 会忽略组件 C, 初始化成功,但运行时找不到 C 而导致流程出错(可以用懒加载机制解决);但是 Go 就不那么幸运了(也许是幸运的,因为它让你早点发现问题)。Em928资讯网——每日最新资讯28at.com

对于这种场景,可以用消息队列来解耦。Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

对策汇总

遇到包循环引用,有哪些经验可循呢 ?Em928资讯网——每日最新资讯28at.com

(1) 提倡小而独立的包。不要把大量的有关联的类都放在一个包下。这样,很容易因为一个类的引入,而引入更多依赖,导致依赖不可控。Em928资讯网——每日最新资讯28at.com

(2)单向依赖原则。internal 下的类不应当依赖 share 下的类。因为 share 下的类一定会依赖 internal。如果 internal 又依赖 share ,就破坏了“单向依赖”原则。当工程越来越大时,一定会有一个点会爆发。不是不报,时候未到。细分包可以缓解这种问题,但不能从根本上避免单向依赖的破坏。Em928资讯网——每日最新资讯28at.com

(3) 依赖倒置原则。尽量依赖接口,而不是具体实现类。Em928资讯网——每日最新资讯28at.com

(4)使用消息队列解耦依赖。Em928资讯网——每日最新资讯28at.com

(5)相对合理的依赖方向:model => (constants, 无依赖的 model) ; dto => types => constants ;util => (dto, types, constants, models);service =>  repository => model ;helper => (repository, util, cache) ; controller => (helper, service) ;  receiver => handler => (service, helper)Em928资讯网——每日最新资讯28at.com

最后问一句:Go 为什么不允许包循环引用呢 ?听听 Go 语言作者怎么说:Em928资讯网——每日最新资讯28at.com

图片图片Em928资讯网——每日最新资讯28at.com

再听听 AI 怎么说 :Em928资讯网——每日最新资讯28at.com

图片Em928资讯网——每日最新资讯28at.com

小结

本文讨论了几个包循环引用的例子,并给出了相应的对策。Em928资讯网——每日最新资讯28at.com

个人觉得,这种禁止循环引用的做法还是可取的,能培养良好的设计习惯。软件开发,本质是应对结构复杂性的技艺。而设计思维,则是应对结构复杂性的重要法宝。开发人员应多多学习设计思考,保持系统和架构的优雅。Em928资讯网——每日最新资讯28at.com

参考资料

  • go循环依赖最佳解决方案[1]

Reference

[1]go循环依赖最佳解决方案:https://juejin.cn/post/7290389972406501432Em928资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-76488-0.htmlGo 包循环引用及对策,你学会了吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: JQuery 4.0震撼发布:这是复兴还是告别?

下一篇: 业务开发做到零 bug 有多难?

标签:
  • 热门焦点
  • 7月安卓手机性能榜:红魔8S Pro再夺榜首

    7月份的手机市场风平浪静,除了红魔和努比亚带来了两款搭载骁龙8Gen2领先版处理器的新机之外,别的也想不到有什么新品了,这也正常,通常6月7月都是手机厂商修整的时间,进入8月份之
  • 6月iOS设备好评榜:第一蝉联榜首近一年

    作为安兔兔各种榜单里变化最小的那个,2023年6月的iOS好评榜和上个月相比没有任何排名上的变化,仅仅是部分设备好评率的下降,长年累月的用户评价和逐渐退出市场的老款机器让这
  • 一年经验在二线城市面试后端的经验分享

    忠告这篇文章只适合2年内工作经验、甚至没有工作经验的朋友阅读。如果你是2年以上工作经验,请果断划走,对你没啥帮助~主人公这篇文章内容来自 「升职加薪」星球星友 的投稿,坐
  • 十个简单但很有用的Python装饰器

    装饰器(Decorators)是Python中一种强大而灵活的功能,用于修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数或类作为参数,并返回一个新的函数或类。它们通常用
  • 花7万退货退款无门:谁在纵容淘宝珠宝商家造假?

    来源:极点商业作者:杨铭在淘宝购买珠宝玉石后,因为保证金不够赔付,店铺关闭,退货退款难、维权无门的比比皆是。“提供相关产品鉴定证书,支持全国复检,可以30天无理由退换货。&
  • 2纳米决战2025

    集微网报道 从三强争霸到四雄逐鹿,2nm的厮杀声已然隐约传来。无论是老牌劲旅台积电、三星,还是誓言重回先进制程领先地位的英特尔,甚至初成立不久的新
  • 2299元起!iQOO Pad开启预售:性能最强天玑平板

    5月23日,iQOO如期举行了新品发布会,除了首发安卓最强旗舰处理器的iQOO Neo8系列新机外,还在发布会上推出了旗下首款平板电脑——iQOO Pad,其搭载了天玑
  • OPPO K11样张首曝:千元机影像“卷”得真不错!

    一直以来,OPPO K系列机型都保持着较为均衡的产品体验,历来都是2K价位的明星机型,去年推出的OPPO K10和OPPO K10 Pro两款机型凭借各自的出色配置,堪称有
  • 世界人工智能大会国际日开幕式活动在世博展览馆开启

    30日上午,世界人工智能大会国际日开幕式活动在世博展览馆开启,聚集国际城市代表、重量级院士专家、国际创新企业代表,共同打造人工智能交流平台。上海市副市
Top