go 语言仅支持封装,不支持继承和多态
go 语言没有class 只有struct

结构体

声明和创建

type treeNode struct {

    value int
    left, right *treeNode
}

func main() {

    var root treeNode
    root = treeNode{value: 3}
    root.left = &treeNode{}
    root.right = &treeNode{5, nil, nil}
    root.right.left = new(treeNode)

    nodes := []treeNode {
        {value: 3},
        {},
        {6, nil,&root   },
    }
    // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc4200a2020}]
    fmt.Println(nodes)
}

go 本身不提供构造函数,虽然提供了那么多构造的方式,但是还是会有使用到构造函数的需求,这时我们可以自己写一个工厂函数

需要注意的是,这里返回了局部变量的地址,但是我们不需要关心这个变量是被分配到堆还是栈上,go内部的垃圾回收机制会帮我们解决这个问题

func createNode(value int) *treeNode {
    return &treeNode{value: value}
}

如果需要对结构体增加扩展方法,我们可以采用以下的方式

func (node treeNode) print() {
    fmt.Println(node.value)
}

func main() {
    var root treeNode
    root = treeNode{value: 3}
    // 3
    root.print()
}

封装

go 语言中的封装使用名字来区分,作用域是包

  • 首字母大写代表 public
  • 首字母小写代表 private

一个文件的目录下只能有一个包

duck typing

  • 走路像鸭子、叫起来像鸭子、长的像鸭子,那么就是鸭子
  • 描述食物的外部行为而非内部结构
  • 严格说go属于结构化类型系统,类似duck typing

python中的 duck typing

运行时才知道传入的retriever有没有get方法,且需要注释来说明接口

def download(retrivever):
    return retriever.get("www.baidu.com")

c++中的 duck typing

编译时才知道传入的retriever有没有get方法,且需要注释来说明接口

template <class R>
string download(const R& retriever) {
    return retriever.get("www.baidu.com")
}

java中的类似代码

传入的参数必须实现IRetriever接口,但不是duck typing

<R extends IRetriever>
String download(R r) {
    return r.get("www.baidu.com")
}

C#中类似代码

传入的参数必须实现IRetriever接口,但也不是duck typing

string download(R r) where r : IRetriever {
    return r.get("www.baidu.com")
}

go语言的 duck typing

具体实现见接口

  • 同时可以满足Readable、Appendable
  • 同时具有python、c++的duck typing的灵活性
  • 又具有java、c#的类型检查

接口

在下面的例子中,我们可以看出go 的interface 的实现与其他语言的区别,并遵守了duck typing

package test

type Retriever struct {
    Contents string
}

func (r Retriever) Get(url string) string {
    return r.Contents
}
package main

type Retriever interface {
    Get(url string) string
}

func download(r Retriever) string {
    return r.Get("http://www.baidu.com")
}

func main() {

    var r Retriever
    r = test.Retriever{"test"}
    // test
    fmt.Println(download(r))
}

接口的类型

试着输出这个接口的类型,看看接口到底是什么

func main() {

    var r Retriever
    r = test.Retriever{"test"}
    // test.Retriever {test}
    fmt.Printf("%T %v\n", r, r)

    r = &reall.Retriever{"Mozilla/5.0", time.Minute}
    // *reall.Retriever {Mozilla/5.0 1m0s}
    fmt.Printf("%T %v\n", r, r)
}

-w500

  • 接口变量自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针
  • 指针接受者实现只能以指针方式使用,值接受者都可以

查看接口变量

在查看接口变量时,我们可以使用Type Assertion、Type Switch

PS:在go语言中,interface{} 表示任何类型

Type Switch

func inspect(r Retriever) {

    switch v := r.(type) {

    case test.Retriever:
        fmt.Println(v.Contents)
    case *reall.Retriever:
        fmt.Println(v.UserAgent)
    }
}

Type Assertion

if testRetriever, ok := r.(test.Retriever); ok {
    fmt.Println(testRetriever.Contents)
} else {
    fmt.Println("this is not test retriever")
}

组合接口

我们可以通过以下方式对接口进行组合

type Retriever interface {
    Get(url string) string
}

type Poster interface {
    Post(url string, form map[string]string) string
}

type RetrieverPoster interface {
    Retriever
    Poster
}

func session(s RetrieverPoster) string {

    s.Post("http://www.baidu.com", map[string]string {"name":"rp"})
    return s.Get("http://www.baidu.com")
}

What doesn’t kill you makes you stronger.