在 Go 语言中,包(Package) 是最基础的代码组织单位

包的基本概念

Go 程序是由一个个“包”组成的。每个 Go 源文件的第一行非注释代码必须是:

1
package name
  • 一个文件夹就是一个包
  • 同一个文件夹下的所有 .go 文件必须声明相同的包名(通常包名与文件夹名一致,但不是强制的,建议保持一致)。

包的作用

  • 将复杂的程序拆分成小的、易于管理的单元。
  • 通过包来控制哪些函数或变量是外部可见的。
  • 通过包名(如 fmt.Printflog.Printf)进行区分,避免命名冲突。

包的可见性控制

包内共享所有变量,常量,以及所有定义的类型,但对于包外而言并不是这样,有时候你并不想让别人访问某一个类型,所以就需要控制可见性。不同于其他的OOP语言存在publicpravite关键词,Go语言控制可见性仅需设置名称的大小写来实现:

  • 大写字母开头,那么它可以被包外的代码访问(相当于 Public)。
  • 小写字母开头,则只能在包内部使用(相当于 Private)。

包的导入

导入一个包的变量,类型,方法,函数等,使用import关键词

  1. 导入一个包

    1
    2
    3
    package main

    import "example"
  2. 导入多个包

    1
    2
    3
    4
    package main

    import "example1"
    import "example2"

    或者:

    1
    2
    3
    4
    5
    6
    package main

    import (
    "example1"
    "example2"
    )
  3. 导入重名包/包名复杂

    可以使用别名来起别名:

    1
    2
    3
    4
    5
    6
    package main

    import (
    e1 "example1"
    e2 "example2"
    )
  4. 匿名导入包

    匿名导入的包无法被使用,这么做通常是为了加载包下的init 函数,但又不需要用到包中的类型,一个常见的例子就是注册数据库驱动,但是你并不需要去手动使用驱动。

    1
    2
    3
    4
    5
    6
    package main

    import (
    e "example"
    _ "mysql-driver-go"
    )
  5. 特殊的导入包格式

    将该包中的所有类型都导入到当前包作用域,以这种方法导入的类型不再需要.运算符去访问,但是如果有重名的类型将会无法通过编译。

    1
    2
    3
    4
    5
    package main

    import (
    . "example"
    )

注意:包不能循环导入或者相互导入

内部包

内部包(Internal Packages) 是一种特殊的访问控制机制。它允许你在一个项目中跨目录共享代码,但禁止外部项目引用这些代码

为什么需要 Internal 包?

假设你正在开发一个大型开源库。你有一些复杂的逻辑需要分成好几个子包(文件夹)来组织,但你并不希望这些复杂的底层逻辑被外部用户直接调用。如果你把函数首字母大写,外部用户就能看到并依赖它们;一旦你未来想修改这些底层逻辑,就会破坏外部用户的代码(破坏兼容性)。

举一个例子,你在开发一个开源的购物系统,你的项目包含api(用来外部用户调用)crypto(用来加密订单信息)

文件结构如下所示:

1
2
3
4
5
Shop/
├── api/
│ └── order.go (这里需要调用 crypto.Sign)
└── crypto/
└── sign.go (定义了 func Encrypt())
  1. 如果没有 Internal 包

    你为了api包中能调用crypto中的Encrypt(),你必须把这个函数首字母大写

    布豪的事情发生了:

    任何下载了你这个库的人,不仅可以使用 api.OrderGood()(order中的方法),还可以直接在他们的代码里写:

    import "github.com/yourname/Shop/crypto" 它可以直接绕过你的api直接调用加密算法!

    你会遇到以下麻烦:

    不敢随便改代码:三个月后,你发现 Encrypt() 有漏洞,你想把 Encrypt(data string) 改成 Encrypt(data string, salt string)

    被迫背锅:你一改,全世界成千上万个直接用了你 crypto 包的开发者代码都会编译报错

    你很冤枉:你本意是这个加密函数只给自己的 api 包内部使用,根本没想过让别人用。但因为你要跨包(从 apicrypto),你不得不把它公开。

    所以我们引入Internal 包

  2. 现在你引入了 Internal 包

    将文件结构修改成:

    1
    2
    3
    4
    5
    6
    Shop/
    ├── api/
    │ └── order.go
    └── internal/
    └── crypto/
    └── sign.go (定义了 func Encrypt())

    你自己能用:你的 api/order.go 依然可以正常导入 Shop/internal/crypto 并调用大写的 Encrypt()。因为 apiinternal 的兄弟(它们有共同的父亲 SuperOrder)。

    外人绝对用不了:如果有用户尝试在自己的项目里 import "github.com/yourname/Shop/internal/crypto"Go 编译器会直接报错,根本不让他编译。

这下解决了痛点了


部分参考自:
Golang中文学习文档站