Golang通脉之面向对象

面向对象的三大特征:

  1. 封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式
  2. 继承:使得子类具有父类的属性和方法或者重新定义、追加属性和方法等
  3. 多态:不同对象中同种行为的不同实现方式

Go并不是一个纯面向对象的编程语言。在 Go 语言中可以使用结构体struct对属性进行封装,结构体就像是类的一种简化形式。可以在结构体上添加捆绑数据和方法的行为,这些数据和方法与类类似

Go语言没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。

结构体和方法

type Employee struct {  
    FirstName   string
    LastName    string
    TotalLeaves int
    LeavesTaken int
}

func (e Employee) LeavesRemaining() {  
    fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

func main() {  
    e := employee.Employee {
        FirstName: "Sam",
        LastName: "Adolf",
        TotalLeaves: 30,
        LeavesTaken: 20,
    }
    e.LeavesRemaining()
}

New()函数

Java语言中提供了构造方法创建并初始化对象,在Go语言中一般需要自己实现一个对外可见的New函数

func main() {  
    var e Employee
    e.LeavesRemaining()
}

运行结果:

has 0 leaves remaining

通过运行结果可以知道,使用Employee的零值创建的变量是不可用的。它没有有效的名、姓,也没有有效的保留细节。在其他的OOP语言中,比如java,这个问题可以通过使用构造函数来解决。使用参数化构造函数可以创建一个有效的对象。

go不支持构造函数。如果某个类型的零值不可用,则程序员的任务是不导出该类型以防止其他包的访问,并提供一个名为NewT(parameters)的函数,该函数初始化类型T和所需的值。在go中,它是一个命名一个函数的约定,它创建了一个T类型的值给NewT(parameters)。这就像一个构造函数。如果包只定义了一个类型,那么它的一个约定就是将这个函数命名为New(parameters)而不是NewT(parameters)

首先修改employee结构体为非导出,并创建一个函数New(),它将创建一个新Employee:

type employee struct {  
    firstName   string
    lastName    string
    totalLeaves int
    leavesTaken int
}

func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {  
    e := employee {firstName, lastName, totalLeave, leavesTaken}
    return e
}

func (e employee) LeavesRemaining() {  
    fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

在这里做了一些重要的改变。已经将Employee struct的起始字母e设置为小写。

由于employee是未导出的,所以不可能从其他包中创建类型employee的值。因此,提供了一个输出的新函数。将所需的参数作为输入并返回新创建的employee

func main() {  
    e := employee.New("Sam", "Adolf", 30, 20)
    e.LeavesRemaining()
}

运行结果:

Sam Adolf has 10 leaves remaining 

因此,虽然Go不支持类,但是结构体可以有效地使用,在使用构造函数的位置,使用New(parameters)的方法即可。


组合与继承

在 Go 语言中没有 extends 关键字,它使用在结构体中内嵌匿名类型的方法来实现继承。在Go语言中称之为组合(Composition)。组合的一般定义是“放在一起”。

举一个博客文章例子:每个博客都有标题、内容和作者信息。这可以用组合完美地表示出来。

嵌入结构体实现组合

可以通过将一个struct类型嵌入到另一个结构中实现:

/*
创建了一个author struct,它包含字段名、lastName和bio。添加了一个方法fullName(),将作者作为接收者类型,这将返回作者的全名。
*/
type author struct {  
    firstName string
    lastName  string
    bio       string
}

func (a author) fullName() string {  
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}
/*
post struct有字段标题、内容。它还有一个嵌入式匿名字段作者。这个字段表示post struct是由author组成的。现在post struct可以访问作者结构的所有字段和方法。还在post struct中添加了details()方法
*/
type post struct {  
    title     string
    content   string
    author
}

func (p post) details() {  
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.author.fullName())
    fmt.Println("Bio: ", p.author.bio)
}

func main() {  
    author1 := author{
        "Naveen",
        "Ramanathan",
        "Golang Enthusiast",
    }
    post1 := post{
        "Inheritance in Go",
        "Go supports composition instead of inheritance",
        author1,
    }
    post1.details()
}

运行结果:

Title:  Inheritance in Go  
Content:  Go supports composition instead of inheritance  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast  

嵌入结构体的切片

在以上程序的main函数下增加以下代码,并运行

type website struct {  
        []post
}
func (w website) contents() {  
    fmt.Println("Contents of Website\n")
    for _, v := range w.posts {
        v.details()
        fmt.Println()
    }
}

运行报错:

syntax error: unexpected [, expecting field name or embedded type 

这个错误指向structs []post的嵌入部分。原因是切片不能当做匿名字段使用。需要一个字段名

type website struct {  
        posts []post
}

修改完完整代码如下:

type author struct {  
    firstName string
    lastName  string
    bio       string
}

func (a author) fullName() string {  
    return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}

type post struct {  
    title   string
    content string
    author
}
func (p post) details() {  
    fmt.Println("Title: ", p.title)
    fmt.Println("Content: ", p.content)
    fmt.Println("Author: ", p.fullName())
    fmt.Println("Bio: ", p.bio)
}

type website struct {  
    posts []post
}
func (w website) contents() {  
    fmt.Println("Contents of Website\n")
    for _, v := range w.posts {
        v.details()
        fmt.Println()
    }
}
func main() {  
    author1 := author{
        "Naveen",
        "Ramanathan",
        "Golang Enthusiast",
    }
    post1 := post{
        "Inheritance in Go",
        "Go supports composition instead of inheritance",
        author1,
    }
    post2 := post{
        "Struct instead of Classes in Go",
        "Go does not support classes but methods can be added to structs",
        author1,
    }
    post3 := post{
        "Concurrency",
        "Go is a concurrent language and not a parallel one",
        author1,
    }
    w := website{
        posts: []post{post1, post2, post3},
    }
    w.contents()
}   

运行结果:

Contents of Website

Title:  Inheritance in Go  
Content:  Go supports composition instead of inheritance  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast

Title:  Struct instead of Classes in Go  
Content:  Go does not support classes but methods can be added to structs  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast

Title:  Concurrency  
Content:  Go is a concurrent language and not a parallel one  
Author:  Naveen Ramanathan  
Bio:  Golang Enthusiast  

接口与多态

Go中的多态性(Polymorphism)是在接口的帮助下实现的。接口可以在Go中隐式地实现。如果类型为接口中声明的所有方法提供了定义,则该类型实现了这个接口。

任何定义接口所有方法的类型都被称为隐式地实现该接口。

类型接口的变量可以保存实现接口的任何值。接口的这个属性用于实现Go中的多态性。

定义一个正方形 Square 和一个长方形 Rectangle

// 正方形
type Square struct {
    side float32
}

// 长方形
type Rectangle struct {
    length, width float32
}

计算这两个几何图形的面积。但由于他们的面积计算方式不同,需要定义两个不同的 Area() 方法。

于是,可以定义一个包含 Area() 方法的接口 Shaper,让 SquareRectangle 都实现这个接口里的 Area()

// 接口 Shaper
type Shaper interface {
    Area() float32
}

// 计算正方形的面积
func (sq *Square) Area() float32 {
    return sq.side * sq.side
}

// 计算长方形的面积
func (r *Rectangle) Area() float32 {
    return r.length * r.width
}

main() 函数中调用 Area()

func main() {
    r := &Rectangle{10, 2}
    q := &Square{10}

    // 创建一个 Shaper 类型的数组
    shapes := []Shaper{r, q}
    // 迭代数组上的每一个元素并调用 Area() 方法
    for n, _ := range shapes {
        fmt.Println("图形数据: ", shapes[n])
        fmt.Println("它的面积是: ", shapes[n].Area())
    }
}

/*Output:
图形数据:  &{10 2}
它的面积是:  20
图形数据:  &{10}
它的面积是:  100
*/

由以上代码输出结果可知:不同对象调用 Area() 方法产生了不同的结果,展现了多态的特征。

总结

  • 面向对象的三大特征是:封装、继承和多态
  • Go 语言使用结构体对属性进行封装,结构体就像是类的一种简化形式
  • 在 Go 语言中,方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量
  • 名称首字母的大小写决定了该变量/常量/类型/接口/结构/函数……能否被外部包导入
  • 无法被导入的字段可以使用 gettersetter 的方式来访问
  • Go 语言使用在结构体中内嵌匿名类型的方法来实现继承
  • 使用接口可以实现多态
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 面向对象(OOP) go并不是一个纯面向对象的编程语言。在go中的面向对象,结构体替换了类。 Go并没有提供类cl...
    yuyangray阅读 2,200评论 1 0
  • 面向对象(OOP) go并不是一个纯面向对象的编程语言。在go中的面向对象,结构体替换了类。 Go并没有提供类cl...
    Venture_Mark阅读 153评论 0 0
  • Day06的课程要点记录详细教程地址:Day6 - 面向对象学习 上节补遗 - Subprocess模块 os.s...
    乘风逐月阅读 302评论 0 0
  • Go 语言的面向对象   Go 语言的面向对象非常简单,仅支持封装,不支持继承和多态。继承和多态是在接口中实现的。...
    hvkcoder阅读 490评论 0 3
  • Golang 面向对象编程 go语言中,虽然没有明确提出面向对象的概念,但是基于已有的语法设计,我们也可以写出面向...
    刘昊2018阅读 10,928评论 1 4