Go Interface接口初学者手册
作者:海上明月A
Interface接口类型实现
接口是一组方法签名。如果一个类型在其上定义了给定接口的所有方法,则该类型 "实现" 了该接口。
在以下示例中,“形状” 必须能够返回其面积和周长。rect 和 circle 都满足该接口。
type shape interface { area() float64 perimeter() float64 } type rect struct { width, height float64 } func (r rect) area() float64 { return r.width * r.height } func (r rect) perimeter() float64 { return 2*r.width + 2*r.height } type circle struct { radius float64 } func (c circle) area() float64 { return math.Pi * c.radius * c.radius } func (c circle) perimeter() float64 { return 2 * math.Pi * c.radius }
当一个类型实现一个接口时,它可以被用作该接口类型。
接口的实现是隐式的。
类型从不声明它实现了给定的接口。如果接口存在,并且类型定义了适当的方法,那么该类型会自动满足该接口。
Multiple Interfaces
在Go中,一个类型可以实现任意数量的接口。例如,空接口 interface{}
总是被每种类型实现,因为它没有要求。
Naming interface args
考虑以下接口:
type Copier interface { Copy(string, string) int }
根据仅有的代码,你能推断出应该将什么类型的字符串传递给 Copy
函数吗?
我们知道函数签名期望两个字符串类型,但它们是什么?文件名?URL?原始字符串数据?另外,返回的那个整数到底是什么?
让我们添加一些有名的参数和返回数据,使其更清晰。
type Copier interface { Copy(sourceFile string, destinationFile string) (bytesCopied int) }
好多了。现在我们可以看到期望是什么了。第一个参数是 sourceFile
,第二个参数是 destinationFile
,并返回了一个整数 bytesCopied
。
Type Assertions in Go
在Go中使用接口时,偶尔需要访问接口值的底层类型。您可以使用类型断言将接口转换为其底层类型。
type shape interface { area() float64 } type circle struct { radius float64 } // "c" is a new circle cast from "s" // which is an instance of a shape. // "ok" is a bool that is true if s was a circle // or false if s isn't a circle c, ok := s.(circle) if !ok { // s wasn't a circle log.Fatal("s is not a circle") } radius := c.radius
Type Switches in Go
类型开关使得在一系列中执行多个类型断言变得很容易。
类型开关类似于常规的开关语句,但是情况指定的是类型而不是值。
func printNumericValue(num interface{}) { switch v := num.(type) { case int: fmt.Printf("%T\n", v) case string: fmt.Printf("%T\n", v) default: fmt.Printf("%T\n", v) } } func main() { printNumericValue(1) // prints "int" printNumericValue("1") // prints "string" printNumericValue(struct{}{}) // prints "struct {}" }
fmt.Printf("%T\n", v)
打印变量的类型。
Clean Interfaces
编写清晰的接口很难。坦率地说,每当在代码中处理抽象概念时,如果不小心,简单的事情很快就会变得复杂。让我们回顾一些保持接口清晰的经验法则。
Keep Interfaces Small
如果你从这篇文章中只记住一点建议,那就是:保持接口的小巧!接口的目的是定义准确表示一个想法或概念所需的最小行为。
以下是标准 HTTP 包中的一个较大接口的示例,它很好地演示了定义最小行为的概念:
type File interface { io.Closer io.Reader io.Seeker Readdir(count int) ([]os.FileInfo, error) Stat() (os.FileInfo, error) }
满足接口行为的任何类型都可以被 HTTP 包视为 File。这很方便,因为 HTTP 包不需要知道它处理的是磁盘上的文件,网络缓冲区还是简单的 []byte。
Interfaces Should Have No Knowledge of Satisfying Types
接口应该定义其他类型需要满足的条件以成为该接口的成员。它们在设计时不应该知道任何恰好满足该接口的类型。
例如,假设我们正在构建一个描述定义汽车所需组件的接口。
type car interface { Color() string Speed() int IsFiretruck() bool }
Color()
和 Speed()
完全有道理,它们是限定在汽车范围内的方法。IsFiretruck()
是一个反模式。我们强制所有汽车声明它们是否是消防车。为了使这种模式有任何意义,我们需要一个可能的子类型清单。IsPickup()
、IsSedan()
、IsTank()
... 这会无休止地扩展。
相反,开发人员应该依赖于类型断言的本机功能,在给定汽车接口的实例时推导出底层类型。或者,如果需要子接口,可以这样定义:
type firetruck interface { car HoseLength() int }
这个接口继承了 car
所需的方法,并添加了一个额外的必需方法,使车辆成为消防车。
Interfaces Are Not Classes
接口不是类,它们更加轻量。
接口没有构造函数或析构函数,不要求创建或销毁数据。
接口在本质上不是层次结构,尽管有语法糖可以创建接口,这些接口碰巧是其他接口的超集。
接口定义了函数签名,但不定义底层行为。创建接口通常不会在结构方法方,使代码更加干燥。例如,如果有五种类型满足 fmt.Stringer
接口,它们都需要有自己版本的 String()
函数。
以上就是Go Interfaces接口初学者手册的详细内容,更多关于Go Interfaces接口的资料请关注脚本之家其它相关文章!