Go interface & struct 接口与结构体

interface & struct 接口与结构体

GO 语言的基础特性 interface 可以理解为一种类型的规范或者约定。它跟java,C# 不太一样,不需要显示说明实现了某个接口,它没有继承或子类或 implements 关键字,只是通过约定的形式,隐式的实现 interface 中的方法即可。

go 语言中的 interface:

  • interface 是方法声明的集合
  • 任何类型的对象实现了在interface 接口中声明的全部方法,则表明该类型实现了该接口。
  • interface 可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。
  • interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface
  • interface 定义的方法不能重载,如 eat() eat(s string) 不能同时存在

以继承为特点的 OOP 只是编程世界的一种抽象方式,在 Golang 的世界里没有继承,只有组合和接口,并且是松散的接口结构,不强制声明实现接口。

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

翻译过来就是:如果某个东西长得像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那它就可以被看成是一只鸭子。

结合到 GO 语言,也就是说那些实现了鸭子某个 interface 全部方法的 struct 对象就是鸭子。

单一继承关系解决了 is-a 也就是定义问题,因此可以把子类当做父类来对待。但对于父类不同但又具有某些共同行为的数据,单一继承就不能解决了,C++ 采取了多继承这种复杂的方式。

GO 采取更贴近现实世界的网状结构,不同于继承,GO 语言的接口是松散的结构,它不和定义绑定。从这一点上来说,Duck Type 相比传统的 extends 是更加松耦合的方式,可以同时从多个维度对数据进行抽象,找出它们的共同点,使用同一套逻辑来处理。

如果想在一个包中访问另一个包中结构体的字段,则必须是大写字母开头的变量,即可导出的变量。

在定义结构体字段时,除字段名称和数据类型外,还可以使用反引号为结构体字段声明元信息,这种元信息称为Tag,用于编译阶段关联到字段当中:

type Member struct {
    Id     int    `json:"id,-"`
    Name   string `json:"name"`
    Email  string `json:"email"`
    Gender int    `json:"gender,"`
    Age    int    `json:"age"`
}

以上使用 encoding/json 包编码或解码结构体时使用的Tag信息,在 Member 成员到 Json 键值 做对应。Tag由反引号括起来的一系列用空格分隔的 key:"value" 键值对组成,如:

Id int `json:"id" gorm:"AUTO_INCREMENT"`

接口使用例子,定义MyString类型与string一样,再实现 VolwelsFInder 接口的方法,使用时只需要实例化对象并赋予接口即可以访问接口规范的方法,rune 是基本数据类型 Unicode 字符:

package main

import (  
    "fmt"
)

// 定义interface 
type VowelsFinder interface {  
    FindVowels() []rune
}

// 别名扩展方式
type MyString string

// 实现接口
func (ms MyString) FindVowels() []rune {  
    var vowels []rune
    for _, rune := range ms {
        if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
            vowels = append(vowels, rune)
        }
    }
    return vowels
}

func findType(i interface{}) {
    switch v := i.(type) {
        case VowelsFinder:
            fmt.Println("VowelsFinder type.", v);
        default:
            fmt.Printf("unknown type\n")
    }
}

func main() {
    name := MyString("Sam Anderson") // 类型转换
    findType(name)  // VowelsFinder type.
    fmt.Printf("Vowels are %c", name.FindVowels())
    
    var v VowelsFinder               // 定义一个接口类型的变量
    v = name 
    fmt.Printf("Vowels are %c", v.FindVowels())
}

所有类型都实现了空接口 Empty Interface 表示为 interface{},空接口也是基础类型,它的作用相当于 Java 中的 Object。可以对任何接口类型进行类型断言 Type Assertions,类型断言检查正确就可以得到期待的类型。类型断言检查是对接口类型进行的动态类型检查,语法格式 x.(T),x 是一个接口,T 就是断言目标类型,通过断言检查就可以从操作数中得到具体的类型数据。

如下面尝试将变量类型 s 转换成 string,先将 s 转换成空接口interface{}(v),再进行 .(string) 断言:

val, ok := interface{}(v).(string)

GO 语言的结构体 struct 是组合非继承,不像其它 OOP 语言那样通过继承机制实现类结构的扩展。

下面例程中,Being 是最基础的结构体,Human 组合了 Being,而 Student 又组合了 Human。 但是通过 Human.Eat() 方法调用 Drink() 只能是 Human.Drink()。如果 Student 也实现了 Eat 方法并调用 Dranking(),则会调用自己的 Student.Dranking()。

对 s 分别进行了 Human、Student 类型断言,输出结果可以看到,s 断言 Human 是不成功的,因为它是 Student 类型,两个结构体是完全不同的类型。断言 IHuman 接口也是成功的,因为 Student 结构实现了 IHuman 接口的所以方法。也就是说看起来叫起来都像鸭子的东西,那就可以认为它是鸭子

package main
 
import "fmt"
 
func main(){
    var h Human
 
    s := Student{Grade: 1, Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
    fmt.Println("student:", s)
    fmt.Println("student ", s.Name, " is alive:", s.IsLive )
 
    // h = s // cannot use s (type Student) as type Human in assignment
    fmt.Println(h)
 
    // Heal(s) // cannot use s (type Student) as type Being in argument to Heal
    Heal(s.Human.Being) // true
 
    s.Drink() // student drinking...
    s.Eat() // human eating... & human drinking...

    human, b := interface{}(s).(Human) // false s is Student but Human
    fmt.Println(human, b)
     
    student, b := interface{}(s).(Student)
    fmt.Println(student, b)

    ihuman, b := interface{}(s).(IHuman)
    fmt.Println(ihuman, b)

}
 
type Being struct {
    IsLive bool
}
 
type Human struct {
    Being
    Name string
    Age int
}

func (h Human) Eat(){
    fmt.Println("human eating...")
    h.Drink()
}
 
func (h Human) Drink(){
    fmt.Println("human drinking...")
}
 
type Student struct {
    Human
    Grade int
}

func (s Student) Drink(){
    fmt.Println("student drinking...")
}

func Heal(b Being){
    fmt.Println(b.IsLive)
}

type IHuman interface {
    Eat()
    Drink()
}

关于结构体的扩展两种方式,嵌入结构体方式与别名扩展方式的总结参考后续的 [JSON 解析与扩展已有类型] 小节。

package main

import (
    "fmt"
    "reflect"
)

type IBase interface {
    Show()
}

type Base struct {
    ID   int    `yaml:"ID" json:"id"`
    Name string `yaml:"NAME" json:"name"`
}

func (this Base) Show() {
    fmt.Printf("#%d - %s\n", this.ID, this.Name)
}

type User struct {
    Base `yaml:"BasePart" json:"basepart"`
}

type Manager struct {
    Base
}

func echoType(i interface{}) {
    switch v := i.(type) {
    case IBase:
        fmt.Println("echoType: IBase.", v)
    }

    switch v := i.(type) {
    case Base:
        fmt.Println("echoType: Base.", v)
    case User:
        fmt.Println("echoType: User.", v)
    case Manager:
        fmt.Println("echoType: Manager.", v)
    default:
        fmt.Printf("echoType: Unknown.\n")
    }
}

func main() {
    var user = User{Base: Base{1, "ABC"}}
    var inte = interface{}(user)
    // panic: interface conversion: interface {} is main.User, not main.Manager
    // var mana = inte.(Manager)
    fmt.Printf("User %#v\n", user)
    fmt.Printf("Inte %#v\n", inte)

    echoType(user)

    // TypeOf return reflect.Type interface
    tt := reflect.TypeOf(&user)
    rt := tt.Elem()
    for i := 0; i < rt.NumField(); i++ {
        field := rt.Field(i)
        at := field.Tag
        yt := field.Tag.Get("yaml")
        jt, _ := field.Tag.Lookup("json")
        fmt.Printf("FIELD: %s\n\tJASON TAG: %s\n\tYAML TAG: %s\n\tALL TAGS: %s\n", field.Name, jt, yt, at)
    }
}

一个对象既至少由两种类型信息,如上面代码中的 user,既是一个 struct 也是一个 interface 类型,以上示例输出:

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

推荐阅读更多精彩内容