|
|
51CTO旗下网站
|
|
移动端

9.1.2 接口类型与约定

《Go语言编程入门与实战技巧》第9章接口与反射,本章需要注意设计类型时确认类型的本质是原始的还是非原始的。接口是声明了一组行为并支持多态的类型,嵌入类型提供了扩展类型的能力,而无须使用继承。本节为大家介绍接口类型与约定。

作者:黄靖钧来源:电子工业出版社|2018-09-23 09:21

9.1.2  接口类型与约定

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

io.Writer类型是用得最广泛的接口之一,因为它提供了所有的类型写入bytes(字节)的抽象,包括文件类型、内存缓冲区、网络链接、HTTP客户端、压缩工具、哈希等。io包中定义了很多其他有用的接口类型。io.Reader可以代表任意可读取bytes的类型,io.Closer可以是任意可关闭的值,例如一个文件或网络链接:

  1. package io  
  2.  
  3. type Reader interface {  
  4.     Read(p []byte) (n int, err error)  
  5. }  
  6. type Closer interface {  
  7.     Close() error  
  8. }  

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

  1. type ReadWriter interface {  
  2.     Reader  
  3.     Writer  
  4. }  
  5. type ReadWriteCloser interface {  
  6.     Reader  
  7.     Writer  
  8.     Closer  
  9. }  

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

  1. type ReadWriter interface {  
  2.     Read(p []byte) (n int, err error)  
  3.     Write(p []byte) (n int, err error)  
  4. }  

或者可以使用混合的风格:

  1. type ReadWriter interface {  
  2.     Read(p []byte) (n int, err error)  
  3.     Writer  
  4. }  

上面3种定义方式都是一样的效果。方法的顺序变化也没有影响,唯一重要的就是这个集合里面的方法。

1.动态类型

一个接口类型的变量varI中可以包含任何类型的值,必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中动态类型可能会有所不同,但是它一定是可以分配给接口变量的类型。通常可以使用类型断言(Go语言内置的一种智能推断类型的功能)来测试在某个时刻varI是否包含类型T的值:

  1. :varI.(T)       // 未经检查的类型断言 

varI必须是一个接口变量,否则编译器会报错:invalid type assertion: varI.(T) (non-interface type (type of varI) on left) 。

类型断言可能是无效的,虽然编译器会尽力检查转换是否有效,但是它不可能预见所有的可能性。如果转换在程序运行时失败,则会导致错误发生。更安全的方式是使用以下形式来进行类型断言:

  1. if v, ok :varI.(T); ok {  // 已检查类型断言  
  2.     Process(v)  
  3.     return  
  4. }  
  5. // varI不是类型T  

如果转换合法,v是varI转换到类型T的值,ok会是true;否则v是类型T的零值,ok是false,也没有运行时错误发生。应该使用这种方式来进行类型断言。

在多数情况下,可能只是想在if中测试ok的值,此时使用以下的方法会是最方便的:

  1. if _, ok :varI.(T); ok {  
  2.     // ...  
  3. }  

具体示例如下:

  1. // 示例代码9-4  
  2. package main  
  3.  
  4. import (  
  5.     "fmt"  
  6.     "math"  
  7. )  
  8.  
  9. type Square struct {  
  10.     side float32  
  11. }  
  12.  
  13. type Circle struct {  
  14.     radius float32  
  15. }  
  16.  
  17. type Shaper interface {  
  18.     Area() float32  
  19. }  
  20.  
  21. func main() {  
  22.     var areaIntf Shaper  
  23.     sq1 :new(Square)  
  24.     sq1.side = 5 
  25.  
  26.     areaIntf = sq1 
  27.     // areaIntf 的类型是否是 Square  
  28.     if t, ok :areaIntf.(*Square); ok {  
  29.         fmt.Printf("areaIntf的类型是:%T\n", t)  
  30.     }  
  31.     if u, ok :areaIntf.(*Circle); ok {  
  32.         fmt.Printf("areaIntf的类型是:%T\n", u)  
  33.     } else {  
  34.         fmt.Println("areaIntf 不含类型为 Circle 的变量")  
  35.     }  
  36. }  
  37.  
  38. func (sq *Square) Area() float32 {  
  39.     return sq.side * sq.side  
  40. }  
  41.  
  42. func (ci *Circle) Area() float32 {  
  43.     return ci.radius * ci.radius * math.Pi  
  44. }  
  45. /*  
  46. 返回:  
  47. areaIntf的类型是:*main.Square  
  48. areaIntf 不含类型为 Circle 的变量  
  49. */  

程序行中定义了一个新类型Circle,它也实现了Shaper接口。

第一个if语句测试areaIntf里是否包含一个Square类型的变量,返回的ok为true则表示包含该类型;然后第二个if语句测试它是否包含一个Circle类型的变量,返回的OK结果为false,所以不包含该类型。

如果忽略areaIntf.(*Square)中的*号,会导致编译错误:

  1. impossible type assertion: Square does not implement Shaper (Area method has pointer receiver) 

这是因为Go语言编译器无法自动推断类型,Area()方法通过指针接收器传入参数。

2.类型判断

接口变量的类型也可以使用type-swtich来检测(部分代码):

  1. switch t :areaIntf.(type) {  
  2. case *Square:  
  3.     fmt.Printf("Square类型的 %T 值为:%v\n", t, t)  
  4. case *Circle:  
  5.     fmt.Printf("Circle类型的 %T 值为:%v\n", t, t)  
  6. case nil:  
  7.     fmt.Printf("nil值:发生了意外。\n")  
  8. default:  
  9.     fmt.Printf("未知类型 %T\n", t)  
  10. }  
  11. /*  
  12. 返回:  
  13. Square类型的 *main.Square 值为:&{5}  
  14. */ 

变量t得到了areaIntf的值和类型,所有case语句中列举的类型(nil除外)都必须实现对应的接口(在上例中即Shaper),如果被检测类型没有在case语句列举的类型中,就会执行default语句。

可以用type-switch进行运行时类型分析,但是type-switch不允许有fallthrough。如果仅仅是测试变量的类型,不用它的值,那么就可以不需要赋值语句,比如:

  1. switch areaIntf.(type) {  
  2. case *Square:  
  3.     // ...  
  4. case *Circle:  
  5.     // ...  
  6. ...  
  7. default:  
  8.     // ...  
  9. }  

下面的代码片段展示了一个类型分类函数,它有一个可变长度参数,可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作:

  1. func classifier(items ...interface{}) {  
  2.     for i, x :range items {  
  3.         switch x.(type) {  
  4.         case bool:  
  5.             fmt.Printf("参数 #%d 类型是 bool\n", i)  
  6.         case float64:  
  7.             fmt.Printf("参数 #%d 类型是 float64\n", i)  
  8.         case int, int64:  
  9.             fmt.Printf("参数 #%d 类型是 int\n", i)  
  10.         case nil:  
  11.             fmt.Printf("参数 #%d 类型是 nil\n", i)  
  12.         case string:  
  13.             fmt.Printf("参数 #%d 类型是 string\n", i)  
  14.         default:  
  15.             fmt.Printf("参数 #%d 类型未知\n", i)  
  16.         }  
  17.     }  
  18. }  

可以这样调用此方法:classifier(13, -14.3, "BELGIUM", complex(1, 2), nil, false)。在处理来自外部的、类型未知的数据时,比如解析诸如JSON或XML编码的数据,类型测试和转换会非常有用。


喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书会第9群:808517103

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

网络管理员备考训练——计算机与网络基础知识

本书是根据全国计算机技术与软件专业技术资格(水平)考试《网络管理员考试大纲》所要求的考试范围而编写的试题集。全书共分10个单元,同步...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊