Golang笔记——包的循环引用问题(import cycle not allowed)和匿名导入

大家好,这里是Good Note,关注 公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Golang中包的循环引用问题(import cycle not allowed)和匿名导入问题。

文章目录

循环引用问题

Go 的设计哲学是避免复杂的循环依赖,从而强制开发者更清晰地思考模块之间的依赖关系。这种设计使代码更易维护,也加快了编译速度。

优势

曾有人建议作者之一的Rob Pike,在以后的Go版本去掉不允许循环引入的问题;Rob Pike则认为这样设计有如下优势:

  1. 加快编译速度
  2. 规范框架设计,使项目结构更加清晰明了
设计原因

作者认为:

  • 没有支持循环引用:目的是迫使 Go 程序员更多地考虑程序的依赖关系。

    • 保持依赖关系图的简洁。

    • 快速的程序构建。

  • 如果支持循环引用:很容易会造成懒惰、不良的依赖性管理和缓慢的构建。这是设计者不希望看见的。

    • 混乱的依赖关系。

    • 缓慢的程序构建

如果在项目中出现循环引用问题,很大程度是因为设计之初就没考虑好模块的划分。

解决方法

优先考虑使用方法1

  1. 新建一个公共包,将涉及的函数或方法放到公共包当中;

    当两个包 AB 互相依赖时,可能是因为它们有某些共享的功能。如果把这些共享功能提取到一个独立的公共包(比如 common),就可以打破循环依赖。AB 可以单独依赖 common,而不需要直接依赖对方。

  2. 将循环引用的方法或者函数抽象成接口;

    通过抽象一个接口来打破直接依赖关系。假设包 A 需要调用包 B 的某些方法,而包 B 同时需要调用包 A 的方法。这时,可以将这些方法抽象成接口,由其中一个包提供接口的实现。

定义接口:让包 B 依赖接口

  • 将包 A 的功能抽象成接口 AInterface
  • B 依赖 AInterface,而不是直接依赖包 A

包 A (A/a.go)

go 复制代码
package A

// 定义接口 AInterface 
type AInterface interface {
    AFunction() 
}

// AType 实现 AInterface 
type AType struct{}

func (a *AType) AFunction() {
    println("AFunction is called") 
} 

包 B (B/b.go)

go 复制代码
package B

// BType 依赖 AInterface 
type BType struct {
   AImpl AInterface // 接口,而不是具体实现 
}

func (b *BType) BFunction() {
   println("BFunction is called")
   b.AImpl.AFunction() // 调用 AInterface 的方法 
}

主程序:组合两个包 在主程序中,将包 A 和包 B 组合在一起,打破了直接依赖:

go 复制代码
import (
    "A"
    "B" )

func main() {
    a := &A.AType{}         // 创建 AType 实例
    b := &B.BType{AImpl: a} // 将 A 的实现传递给 B

    b.BFunction() // 调用 B 的方法,间接调用 A 的方法
} 

输出结果BFunction is called AFunction is called

  • 包 B 依赖的是 AInterface 接口,而不是包 A 的具体实现。当接口(AInterface)不是由包 A 定义,而是由包 B 本身定义或通过其他途径提供时,包 B 就完全不需要导入包 A。
  • Go 的接口机制允许我们只依赖接口,而不依赖接口的实现,从而实现解耦。这种设计是 Go 语言的关键优势。

明确导入的包必须使用

在 Go 中,如果导入了一个包但没有使用它,编译器会报错。Go 不允许"死包"导入。

设计目的:

  • 强制开发者移除无用的代码和依赖。
  • 提高代码的可读性和可维护性。

示例:

go 复制代码
import "fmt"

func main() {
    // 如果不使用 fmt 包,这段代码将无法编译。
    fmt.Println("Hello, Go!")
}
匿名导入

在 Go 中,如果导入了一个包却没有使用其中的任何内容,编译器会报错。匿名导入通过 _ 使得包被导入,但不直接使用包内的任何内容,从而避免编译错误。

  • 目的:为了触发包的 init() 函数,执行包级别的初始化逻辑,而无需显式调用包中的其他内容。

补充

不允许隐式类型转换

Go 不支持隐式类型转换,所有的类型转换必须是显式的。

设计目的:

  • 避免隐式转换带来的意外错误。
  • 使代码更加清晰和安全。

示例:

go 复制代码
var a int = 10
var b float64 = 3.14

// b = a  // 编译错误:无法将 int 隐式转换为 float64
b = float64(a)  // 显式转换,合法

对比其他语言:

  • 在 C/C++、php中,很多时候可以隐式将 int 转换为 float,容易导致一些不易察觉的精度问题或错误。

历史文章

MySQL数据库

MySQL数据库

Redis

Redis数据库笔记合集

Golang

  1. Golang笔记------语言基础知识
  2. Golang笔记------切片与数组
  3. Golang笔记------hashmap
  4. Golang笔记------rune和byte
  5. Golang笔记------channel
  6. Golang笔记------Interface类型
  7. Golang笔记------数组、Slice、Map、Channel的并发安全性
  8. Golang笔记------协程同步
  9. Golang笔记------并发控制
  10. Golang笔记------GPM调度器
  11. Golang笔记------ new() 、 make() 和简短声明符
  12. Golang笔记------ error 和 panic
  13. Golang------内存(内存管理、内存逃逸、垃圾回收 (GC) 机制)
相关推荐
羊小猪~~20 分钟前
MYSQL学习笔记(二):基本的SELECT语句使用(基本、条件、聚合函数查询)
数据库·笔记·sql·学习·mysql·考研·数据分析
珹洺24 分钟前
踏上 C++ 编程之旅:开篇之作
开发语言·c++
Yeats_Liao32 分钟前
Java List过滤 Stream API filter() 应用
java·开发语言·list
qingy_20461 小时前
【算法】图解二叉树的前中后序遍历
java·开发语言·算法
程思扬1 小时前
Android笔记: 实现点击事件透传到底部
android·前端·windows·经验分享·笔记·科技·ui
macrozheng1 小时前
Jenkins+Docker一键打包部署项目!步骤齐全,少走坑路!
java·spring boot·后端·docker·jenkins
!!!5251 小时前
MyBatis-增删改查操作&一些细节
java·数据库·spring boot·mybatis
azhou的代码园1 小时前
基于Java+SpringBoot+Vue的前后端分离的体质测试数据分析及可视化设计
java·vue.js·spring boot
黑口罩1 小时前
【JavaScript】比较运算符的运用、定义函数、if(){}...esle{} 语句
java·前端·javascript
5980354152 小时前
【spring mvc】文件上传、下载
java·spring·mvc