GO——接口(上)

接口

接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。

但Go语言中接口类型的独特之处在于它是满足隐式实现的。也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地拥有一些必需的方法就足够了。这种设计可以让你创建一个新的接口类型满足已经存在的具体类型却不会去改变这些类型的定义;当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。

约定

具体类型+接口类型(接口类型是一种抽象的类型。它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会表现出它们自己的方法)。

它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合;它们只会表现出它们自己的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,唯一知道的就是可以通过它的方法来做什么。

package fmt

func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
func Printf(format string, args ...interface{}) (int, error) {
    return Fprintf(os.Stdout, format, args...)
}
func Sprintf(format string, args ...interface{}) string {
    var buf bytes.Buffer
    Fprintf(&buf, format, args...)
    return buf.String()
}

即使Fprintf函数中的第一个参数也不是一个文件类型。它是io.Writer类型,这是一个接口类型定义如下:

package io

type Writer interface {
    Write(p []byte) (n int, err error)
}

类型

接口类型具体描述了一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。

io.Writer类型是用得最广泛的接口之一,因为它提供了所有类型的写入bytes的抽象,包括文件类型,内存缓冲区,网络链接,HTTP客户端,压缩工具,哈希等等。Reader可以代表任意可以读取bytes的类型,Closer可以是任意可以关闭的值。

我们发现有些新的接口类型通过组合已有的接口来定义。下面是两个例子:

type ReadWriter interface {
    Reader
    Writer
}
type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

上面用到的语法和结构内嵌相似,我们可以用这种方式以一个简写命名一个接口,而不用声明它所有的方法。这种方式称为接口内嵌。尽管略失简洁,我们可以像下面这样,不使用内嵌来声明io.ReadWriter接口。

type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

或者甚至使用一种混合的风格:

type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Writer
}

实现接口的条件

一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。

接口指定的规则非常简单:表达一个类型属于某个接口只要这个类型实现这个接口。所以:

var w io.Writer
w = os.Stdout           // OK: *os.File has Write method
w = new(bytes.Buffer)   // OK: *bytes.Buffer has Write method
w = time.Second         // compile error: time.Duration lacks Write method

T类型的值不拥有所有*T指针的方法,这样它就可能只实现了更少的接口。

IntSet类型的String方法的接收者是一个指针类型,所以我们不能在一个不能寻址的IntSet值上调用这个方法:

type IntSet struct { /* ... */ }
func (*IntSet) String() string
var _ = IntSet{}.String() // compile error: String requires *IntSet receiver

但是我们可以在一个IntSet变量上调用这个方法:

var s IntSet
var _ = s.String() // OK: s is a variable and &s has a String method

然而,由于只有*IntSet类型有String方法,所以也只有*IntSet类型实现了fmt.Stringer接口:

var _ fmt.Stringer = &s // OK
var _ fmt.Stringer = s  // compile error: IntSet lacks String method

interface{}被称为空接口类型是不可或缺的。因为空接口类型对实现它的类型没有要求,所以我们可以将任意一个值赋给空接口类型。

一个具体的类型可能实现了很多不相关的接口。

不像基于类的语言,他们一个类实现的接口集合需要进行显式的定义,在Go语言中我们可以在需要的时候定义一个新的抽象或者特定特点的组,而不需要修改具体类型的定义。当具体的类型来自不同的作者时这种方式会特别有用。

flag.Value接口

为我们自己的数据类型定义新的标记符号是简单容易的。我们只需要定义一个实现flag.Value接口的类型。

(所谓标记就是类似于在命令行中执行:./sleep -period 50ms中的-period一样)

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方法解析它的字符串参数并且更新标记变量的值。

type celsiusFlag struct {
	tempconv.Celsius
}

func (f *celsiusFlag) String() string {
	return fmt.Sprintf("%gC", f.Celsius)
}

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

func CelsiusFlag(name string, value tempconv.Celsius, usage string) *tempconv.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)
}

相当于Set()方法用于从命令行中获取到-period后面跟的值,然后进行处理;String()方法则是在需要输出的时候进行各式的转换。在这里的话是在fmt.Println(*temp)调用String()的。

总而言之,如果说这个类想要调用flag.CommandLine.Var这个方法修改参数的话,必须要实现flag.Value这个接口才行。

具体流程:

  1. 先根据flag.CommandLine.Var(&f, name, usage)传入的name解析命令行(在flag.Parse()的时候解析的)
  2. 然后对这个f调用Set(),传入的是跟在命令行name后的字符串。这样temp就相当于从命令行的标记中获取到传入的信息了。
  3. 调用fmt.Println(*temp),这个时候用到了Strings()。String方法格式化标记的值用在命令行帮助消息中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值