go语言接口之flag.Value接口

我们先看下这个会休眠特定时间的程序:

复制代码
var period = flag.Duration("period", 1*time.Second, "sleep period")

func main() {
    flag.Parse()
    fmt.Printf("Sleeping for %v...", *period)
    time.Sleep(*period)
    fmt.Println()
}

在它休眠前它会打印出休眠的时间周期。fmt包调用time.Duration的String方法打印这个时间 周期是以用户友好的注解方式,而不是一个纳秒数字:

复制代码
$ go build gopl.io/ch7/sleep
$ ./sleep
Sleeping for 1s...

默认情况下,休眠周期是一秒,但是可以通过 -period 这个命令行标记来控制。flag.Duration 函数创建一个time.Duration类型的标记变量并且允许用户通过多种用户友好的方式来设置这 个变量的大小,这种方式还包括和String方法相同的符号排版形式。这种对称设计使得用户交 互良好。

复制代码
$ ./sleep -period 50ms
Sleeping for 50ms...
$ ./sleep -period 2m30s
Sleeping for 2m30s...
$ ./sleep -period 1.5h
Sleeping for 1h30m0s...
$ ./sleep -period "1 day"
invalid value "1 day" for flag -period: time: invalid duration 1 day

因为时间周期标记值非常的有用,所以这个特性被构建到了flag包中;但是我们为我们自己的 数据类型定义新的标记符号是简单容易的。我们只需要定义一个实现flag.Value接口的类型,如下:

复制代码
package flag

// Value is the interface to the value stored in a flag.
type Value interface {
    String() string
    Set(string) error
}

String方法格式化标记的值用在命令行帮组消息中;这样每一个flag.Value也是一个 fmt.Stringer。Set方法解析它的字符串参数并且更新标记变量的值。实际上,Set方法和String 是两个相反的操作,所以最好的办法就是对他们使用相同的注解方式。

让我们定义一个允许通过摄氏度或者华氏温度变换的形式指定温度的celsiusFlag类型。注意 celsiusFlag内嵌了一个Celsius类型,,因此不用实现本身就已经有String方法了。为了实 现flag.Value,我们只需要定义Set方法:

复制代码
// *celsiusFlag satisfies the flag.Value interface.
type celsiusFlag struct{ Celsius }

func (f *celsiusFlag) Set(s string) error {
    var unit string
    var value float64
    fmt.Sscanf(s, "%f%s", &value, &unit) // no error check needed
    switch unit {
    case "C", "°C":
        f.Celsius = Celsius(value)
        return nil
    case "F", "°F":
        f.Celsius = FToC(Fahrenheit(value))
        return nil
    }
    return fmt.Errorf("invalid temperature %q", s)
}

调用fmt.Sscanf函数从输入s中解析一个浮点数(value)和一个字符串(unit)。虽然通常必 须检查Sscanf的错误返回,但是在这个例子中我们不需要因为如果有错误发生,就没有switch case会匹配到。

下面的CelsiusFlag函数将所有逻辑都封装在一起。它返回一个内嵌在celsiusFlag变量f中的 Celsius指针给调用者。Celsius字段是一个会通过Set方法在标记处理的过程中更新的变量。 调用Var方法将标记加入应用的命令行标记集合中,有异常复杂命令行接口的全局变量 flag.CommandLine.Programs可能有几个这个类型的变量。调用Var方法将一个celsiusFlag参 数赋值给一个flag.Value参数,导致编译器去检查celsiusFlag是否有必须的方法。

复制代码
// CelsiusFlag defines a Celsius flag with the specified name,
// default value, and usage, and returns the address of the flag variable.
// The flag argument must have a quantity and a unit, e.g., "100C".
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
    f := celsiusFlag{value}
    flag.CommandLine.Var(&f, name, usage)
    return &f.Celsius
}

现在我们可以开始在我们的程序中使用新的标记:

复制代码
var temp = tempconv.CelsiusFlag("temp", 20.0, "the temperature")

func main() {
    flag.Parse()
    fmt.Println(*temp)
}

下面是典型的场景:

复制代码
$ go build gopl.io/ch7/tempflag
$ ./tempflag
20°C
$ ./tempflag -temp -18C
-18°C
$ ./tempflag -temp 212°F
100°C
$ ./tempflag -temp 273.15K
invalid value "273.15K" for flag -temp: invalid temperature "273.15K"
Usage of ./tempflag:
    -temp value
        the temperature (default 20°C)
$ ./tempflag -help
Usage of ./tempflag:
    -temp value
        the temperature (default 20°C)
相关推荐
emplace_back13 分钟前
C# 集合表达式和展开运算符 (..) 详解
开发语言·windows·c#
jz_ddk19 分钟前
[学习] C语言数学库函数背后的故事:`double erf(double x)`
c语言·开发语言·学习
萧曵 丶29 分钟前
Rust 所有权系统:深入浅出指南
开发语言·后端·rust
xiaolang_8616_wjl34 分钟前
c++文字游戏_闯关打怪2.0(开源)
开发语言·c++·开源
收破烂的小熊猫~42 分钟前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
老任与码1 小时前
Spring AI Alibaba(1)——基本使用
java·人工智能·后端·springaialibaba
nananaij1 小时前
【Python进阶篇 面向对象程序设计(3) 继承】
开发语言·python·神经网络·pycharm
阿蒙Amon1 小时前
为什么 12 版仍封神?《C# 高级编程》:从.NET 5 到实战架构,进阶者绕不开的必修课
开发语言·c#
无小道1 小时前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++