golang 基础(20) 接口

square-gopher.png
interface.jpg

接口

什么是 interface

提到接口,我们会联想到汽车的接口,也就是让司机如何操作汽车按自己意愿使用,用户界面也是接口,会联想到很多很多。接口看了很多,对初学者比较难理解,但是一旦理解了并可以灵活运用了,接口 power 还是很大的。
接口可以理解是规范、协议、用户使用手册和对类型抽象,对行为描述。说了这么一大堆还需要您自己了解。

In object-oriented programming, a protocal or interface is a common means for unrelated objects to communicate with each other
wikipedia

上面的话摘字 wiki,这里传递了两个信息

  • communicate 接口是用于通讯,接口就是用来定义通讯遵循的规则
  • unrelated objects 没有关系的对象,接口定义通讯规则可以使用两个互不相干的对象。
1280px-Lego_dimensions.svg.png

乐高玩具就是一个好的例子。乐高玩具的一个piece 组合时只要遵守尺寸规则,无论大小和颜色就可以组合在一起进行拼接。

以后兼职工作也是一样只要满足规定的条件,在拼接 Lego 玩具时是否可以拼接是和piece 的颜色和形状没有关系的,只要他们都遵守一定尺寸就可以进行拼接。在软件控制模块搭建和通讯也是通过定义一定接口规范来实现了。我想软件工程也在某些方面借鉴传统的行业。

那么什么是 go 的 interface

  • abstract types
  • concrete types

当然在 go 语言中有很多种类型,不过我们大致可以将归为两种一种类型属于abstract类型(抽象类型)和concrete类型(实体类型)

concrete 类型
  • 用于描述类型在内存中分配情况
    int8/int16/int32/int64/struct/float
  • 使用方法赋予数据一定的行为
type Number int
func (n Number) Positive() bool{
    return n >0
}
abstract 类型

抽象类型并没定义描述如何为这种类型分配内存空间,而是描述类型的行为。按行为为类型进行划分。这些抽象类型有io.Readerio.Writerfmt.String等等

type Positiver interface{
  Positive() bool
}

用来说明 go 语言接口的经典接口 Writer 和 Reader 接口

type Reader interface{
  Read(b []byte)(int,error)
}
type Writer interface{
  Write(b []byte)(int,error)
}

只要实现了接口的方法的类型就属于接口类型,所以集合是普通类型的集合。我们接口是可以组合,但是接口越详细确定范围也就小 weak

type ReadWriter interface{
  Read
  Writer
}

这里有一个 interface{} Rob Pike 指着interface{}是没有任何意思,因为没有任何限制,没有限制也就是没有意义,这个应该不难理解

type ConsoleWriter struct{

}

继续前面的内容,定义一个 struct,struct 表示数据抽象而 interface 表示对行为抽象

func (cw ConsoleWriter) Write(data []byte)(int, error){
    n, err := fmt.Println(string(data))
    return n,err
}

上面的代码表示如何让 ConsoleWriter 来实现接口 Writer 实现,go 语言对接口实现与其他语言不通,只要我们 struct 作为接受者具有一个 interface 方法名以及参数和返回值类型都相同的方法,就说明该 struct 实现了该接口。

func main()  {
    var w Writer = ConsoleWriter{}
    w.Write([]byte("Hello Interface"))
}

使用起来也很方便,只要实现了 Write 方法的 struct 都可以看成 Writer 按接口分类的类型。这个可以理解为 java 的向上转型也可以理解为多态一种表现。

在这里 go 语言对接口命名也有一定规则可循,就是如果我们定义一个只有方法接口,接口名称应以 er 结尾 Writer 而方法名为 Write。

type Incrementer interface{
    Increment() int
}

type IntCounter int

func (ic *IntCounter) Increment() int {
    *ic++
    return int(*ic)
}

func main()  {
    myInt := IntCounter(0)
    var inc Incrementer = &myInt
    for i := 0; i < 10; i++ {
        fmt.Println(inc.Increment())
    }
}

接口的组合

在 go 语言中我们可以通过接口组合来实现 struct 实现多接口。这个例子相对复杂并且贴近真实项目。

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

type Closer interface{
    Close() error
}

这里定义两个接口 Writer 和 Closer,分别提供了 Write 和 Close 两个方法来实现对文件写入和关闭。


type WriterCloser interface{
    Writer
    Closer
}

我们可以定义 WriterCloser 接口包含了 Writer 接口同时也包含了 Closer 接口两个接口。


type BufferedWriterCloser struct{
    buffer *bytes.Buffer
}

定义 struct BufferedWriterCloser 来实现 WriterCloser 接口。
然后 BufferedWriterCloser 来分别实现 Write 和 Close 方法以实现 WriterCloser 这个接口。

func (bwc *BufferedWriterCloser) Write(data []byte)(int, error){
    n, err := bwc.buffer.Write(data)
    if err != nil{
        return 0, err
    }
    v := make([]byte,8)
    for bwc.buffer.Len() > 8 {
        _, err := bwc.buffer.Read(v)
        if err != nil{
            return 0, err
        }

        _, err = fmt.Println(string(v))
        if err != nil{
            return 0, err
        }
    }
    return n, nil
}


func (bwc *BufferedWriterCloser) Close() error{
    for bwc.buffer.Len() > 0 {
        data := bwc.buffer.Next(8)
        _, err := fmt.Println(string(data))
        if err != nil{
            return err
        }
    }
    return nil
}


func NewBufferedWriterCloser() *BufferedWriterCloser{
    return &BufferedWriterCloser{
        buffer: bytes.NewBuffer([]byte{}),
    }
}
func main()  {
    myInt := IntCounter(0)
    var inc Incrementer = &myInt
    for i := 0; i < 10; i++ {
        fmt.Println(inc.Increment())
    }

    var wc WriterCloser = NewBufferedWriterCloser()
    wc.Write([]byte("Hello jianshu readers, this is a test"))
    wc.Close()
}

接口检查

其实在实际开发中有时候我们不知道某个结构是否已经实现接口所有方法。
通过接口.(接收者)(wc.(*BufferedWriterCloser))可以检查接收者是否已经实现了该接口的所有方法

    bwc := wc.(*BufferedWriterCloser)
    fmt.Println(bwc)

如果接收者已经实现了接口的所有的方法会返回一个对象,否则就会panic错误,大家都知道我们在程序运行时是不希望看到panic的,这个代价太expansive。例如我们传入 io.Reader就会发生错误。

    bwc := wc.(io.Reader)
    fmt.Println(bwc)
interface conversion: *main.BufferedWriterCloser is not io.Reader: missing method Read

其实检查类型会给我们返回两个类型

    r, ok := wc.(io.Reader)
    if ok {
        fmt.Println(r)
    }else{
        fmt.Println("Conversion failed")
    }

空接口

对于一个空接口来说,可以表示任何类型。所以当我们无法判断变量类型时候可以为其指定 interface{}类型。但是大家对interface{}要慎用,因为他代表任何类型没有任何限制同时也就失去意义。下面是一个判断类型例子。

    var i interface{} = 0
    switch i.(type) {
    case int:
        fmt.Println("i is an integer")
    case string:
        fmt.Println("i is a string")
    default:
        fmt.Println("I don't know what is is")
    }

引用和值接受者

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

type Reader interface{
    Read([]byte)(int,error)
}

type WriterReader interface{
    Writer
    Reader
}

type FileReaderWriter struct{
    filename string
}

func (frw FileReaderWriter) Write(data []byte)(int,error){
    fmt.Println("write...")
    return 0,nil
}

func (frw FileReaderWriter) Read(data []byte)(int,error){
    fmt.Println("Read...")
    return 0, nil
}

func main()  {
    var fileRW WriterReader = FileReaderWriter{
        "angular",
    }
    bytes := []byte("hell world")
    fileRW.Write(bytes)
    fileRW.Read(bytes)
}
func (frw *FileReaderWriter) Write(data []byte)(int,error){
    fmt.Println("write...")
    return 0,nil
}
der in assignment:
        FileReaderWriter does not implement WriterReader (Write method has pointer receiver)
    var fileRW WriterReader = &FileReaderWriter{
        "angular",
    }

最佳实践

  • Use many, small interface
    Single method interfaces are some of the most powerful and flexible
    io.Writer , io.Reader, interface{}

  • Don't export interfaces for types that will be consumed

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

推荐阅读更多精彩内容

  • Class and Interfaces CLASSES and interfaces lie at the he...
    LaMole阅读 827评论 0 1
  • JAVA面试题 1、作用域public,private,protected,以及不写时的区别答:区别如下:作用域 ...
    JA尐白阅读 1,124评论 1 0
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 4,484评论 1 114
  • JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象: 抽象就是忽略一个主题中与当前目标无关的那些方面,以...
    yangkg阅读 638评论 0 1
  • 已到天命之尾的我在慢慢变老,但对于整个人生的见识、见地和经历,会让人渐渐成熟。 把细水长流的幸福感,作为一个男人的...
    沃原阅读 166评论 0 0