8. 方法抽象与接口

上一节中谈到了面向对象,并且定义了一堆方法,从对象的角度定义了其操作的方法。然而,你是谁,我并不关心,我只关心你能为我干什么,是所有不同对象接触的一个界面。在Go中,接口就是这样一种抽象类型,它定义了一个对象的行为,即定义了你应该做什么,但具体怎么做,接口并不管。如果说封装是保护了代码实现的内部,那么接口则是规范了交互约定,保护了外部代码,两者是相辅相成的。定义接口对于团队协作,保护外部代码,维持历史软件版本非常重要。具体来说,接口是一个或几个方法签名的集合,如果一个类型定义了接口中所有方法,就实现了该接口。Unix/Linux系统中流传着一句话叫 "Everything is a file",即一切对象都是文件(更为确切的应当是一切对象都是文件描述符)设备是文件自身是文件、设备是文件、网络是文件,进程都可以是一个文件,可以文件是一个抽象,一切支持读写操作接口,事实上操作一个普通文件和操作一个设备,其底层差异是非常巨大的,但面对对象与接口设计让他们都成为了文件。

接口的定义与声明

// 定义接口
type InterfaceNameA interface {
    Method1()
    Method2()
}
type InterfaceNameB interface{
    Method3()
    Method4()
}
type InterfaceNameC interface{
    InterfaceNameA
    InterfaceNameB
    Method5()
}
// 声明
var IN InterfaceNameA
// 调用
IN = DataType
IN.MethodX()

接口的定义不像结构体,其对顺序没有要求,或者说不区分顺序。在接口中可以直接写入方法签名或使用另一个接口。要实现一个接口,就必须实现接口中的所有方法。一个数据类型可以实现多个接口类型。

接口的声明需要通过 var 关键字实现,无法使用类型推导。接口类型的零值是 nil,如果声明接口却没有赋值或没有实现,调用就会出错。

下面看一个具体的例子。假设名为"生长"的接口,里面包含"开花"、"结果"两个方法,用于演示植物生长过程。

package main

import (
    "fmt"
    "time"
)

// GrowUp interface has methods bloom, fructify
type GrowUp interface {
    bloom() string
    fructify() string
}

type sumflower string
type apple string

func (app apple) bloom() string {
    return fmt.Sprintf("Apple Tree %s Bloom at %s", string(app), time.Now().Format("2006-01-02 15:04:05"))
}

func (app apple) fructify() string {
    return fmt.Sprintf("Apple Tree %s Fructified at %s", string(app), time.Now().Format("2006-01-02 15:04:05"))
}

func (sf sumflower) bloom() string {
    return fmt.Sprintf("Sumflower %s Bloom at %s", string(sf), time.Now().Format("2006-01-02 15:04:05"))
}

func (sf sumflower) fructify() string {
    return fmt.Sprintf("Sumflower %s Fructified at %s", string(sf), time.Now().Format("2006-01-02 15:04:05"))
}

func main() {
    Littlesmile := sumflower("LittleSmile")
    Sweet := apple("Sweet")
    var gup GrowUp
    gup = Littlesmile
    fmt.Println("This is", Littlesmile)
    fmt.Println(gup.bloom())
    time.Sleep(2 * time.Second)
    fmt.Println(gup.fructify())
    gup = Sweet
    fmt.Println("This is", Sweet)
    fmt.Println(gup.bloom())
    time.Sleep(3 * time.Second)
    fmt.Println(gup.fructify())
}
/* ------------Result----------
This is LittleSmile
Sumflower LittleSmile Bloom at 2018-11-20 00:44:33
Sumflower LittleSmile Fructified at 2018-11-20 00:44:35
This is Sweet
Apple Tree Sweet Bloom at 2018-11-20 00:44:35
Apple Tree Sweet Fructified at 2018-11-20 00:44:38
*/

//跟其他语言不同,Go 实现一个接口不需要显式说明,只要实现了,就可以使用。

在这个例子中,声明了两种植物向日葵(sumflower)和苹果,按照接口要求,都定义了“开花”和“结果”的方法,与前一节面向对象编程时的main函数不同,我们没有单纯的使用创建对象,调用对象方法,而是创建了对象,声明了GroupUp接口类型变量 gup,然后神奇的将 apple 对象 Sweetsumflower 对象 Littlesmile 赋值给了 gup,然后还成功的调用了他们的方法展现了生长的过程。这就是接口,它只关心方法,不关心对象,这对于进一步处理更多类型的“生长”留下了良好的基础,因为你不必再关心那些植物有哪些可用的方法了。如果有一个植物没有实现 GrowUp 接口,然后赋值给 gup 会发生什么,会在编译时生成panic导致失败。对于 var gup GrowUp 如果没有经过 gup = 的赋值,那么 gup 就会是一个 nil,这也是接口唯一可以比较的对象 gup == nil,如果判定成功,那么对于后续接口的使用就应该立即避免,否则会引发 panic

接口是一个interface类型变量,那么它就和其他所有类型一致,所有类型值可以使用的习惯,在接口处也全部成立。例如,切片、循环、指针等等。

sumflowerA := sumflower("sumflowerA")
sumflowerB := sumflower("sumflowerB")
appleA := apple("appleA")
appleB := apple("appleB")
gups := []GrowUp{sumflowerA, sumflowerB, appleA, appleB}
for _, v := range gups {
    fmt.Println(v.bloom())
}
/* ----result----------
Sumflower sumflowerA Bloom at 2018-11-20 10:03:34
Sumflower sumflowerB Bloom at 2018-11-20 10:03:34
Apple Tree appleA Bloom at 2018-11-20 10:03:34
Apple Tree appleB Bloom at 2018-11-20 10:03:34

如果一个接口没有约定方法 type i interface{},将其称之为空接口,所有类型都实现了空接口。空接口有什么用呢?如上那个例子,空接口可以作为任何类型的接收器,用于承载任何数据。之前都没有做任何接口数据的介绍,只关注方法,接口确实是这样,但不代表接口没有值。接口在Go内部可视为(type, value)表达,type 为底层一种数据类型,value就是value,正是由于这种特性,接口才能承载任何对象。看下面例子,可能会理解的更为直观一点。

for _, v := range gups {
    fmt.Printf("%T %+v",v,v)
}
/* ---result-----
main.sumflower sumflowerA
main.sumflower sumflowerB
main.apple appleA
main.apple appleB
*/

假如知道底层类型,那是不是可以获取是值呢,答案是肯定的,这需要用到类型断言,怎么叫断言,就是对数据强制转换。typevalue, ok := i.(type) ,例如延续上面的例子

// 将循环的语句修改为
fmt.Printf("%T %+v\n", v.(sumflower), v.(sumflower))
/* ----- result ---------
main.sumflower sumflowerA
main.sumflower sumflowerB
panic: interface conversion: main.GrowUp is main.apple, not main.sumflower

断言结果无非两种:如果是 sumflower 类型,那么就断言成功,获取sumflower的值,如果是 apple 类型,那么断言失败,导致运行panic异常。如果完整使用断言 typevalue, ok := i.(type) 这时断言失败,就不会引发 panic。除非知道自己干什么,否则不要轻易断言。

sfv, ok := v.(sumflower)
if ok {
    fmt.Printf("%T %+v\n", sfv, sfv)
}
/* --------result------------
main.sumflower sumflowerA
main.sumflower sumflowerB
*/

还记得fmt.Println(),它可以打印任何类型值,看一下 fmt.Println() 签名,func Println(a ...interface{}) (n int, err error) 就是一个空接口,如果现在去实现这么一个功能,就可以这样做

switch i.(type){
    case int: fmt.Printf("%d\n",i.(int))
    case float64: fmt.Printf("%.2f\n",i.(float64))
    case string: fmt.Printf("%s\n",i.(string))
    default: fmt.Printf("Unknown\n")
}

GO 库中存在很多接口,io.Reader,io.Writer,fmt.Stringer,sort.Interface,http.Handler,将在X.5做简单介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352