听起来您正在寻找一种替代方法,以将相同的配置整体结构传递给每个包和每个函数。这个问题有很多解决方案(比我要在这里列出的更多),哪一个适合你,需要比我们更多地了解你的代码和你的目标,所以最好由你决定。听起来您想知道 Dave Cheney 关于功能选项的帖子是否提供了解决方案以及如何应用它。
如果您的应用程序的配置是静态的,因为它不可能通过不同的执行线程更改(变异),并且您不需要在同一个main 中创建具有不同配置的多个实例,那么一个选项是包级别变量和包初始化。如果您反对导出的包变量,您可以使用未导出的包变量并通过导出的函数控制访问。假设run 和build 是两个不同的包:
// package main
import(
"github.com/profilename/appname/build"
"github.com/profilename/appname/run"
)
func main() {
// do something to get configuration values
build.Initialize(buildFlavor, buildType)
// any post-build-initialize-pre-run-initialize stuff
run.Initialize(logLevel, port, host)
// other processing
build.PreBuild("title") // other build functions may rely on configuration
build.Build()
// other stuff
run.ReadFiles(f1, f2)
run.Validate(preferredBackupPort) // port availability, chance to log.Fatal out
run.Run()
// cleanup
}
// package run
var Host string
var LogLevel, Port int
init() {
Host = `localhost`
Port = 8888
Loglevel = 1
}
func Initialize(logLevel, port int, host string) {
// validation, panic on failure
LogLevel = logLevel
Host = host
Port = port
}
func Run() {
// do something with LogLevel, Host, Port
}
但这并不能解决 Dave Cheney 的帖子中提到的问题。如果用户在没有主机、端口或 buildType(或其他配置变量)的情况下运行它,因为他不需要这些功能怎么办?如果用户想要运行具有不同配置的多个实例怎么办?
Dave 的方法主要用于不使用包级变量进行配置的情况。实际上,它旨在启用事物的多个实例,其中每个实例可以具有不同的配置。您的可选配置参数成为单个可变参数,其中类型是修改指向正在配置的事物的指针的函数。对你来说,可能是
// package run
type Runner struct {
Port int
// rest of runner configuration
}
func NewRunner(options ...func(*Runner)) (runner *Runner, err error) {
// any setup
for _, option := range options {
err = option(runner)
if err != nil {
// do something
}
}
return runner, err
}
// package main
func main() {
// do something to get configuration values
port := func(runner *Runner) {
runner.Port = configuredPort
}
// other configuration if applicable
runner := run.NewRunner(port)
// ...
在某种程度上,Dave 的方法似乎针对将用作非常灵活的库的包,并将提供用户可能希望创建多个实例的应用程序接口。它允许main 定义启动具有不同配置的多个实例。在那篇文章中,他没有详细介绍如何处理main 或配置包中的配置输入。
请注意,上面生成的代码中设置端口的方式与此没有太大区别:
// package run
type Runner struct {
Port int
// rest of runner configuration
}
// package main, func main()
runner := new(run.Runner)
runner.Port = configuredPort
这是更传统的方法,可能对大多数开发人员来说更容易阅读和理解,如果它适合您的需求,也是一种非常好的方法。 (如果需要,您可以使 runner.port 未导出并添加 func (r *Runner) SetPort(p int) { r.port = p } 方法。)它也是一种设计,根据实现,有可能处理变异配置、多个执行线程(您需要通道或sync 包来处理那里的突变),以及多个实例。
Dave 提出的功能选项设计比该方法更强大的地方在于,当您有更多与要放置在 main 而不是 run 中的选项设置相关的语句时——那些将组成函数体。
更新 这是一个使用 Dave 的功能选项方法的可运行示例,包含在两个文件中。请务必更新导入路径以匹配您放置 run 包的位置。
包run:
package run
import(
"fmt"
"log"
)
const(
DefaultPort = 8888
DefaultHost = `localhost`
DefaultLogLevel = 1
)
type Runner struct {
Port int
Host string
LogLevel int
}
func NewRunner(options ...func(*Runner) error) (runner *Runner) {
// any setup
// set defaults
runner = &Runner{DefaultPort, DefaultHost, DefaultLogLevel}
for _, option := range options {
err := option(runner)
if err != nil {
log.Fatalf("Failed to set NewRunner option: %s\n", err)
}
}
return runner
}
func (r *Runner) Run() {
fmt.Println(r)
}
func (r *Runner) String() string {
return fmt.Sprintf("Runner Configuration:\n%16s %22d\n%16s %22s\n%16s %22d",
`Port`, r.Port, `Host`, r.Host, `LogLevel`, r.LogLevel)
}
包main:
package main
import(
"errors"
"flag"
"github.com/jrefior/run" // update this path for your filesystem
)
func main() {
// do something to get configuration values
portFlag := flag.Int("p", 0, "Override default listen port")
logLevelFlag := flag.Int("l", 0, "Override default log level")
flag.Parse()
// put your runner options here
runnerOpts := make([]func(*run.Runner) error, 0)
// with flags, we're not sure if port was set by flag, so test
if *portFlag > 0 {
runnerOpts = append(runnerOpts, func(runner *run.Runner) error {
if *portFlag < 1024 {
return errors.New("Ports below 1024 are privileged")
}
runner.Port = *portFlag
return nil
})
}
if *logLevelFlag > 0 {
runnerOpts = append(runnerOpts, func(runner *run.Runner) error {
if *logLevelFlag > 8 {
return errors.New("The maximum log level is 8")
}
runner.LogLevel = *logLevelFlag
return nil
})
}
// other configuration if applicable
runner := run.NewRunner(runnerOpts...)
runner.Run()
}
示例用法:
$ ./program -p 8987
Runner Configuration:
Port 8987
Host localhost
LogLevel 1