java子包 父包可见性
介绍 (Introduction)
When creating a package in Go, the end goal is usually to make the package accessible for other developers to use, either in higher order packages or whole programs. By importing the package, your piece of code can serve as the building block for other, more complex tools. However, only certain packages are available for importing. This is determined by the visibility of the package.
在Go中创建程序包时 ,最终目标通常是使程序包可以供其他开发人员使用,无论是高级程序包还是整个程序。 通过导入软件包 ,您的代码段可以用作其他更复杂工具的构建块。 但是,仅某些软件包可用于导入。 这由包装的可见性决定。
Visibility in this context means the file space from which a package or other construct can be referenced. For example, if we define a variable in a function, the visibility (scope) of that variable is only within the function in which it was defined. Similarly, if you define a variable in a package, you can make it visible to just that package, or allow it to be visible outside the package as well.
在这种情况下, 可见性意味着可以从中引用包或其他构造的文件空间。 例如,如果我们在函数中定义变量,则该变量的可见性(作用域)仅在定义该变量的函数内。 同样,如果在程序包中定义变量,则可以使该变量仅对该程序包可见,或者也允许它在程序包外部可见。
Carefully controlling package visibility is important when writing ergonomic code, especially when accounting for future changes that you may want to make to your package. If you need to fix a bug, improve performance, or change functionality, you’ll want to make the change in a way that won’t break the code of anyone using your package. One way to minimize breaking changes is to allow access only to the parts of your package that are needed for it to be used properly. By limiting access, you can make changes internally to your package with less of a chance of affecting how other developers are using your package.
在编写符合人体工程学的代码时,尤其是考虑到将来可能要对软件包进行的更改时,仔细控制软件包的可见性非常重要。 如果您需要修复错误,提高性能或更改功能,则需要以不会破坏任何使用您软件包的代码的方式进行更改。 最小化重大更改的一种方法是仅允许访问包装中正确使用所需的部分。 通过限制访问,您可以在内部对程序包进行更改,而不会影响其他开发人员使用程序包的机会。
In this article, you will learn how to control package visibility, as well as how to protect parts of your code that should only be used inside your package. To do this, we will create a basic logger to log and debug messages, using packages with varying degrees of item visibility.
在本文中,您将学习如何控制包的可见性,以及如何保护只应在包内使用的部分代码。 为此,我们将创建一个基本的记录器,以使用具有不同程度的项目可见性的包来记录和调试消息。
先决条件 (Prerequisites)
To follow the examples in this article, you will need:
要遵循本文中的示例,您将需要:
A Go workspace set up by following How To Install Go and Set Up a Local Programming Environment. This tutorial will use the following file structure:
通过遵循如何安装Go和设置本地编程环境来设置 Go工作区。 本教程将使用以下文件结构:
.
├── bin
│
└── src
└── github.com
└── gopherguides
出口和未出口项目 (Exported and Unexported Items)
Unlike other program languages like Java and Python that use access modifiers such as public
, private
, or protected
to specify scope, Go determines if an item is exported
and unexported
through how it is declared. Exporting an item in this case makes it visible
outside the current package. If it’s not exported, it is only visible and usable from within the package it was defined.
与其他使用访问修饰符(例如public
, private
或protected
来指定范围的Java和Python等其他程序语言不同,Go通过声明项的方式确定是exported
还是不unexported
项。 在这种情况下,导出项目将使其在当前包外部visible
。 如果未导出,则仅在定义的包中可见并可用。
This external visibility is controlled by capitalizing the first letter of the item declared. All declarations, such as Types
, Variables
, Constants
, Functions
, etc., that start with a capital letter are visible outside the current package.
外部可见性通过大写声明的项目的首字母来控制。 所有以大写字母开头的声明,例如Types
, Variables
, Constants
, Functions
等,都可以在当前包外部看到。
Let’s look at the following code, paying careful attention to capitalization:
让我们看下面的代码,注意大写:
package greet
import "fmt"
var Greeting string
func Hello(name string) string {
return fmt.Sprintf(Greeting, name)
}
This code declares that it is in the greet
package. It then declares two symbols, a variable called Greeting
, and a function called Hello
. Because they both start with a capital letter, they are both exported
and available to any outside program. As stated earlier, crafting a package that limits access will allow for better API design and make it easier to update your package internally without breaking anyone’s code that is depending on your package.
此代码声明它在greet
包中。 然后,它声明两个符号,一个名为Greeting
的变量和一个名为Hello
的函数。 因为它们都以大写字母开头,所以它们都可以exported
并可以用于任何外部程序。 如前所述,设计一个限制访问权限的软件包将有助于更好的API设计,并使内部更新软件包变得更加容易,而不会破坏依赖于软件包的任何人的代码。
定义包装的可见性 (Defining Package Visibility)
To give a closer look at how package visibility works in a program, let’s create a logging
package, keeping in mind what we want to make visible outside our package and what we won’t make visible. This logging package will be responsible for logging any of our program messages to the console. It will also look at what level we are logging at. A level describes the type of log, and is going to be one of three statuses: info
, warning
, or error
.
为了更仔细地查看程序包中可见性的工作方式,让我们创建一个logging
程序包,同时牢记我们要在程序包之外显示的内容以及不希望显示的内容。 此日志记录包将负责将我们的任何程序消息记录到控制台。 它还将查看我们正在记录的级别 。 一个级别描述了日志的类型,它将成为以下三种状态之一: info
, warning
或error
。
First, within your src
directory, let’s create a directory called logging
to put our logging files in:
首先,在您的src
目录中,创建一个名为logging
的目录,以将我们的日志文件放入:
- mkdir logging mkdir日志记录
Move into that directory next:
接下来进入该目录:
- cd logging cd记录
Then, using an editor like nano, create a file called logging.go
:
然后,使用诸如nano之类的编辑器,创建一个名为logging.go
的文件:
- nano logging.go 纳米测井
Place the following code in the logging.go
file we just created:
将以下代码放在我们刚刚创建的logging.go
文件中:
package logging
import (
"fmt"
"time"
)
var debug bool
func Debug(b bool) {
debug = b
}
func Log(statement string) {
if !debug {
return
}
fmt.Printf("%s %s\n", time.Now().Format(time.RFC3339), statement)
}
The first line of this code declared a package called logging
. In this package, there are two exported
functions: Debug
and Log
. These functions can be called by any other package that imports the logging
package. There is also a private variable called debug
. This variable is only accessible from within the logging
package. It is important to note that while the function Debug
and the variable debug
both have the same spelling, the function is capitalized and the variable is not. This makes them distinct declarations with different scopes.
此代码的第一行声明了一个称为logging
的程序包。 在此软件包中,有两个exported
函数: Debug
和Log
。 导入logging
软件包的任何其他软件包均可调用这些函数。 还有一个称为debug
的私有变量。 此变量只能从logging
包中访问。 重要的是要注意,尽管函数Debug
和变量debug
的拼写是相同的,但函数是大写的而变量不是。 这使它们具有不同范围的不同声明。
Save and quit the file.
保存并退出文件。
To use this package in other areas of our code, we can import
it into a new package. We’ll create this new package, but we’ll need a new directory to store those source files in first.
要在我们代码的其他区域使用此包,我们可以import
其import
到新包中 。 我们将创建此新程序包,但首先需要一个新目录来存储这些源文件。
Let’s move out of the logging
directory, create a new directory called cmd
, and move into that new directory:
让我们移出logging
目录,创建一个名为cmd
的新目录,然后移至该新目录:
- cd .. 光盘..
- mkdir cmd mkdir cmd
- cd cmd cd cmd
Create a file called main.go
in the cmd
directory we just created:
在我们刚创建的cmd
目录中创建一个名为main.go
的文件:
- nano main.go 纳米main.go
Now we can add the following code:
现在我们可以添加以下代码:
package main
import "github.com/gopherguides/logging"
func main() {
logging.Debug(true)
logging.Log("This is a debug statement...")
}
We now have our entire program written. However, before we can run this program, we’ll need to also create a couple of configuration files for our code to work properly. Go uses Go Modules to configure package dependencies for importing resources. Go modules are configuration files placed in your package directory that tell the compiler where to import packages from. While learning about modules is beyond the scope of this article, we can write just a couple lines of configuration to make this example work locally.
现在,我们编写了整个程序。 但是,在运行该程序之前,我们还需要创建几个配置文件,以使我们的代码正常运行。 Go使用Go Modules配置用于导入资源的程序包依赖项。 Go模块是放置在软件包目录中的配置文件,告诉编译器从何处导入软件包。 尽管关于模块的学习超出了本文的范围,但我们仅需编写几行配置即可使该示例在本地运行。
Open the following go.mod
file in the cmd
directory:
在cmd
目录中打开以下go.mod
文件:
- nano go.mod 纳米go.mod
Then place the following contents in the file:
然后将以下内容放入文件中:
module github.com/gopherguides/cmd
replace github.com/gopherguides/logging => ../logging
The first line of this file tells the compiler that the cmd
package has a file path of github.com/gopherguides/cmd
. The second line tells the compiler that the package github.com/gopherguides/logging
can be found locally on disk in the ../logging
directory.
该文件的第一行告诉编译器, cmd
软件包的文件路径为github.com/gopherguides/cmd
。 第二行告诉编译器,可以在../logging
目录中的磁盘上本地找到软件包github.com/gopherguides/logging
。
We’ll also need a go.mod
file for our logging
package. Let’s move back into the logging
directory and create a go.mod
file:
我们还将需要一个go.mod
文件作为logging
包。 让我们回到logging
目录并创建一个go.mod
文件:
- cd ../logging cd ../记录
- nano go.mod 纳米go.mod
Add the following contents to the file:
将以下内容添加到文件中:
module github.com/gopherguides/logging
This tells the compiler that the logging
package we created is actually the github.com/gopherguides/logging
package. This makes it possible to import the package in our main
package with the following line that we wrote earlier:
这告诉编译器我们创建的logging
包实际上是github.com/gopherguides/logging
包。 这样就可以使用前面编写的以下行将包导入到我们的main
包中:
package main
import "github.com/gopherguides/logging"
func main() {
logging.Debug(true)
logging.Log("This is a debug statement...")
}
You should now have the following directory structure and file layout:
您现在应该具有以下目录结构和文件布局:
├── cmd
│ ├── go.mod
│ └── main.go
└── logging
├── go.mod
└── logging.go
Now that we have all the configuration completed, we can run the main
program from the cmd
package with the following commands:
现在,我们已经完成了所有配置,我们可以使用以下命令从cmd
包运行main
:
- cd ../cmd cd ../cmd
- go run main.go 去运行main.go
You will get output similar to the following:
您将获得类似于以下内容的输出:
Output
2019-08-28T11:36:09-05:00 This is a debug statement...
The program will print out the current time in RFC 3339 format followed by whatever statement we sent to the logger. RFC 3339 is a time format that was designed to represent time on the internet and is commonly used in log files.
该程序将以RFC 3339格式打印当前时间,然后是我们发送给记录器的所有语句。 RFC 3339是一种时间格式,旨在表示互联网上的时间,通常用于日志文件中。
Because the Debug
and Log
functions are exported from the logging package, we can use them in our main
package. However, the debug
variable in the logging
package is not exported. Trying to reference an unexported declaration will result in a compile-time error.
由于Debug
和Log
函数是从日志记录包中导出的,因此我们可以在main
包中使用它们。 但是,不会导出logging
包中的debug
变量。 尝试引用未导出的声明将导致编译时错误。
Add the following highlighted line to main.go
:
将以下突出显示的行添加到main.go
:
package main
import "github.com/gopherguides/logging"
func main() {
logging.Debug(true)
logging.Log("This is a debug statement...")
fmt.Println(logging.debug)
}
Save and run the file. You will receive an error similar to the following:
保存并运行文件。 您将收到与以下类似的错误:
Output
. . .
./main.go:10:14: cannot refer to unexported name logging.debug
Now that we have seen how exported
and unexported
items in packages behave, we will next look at how fields
and methods
can be exported from structs
.
现在,我们已经了解了包中已exported
和unexported
项目的行为,接下来将研究如何从structs
导出fields
和methods
。
结构内的可见性 (Visibility Within Structs)
While the visibility scheme in the logger we built in the last section may work for simple programs, it shares too much state to be useful from within multiple packages. This is because the exported variables are accessible to multiple packages that could modify the variables into contradictory states. Allowing the state of your package to be changed in this way makes it hard to predict how your program will behave. With the current design, for example, one package could set the Debug
variable to true
, and another could set it to false
in the same instance. This would create a problem since both packages that are importing the logging
package are affected.
尽管我们在上一节中构建的记录器中的可见性方案可能适用于简单程序,但它共享了太多状态,无法在多个软件包中使用。 这是因为导出的变量可供多个程序包访问,这些程序包可能会将变量修改为矛盾的状态。 允许以这种方式更改程序包的状态使得很难预测程序的行为。 例如,在当前设计中,一个包可以将Debug
变量设置为true
,而另一个包可以在同一实例中将其设置为false
。 这将造成问题,因为导入logging
软件包的两个软件包均受到影响。
We can make the logger isolated by creating a struct and then hanging methods off of it. This will allow us to create an instance
of a logger to be used independently in each package that consumes it.
我们可以通过创建结构然后将方法挂起使记录器隔离。 这将允许我们创建一个记录器instance
,以在使用它的每个程序包中独立使用。
Change the logging
package to the following to refactor the code and isolate the logger:
将logging
包更改为以下内容以重构代码并隔离记录器:
package logging
import (
"fmt"
"time"
)
type Logger struct {
timeFormat string
debug bool
}
func New(timeFormat string, debug bool) *Logger {
return &Logger{
timeFormat: timeFormat,
debug: debug,
}
}
func (l *Logger) Log(s string) {
if !l.debug {
return
}
fmt.Printf("%s %s\n", time.Now().Format(l.timeFormat), s)
}
In this code, we created a Logger
struct. This struct will house our unexported state, including the time format to print out and the debug
variable setting of true
or false
. The New
function sets the initial state to create the logger with, such as the time format and debug state. It then stores the values we gave it internally to the unexported variables timeFormat
and debug
. We also created a method called Log
on the Logger
type that takes a statement we want to print out. Within the Log
method is a reference to its local method variable l
to get access back to its internal fields such as l.timeFormat
and l.debug
.
在此代码中,我们创建了一个Logger
结构。 该结构将容纳我们的未导出状态,包括要打印的时间格式以及debug
变量设置true
或false
。 New
功能设置用于创建记录器的初始状态,例如时间格式和调试状态。 然后,它将内部给我们的值存储到未导出的变量timeFormat
和debug
。 我们还创建了一个名为Log
on Logger
类型的方法,该方法采用了我们要打印的语句。 在Log
方法中,是对其本地方法变量l
的引用,以获取对其内部字段(如l.timeFormat
和l.debug
。
This approach will allow us to create a Logger
in many different packages and use it independently of how the other packages are using it.
这种方法将使我们能够在许多不同的程序包中创建一个Logger
,并且独立于其他程序包的使用方式来使用它。
To use it in another package, let’s alter cmd/main.go
to look like the following:
要在另一个软件包中使用它,让我们将cmd/main.go
更改为如下所示:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("This is a debug statement...")
}
Running this program will give you the following output:
运行该程序将为您提供以下输出:
Output
2019-08-28T11:56:49-05:00 This is a debug statement...
In this code, we created an instance of the logger by calling the exported function New
. We stored the reference to this instance in the logger
variable. We can now call logging.Log
to print out statements.
在此代码中,我们通过调用导出的函数New
创建了记录器的实例。 我们将对此实例的引用存储在logger
变量中。 现在我们可以调用logging.Log
来打印出语句。
If we try to reference an unexported field from the Logger
such as the timeFormat
field, we will receive a compile-time error. Try adding the following highlighted line and running cmd/main.go
:
如果尝试引用Logger
未导出的字段(例如timeFormat
字段),则会收到编译时错误。 尝试添加以下突出显示的行并运行cmd/main.go
:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("This is a debug statement...")
fmt.Println(logger.timeFormat)
}
This will give the following error:
这将产生以下错误:
Output
. . .
cmd/main.go:14:20: logger.timeFormat undefined (cannot refer to unexported field or method timeFormat)
The compiler recognizes that logger.timeFormat
is not exported, and therefore can’t be retrieved from the logging
package.
编译器识别出未导出logger.timeFormat
,因此无法从logging
包中检索它。
方法中的可见性 (Visibility Within Methods)
In the same way as struct fields, methods can also be exported or unexported.
与结构字段相同,方法也可以导出或不导出。
To illustrate this, let’s add leveled logging to our logger. Leveled logging is a means of categorizing your logs so that you can search your logs for specific types of events. The levels we will put into our logger are:
为了说明这一点,让我们向记录器中添加分级记录。 分级日志记录是对日志进行分类的一种方法,以便您可以在日志中搜索特定类型的事件。 我们将放入记录器的级别为:
The
info
level, which represents information type events that inform the user of an action, such asProgram started
, orEmail sent
. These help us debug and track parts of our program to see if expected behavior is happening.info
级别,它表示信息类型的事件,这些事件通知用户一项操作,例如“Program started
”或“Email sent
。 这些帮助我们调试和跟踪程序的各个部分,以查看是否正在发生预期的行为。The
warning
level. These types of events identify when something unexpected is happening that is not an error, likeEmail failed to send, retrying
. They help us see parts of our program that aren’t going as smoothly as we expected them to.warning
级别。 这些类型的事件可确定什么时候发生意外而不是错误,例如Email failed to send, retrying
。 他们帮助我们看到程序的某些部分运行不如预期的那样顺利。The
error
level, which means the program encountered a problem, likeFile not found
. This will often result in the program’s operation failing.error
级别,表示程序遇到了问题,例如File not found
。 这通常会导致程序的操作失败。
You may also desire to turn on and off certain levels of logging, especially if your program isn’t performing as expected and you’d like to debug the program. We’ll add this functionality by changing the program so that when debug
is set to true
, it will print all levels of messages. Otherwise, if it’s false
, it will only print error messages.
您可能还希望打开和关闭某些级别的日志记录,尤其是在您的程序未按预期执行并且希望调试该程序时。 我们将通过更改程序来添加此功能,以便在debug
设置为true
,它将打印所有级别的消息。 否则,如果为false
,则仅显示错误消息。
Add leveled logging by making the following changes to logging/logging.go
:
通过对logging/logging.go
进行以下更改来添加分级日志logging/logging.go
:
package logging
import (
"fmt"
"strings"
"time"
)
type Logger struct {
timeFormat string
debug bool
}
func New(timeFormat string, debug bool) *Logger {
return &Logger{
timeFormat: timeFormat,
debug: debug,
}
}
func (l *Logger) Log(level string, s string) {
level = strings.ToLower(level)
switch level {
case "info", "warning":
if l.debug {
l.write(level, s)
}
default:
l.write(level, s)
}
}
func (l *Logger) write(level string, s string) {
fmt.Printf("[%s] %s %s\n", level, time.Now().Format(l.timeFormat), s)
}
In this example, we introduced a new argument to the Log
method. We can now pass in the level
of the log message. The Log
method determines what level of message it is. If it’s an info
or warning
message, and the debug
field is true
, then it writes the message. Otherwise it ignores the message. If it is any other level, like error
, it will write out the message regardless.
在此示例中,我们为Log
方法引入了新的参数。 现在,我们可以传递日志消息的level
。 Log
方法确定消息的级别。 如果是info
或warning
消息,并且debug
字段为true
,那么它将写入该消息。 否则,它将忽略该消息。 如果它是任何其他级别,例如error
,它将无论如何写出消息。
Most of the logic for determining if the message is printed out exists in the Log
method. We also introduced an unexported method called write
. The write
method is what actually outputs the log message.
Log
方法中存在用于确定是否将消息打印出来的大多数逻辑。 我们还引入了一种未导出的方法,称为write
。 write
方法是实际输出日志消息的方法。
We can now use this leveled logging in our other package by changing cmd/main.go
to look like the following:
现在,我们可以通过将cmd/main.go
更改为以下内容,在其他程序包中使用此级别的日志记录:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("info", "starting up service")
logger.Log("warning", "no tasks found")
logger.Log("error", "exiting: no work performed")
}
Running this will give you:
运行此命令将为您提供:
Output
[info] 2019-09-23T20:53:38Z starting up service
[warning] 2019-09-23T20:53:38Z no tasks found
[error] 2019-09-23T20:53:38Z exiting: no work performed
In this example, cmd/main.go
successfully used the exported Log
method.
在此示例中, cmd/main.go
成功使用了导出的Log
方法。
We can now pass in the level
of each message by switching debug
to false
:
现在我们可以通过将debug
切换为false
来传递每个消息的level
:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, false)
logger.Log("info", "starting up service")
logger.Log("warning", "no tasks found")
logger.Log("error", "exiting: no work performed")
}
Now we will see that only the error
level messages print:
现在,我们将看到仅显示error
级别消息:
Output
[error] 2019-08-28T13:58:52-05:00 exiting: no work performed
If we try to call the write
method from outside the logging
package, we will receive a compile-time error:
如果尝试从logging
包外部调用write
方法,则会收到编译时错误:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("info", "starting up service")
logger.Log("warning", "no tasks found")
logger.Log("error", "exiting: no work performed")
logger.write("error", "log this message...")
}
Output
cmd/main.go:16:8: logger.write undefined (cannot refer to unexported field or method logging.(*Logger).write)
When the compiler sees that you are trying to reference something from another package that starts with a lowercase letter, it knows that it is not exported, and therefore throws a compiler error.
当编译器看到您试图引用另一个以小写字母开头的包中的内容时,它知道它没有被导出,因此会引发编译器错误。
The logger in this tutorial illustrates how we can write code that only exposes the parts we want other packages to consume. Because we control what parts of the package are visible outside the package, we are now able to make future changes without affecting any code that depends on our package. For example, if we wanted to only turn off info
level messages when debug
is false, you could make this change without affecting any other part of your API. We could also safely make changes to the log message to include more information, such as the directory the program was running from.
本教程中的记录器说明了我们如何编写仅公开希望其他程序包使用的部分的代码。 由于我们控制着包装的哪些部分在包装外部可见,因此我们现在可以进行将来的更改而不会影响依赖于包装的任何代码。 例如,如果我们只想在debug
为false时关闭info
级消息,则可以进行此更改而不会影响API的任何其他部分。 我们还可以安全地更改日志消息以包含更多信息,例如程序从中运行的目录。
结论 (Conclusion)
This article showed how to share code between packages while also protecting the implementation details of your package. This allows you to export a simple API that will seldom change for backwards compatibility, but will allow for changes privately in your package as needed to make it work better in the future. This is considered a best practice when creating packages and their corresponding APIs.
本文介绍了如何在程序包之间共享代码,同时还保护程序包的实现细节。 这样,您就可以导出一个简单的API,该API很少更改以实现向后兼容性,但是可以根据需要在包中私下进行更改,以使其在将来更好地工作。 创建包及其相应的API时,这被视为最佳实践。
To learn more about packages in Go, check out our Importing Packages in Go and How To Write Packages in Go articles, or explore our entire How To Code in Go series.
要了解有关Go中程序包的更多信息,请查看我们的Go中的导入程序包和如何在Go中编写程序包文章,或浏览我们的整个如何 在Go中编写 代码系列 。
翻译自: https://www.digitalocean.com/community/tutorials/understanding-package-visibility-in-go
java子包 父包可见性