【go语言学习】面向对象oop

go并不是一个纯面向对象的编程语言。在go中的面向对象,结构体替换了类。
go并没有提供类class,但是它提供了结构体struct,方法method可以在结构体上添加。提供了捆绑数据和方法的行为,这些数据和方法与类类似。

面向对象的基本思想主要体现在封装,继承以及多态等的设计与运用上。下面来看看封装、继承与多态在golang中是如何实现的。

一、封装

  • 封装主要是通过访问权限控制实现的。
  • 在Java中,共有public 、protected、default、private这四种权限控制。
  • 而相应的在golang中,是通过约定来实现权限控制的。变量名首字母大写,相当于java中的public,首字母小写,相当于private。同一个包中访问,相当于default。由于go没有继承,也就没有protected。

在go_project目录下创建model文件,在model文件夹下创建student.go

package model
// student 学生结构体,首字母小写,包外不可见
type student struct {
    Name string //  首字母大写,包外可见
    age  int  // 首字母小写,包外不可见
}

// New student的构造函数
func New(name string, age int) *student {
    return &student{
        Name: name,
        age:  age,
    }
}

// GetAge student的方法,用于外部包访问age字段
func (s *student) GetAge() int {
    return s.age
}

// SetAge student的方法,用于外部包修改age字段
func (s *student) SetAge(age int) {
    s.age = age
}

在go_project目录下创建main文件夹,在main文件夹下创建main.go

package main

import (
    "fmt"
    "go_project/model"
)

func main() {
    s := model.New("tom", 20)
    fmt.Println(s)
    // 可以直接访问Name字段
    fmt.Println(s.Name)
    // 访问不到age字段
    // fmt.Println(s.age)
    fmt.Println(s.GetAge())
    s.Name = "jack"
    // 无法直接修改age字段
    // s.age = 23
    s.SetAge(23)
    fmt.Println(s)
}

运行结果

&{tom 20}
tom
20
&{jack 23}
  • 在model包里面定义了一个student的结构体;由于首字母小写,所以不可能从其他包中创建student结构体的实例。也就是说没有其他的包能够创建一个零值的student实例。
  • 现在导出student实例只能通过构造函数NewT(parameters)。如果包只定义了一个类型,那么约定将这个函数命名为New(parameters)而不是NewT(parameters)。
  • 由于没有将student的所有字段导出,避免了对student字段的随意访问和修改。只能通过导出的student的GetSet方法操作。

二、继承

go不支持继承,可以通过将一个struct类型嵌入到另一个结构中实现类似继承效果。

package main

import "fmt"

// Person 结构体 父类
type Person struct {
    name string
    age  int
}

// Say Person的方法,父类方法
func (p Person) Say() {
    fmt.Println(p.name, "say hello")
}

// Student 结构体 子类
type Student struct {
    Person
    school string
}

// Study Student的方法, 子类自己的方法
func (s Student) Study() {
    fmt.Println(s.name, "goood good study, day day up")
}

// Say Student的方法,重写父类的方法
func (s Student) Say() {
    fmt.Println(s.name, "说:你好!")
}

func main() {
    // 创建父类对象
    p := Person{
        name: "tom",
        age:  19,
    }
    p.Say()
    // 创建子类对象
    s := Student{
        Person: Person{
            name: "jack",
            age:  18,
        },
        school: "清华大学",
    }
    // 访问父类的方法
    s.Say()
    // 访问子类自己的方法
    s.Study()
}

运行结果

tom say hello
jack 说:你好!
jack goood good study, day day up

三、多态

Java 中的多态是通过 extends class 或者 implements interface 实现的,在 golang 中既没有 extends,也没有 implements ,那么 go 中多态是如何实现的呢 ?

答案:在golang中,只要某个struct实现了某个interface中的所有方法,那么我们就认为,这个struct实现了这个接口。

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

一个接口的实现:

  • 看成实现本身的类型,可以访问实现类中的属性和方法
  • 看成对应的接口类型,只能访问接口中的方法
package main

import "fmt"

// AnimalIF 接口
type AnimalIF interface {
    Eat()
    Sleep()
}

// Animal 结构体 父类
type Animal struct {
    name string
    age  int
}

// Eat Animal的方法
func (a Animal) Eat() {
    fmt.Println(a.name, "eat")
}

// Sleep Animal的方法
func (a Animal) Sleep() {
    fmt.Println(a.name, "sleep")
}

// Cat 结构体 子类
type Cat struct {
    Animal
    color string
}

// Eat Cat的方法,重写父类的方法
func (c Cat) Eat() {
    fmt.Println("cat eat fish")
}

// Dog 结构体 子类
type Dog struct {
    Animal
}

// LookDoor Dog的方法, 子类自己的方法
func (d Dog) LookDoor() {
    fmt.Println("dog lookdoor")
}

func main() {
    // 创建父类对象
    a := Animal{
        name: "狮子",
        age:  5,
    }
    a.Eat()
    // 创建子类对象
    c := Cat{
        Animal: Animal{
            name: "小花",
            age:  3,
        },
        color: "白色",
    }
    // 创建子类对象
    d := Dog{
        Animal: Animal{
            name: "旺财",
            age:  2,
        },
    }
    // 访问子类拥有的父类的字段
    fmt.Println(c.name, c.age)
    // 访问子类自己的字段
    fmt.Println(c.color)
    // 访问父类的方法
    d.Eat()
    // 访问子类自己的方法
    d.LookDoor()
    // 访问子类重写的父类的方法
    c.Eat()
    fmt.Println("--------------------")

    // 创建接口类型
    var ai AnimalIF
    ai = a
    ai.Eat()
    ai.Sleep()
    ai = c
    ai.Eat()
    ai.Sleep()
    ai = d
    ai.Eat()
    ai.Sleep()
}

运行结果

狮子 eat
小花 3
白色
旺财 eat
dog lookdoor
cat eat fish
--------------------
狮子 eat
狮子 sleep
cat eat fish
小花 sleep
旺财 eat
旺财 sleep

应用举例
计算一些图形的面积,目前有正方形和圆形。

package main

import (
    "fmt"
    "math"
)

// Area 面积接口,包含两个方法source和calculate
type Area interface {
    source() string
    calculate() float64
}

// Square 结构体,正方形
type Square struct {
    graphName  string
    sideLength float64
}

// source Square的方法
func (s Square) source() string {
    return s.graphName
}

// calculate Square的方法
func (s Square) calculate() float64 {
    return math.Pow(s.sideLength, 2)
}

// Round 结构体,圆形
type Round struct {
    graphName string
    radius    float64
}

// source Round的方法
func (r Round) source() string {
    return r.graphName
}

// calculate Round的方法
func (r Round) calculate() float64 {
    return math.Pi * math.Pow(r.radius, 2)
}

// totalArea 获取总面积的函数
func totalArea(a []Area) {
    var totalArea = 0.0
    for _, v := range a {
        fmt.Printf("面积来自%v, 它的面积是%v\n", v.source(), v.calculate())
        totalArea += v.calculate()
    }
    fmt.Printf("所有图形的总面积是:%v\n", totalArea)
}

func main() {
    graph1 := Square{
        graphName:  "graph1",
        sideLength: 6.3,
    }
    graph2 := Round{
        graphName: "graph2",
        radius:    3.4,
    }
    a := []Area{graph1, graph2}
    totalArea(a)
}

运行结果

面积来自graph1, 它的面积是39.69
面积来自graph2, 它的面积是36.316811075498
所有图形的总面积是:76.006811075498

如果再增加长方形的面积,不需要对totalArea()做任何更改。

首先定义Rectangle结构体和source()calculate()方法

// Rectangle 结构体, 长方形
type Rectangle struct {
    graphName string
    length    float64
    width     float64
}

// source Rectangle的方法
func (r Rectangle) source() string {
    return r.graphName
}

// calculate Rectangle的方法
func (r Rectangle) calculate() float64 {
    return r.length * r.width
}

修改主函数

func main() {
    graph1 := Square{
        graphName:  "graph1",
        sideLength: 6.3,
    }
    graph2 := Round{
        graphName: "graph2",
        radius:    3.4,
    }
    graph3 := Rectangle{
        graphName: "graph3",
        length:    12.3,
        width:     6.5,
    }
    a := []Area{graph1, graph2, graph3}
    totalArea(a)
}

运行结果

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