Golang的面向对象编程

          Golang的面向对象编程

                               作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

 

 

  Go语言的面向对象之所以与C++,Java以及(较小程度上的)Python这些语言不同,是因为它不支持继承,仅支持聚合(也叫组合)和嵌入。接下来我们一起来学习一下Go语言的面向对象编程吧。

 

一.面向对象编程思想

  面向对象编程刚流行的时候,继承是它首先被吹捧的最大优点之一。但是历经几十载的实践之后,事实证明该特性也有些明显的缺点,特别是当用于维护大系统时。Go语言建议的是面向接口编程。

  常见的编程方式:
    面向过程(面向函数式编程):
      典型代表:
        C语言
      优点:
        流程清晰,代码易读。
      缺点:
        耦合度太高,不利于项目迭代。

    面向对象编程:
      典型代表:
        C++,Java,Python,Golang等。
      优点:
        解耦。
      缺点:
        代码抽象度过高,不易读。

  面向对象三要素:
    封装
      组装:将数据和操作组装到一起。
      隐藏数据:对外只暴露一些接口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解其中的机动原理。
    继承
      多复用,继承来的就不用自己写了。
      多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性。
    多态
      面向对象编程最灵活的地方,动态绑定。
 
  与其它大部分使用聚合和继承的面向对象语言不同的是,Go语言只支持聚合(也叫做组合)和嵌入。

二.结构体的定义及初始化

package main

import (
    "fmt"
)

type Person struct {
    Name   string
    Age    int
    Gender string
}

type Student struct {
    Person //通过匿名组合的方式嵌入了Person的属性。
    Score  float64
}

type Teacher struct {
    Person //通过匿名组合的方式嵌入了Person的属性。
    Course string
}

type Schoolmaster struct {
    Person   //通过匿名组合的方式嵌入了Person的属性。
    CarBrand string
}

func main() {
    /**
    第一种初始化方式:先定义后赋值
    */
    s1 := Student{}
    s1.Name = "Jason Yin"
    fmt.Println(s1)
    fmt.Printf("%+v

", s1) //"+v表示打印结构体的各个字段"

    /**
    第二种初始化方式:直接初始化
    */
    s2 := Teacher{Person{"尹正杰", 18, "boy"}, "Go并发编程"}
    fmt.Println(s2)
    fmt.Printf("%+v

", s2)

    /**
    第三种赋值方式:初始化赋值部分字段
    */
    s3 := Schoolmaster{CarBrand: "丰田", Person: Person{Name: "JasonYin最强王者"}}
    fmt.Println(s3)
    fmt.Printf("%+v
", s3)
}

三.结构体的属性继承及变量赋值

package main

import (
    "fmt"
)

type Animal struct {
    Age int
}

type People struct {
    Animal
    Name   string
    Age    int
    Gender string
}

type IdentityCard struct {
    IdCardNO    int
    Nationality string
    Address     string
    Age         int
}

/*
    此时的Students以及是多重继承
*/
type Students struct {
    IdentityCard
    People //多层继承
    Age    int
    Score  int
}

func main() {
    /**
    如果子类和父类存在同名的属性,那么以就近原则为准
    */
    s1 := Students{
        Score: 150,
        IdentityCard: IdentityCard{
            IdCardNO:    110105199003072872,
            Nationality: "中华人民共和国",
            Address:     "北京市朝阳区望京SOHO",
            Age:         8,
        },
        People: People{Name: "Jason Yin", Age: 18, Animal: Animal{Age: 20}},
        Age:    27,
    }

    /**
    如果子类和父类存在同名的属性(如果父类还继承了其它类型,我们称之为多层继承),那么就以就近原则为准;
    但是如果一个子类如果继承自多个父类(我们称之为多重继承),且每个字段中都有相同的字段,此时我们无法直接在子类调用该属性;
    */
    fmt.Printf("学生的年龄是:[%d]
", s1.Age)
    s1.Age = 21
    fmt.Printf("学生的年龄是:[%d]

", s1.Age)

    //给People类的Age赋值
    fmt.Printf("People的年龄是:[%d]
", s1.People.Age)
    s1.People.Age = 5000
    fmt.Printf("People的年龄是:[%d]

", s1.People.Age)

    //给IdentityCard类的Age赋值
    fmt.Printf("IdentityCard的年龄是:[%d]
", s1.IdentityCard.Age)
    s1.IdentityCard.Age = 80
    fmt.Printf("IdentityCard的年龄是:[%d]
", s1.IdentityCard.Age)
}

四.匿名组合对象指针使用案例

package main

import (
    "fmt"
    "time"
)

type Vehicle struct {
    Brand string
    Wheel byte
}

type Car struct {
    Vehicle
    Colour string
}

type Driver struct {
    *Car
    DrivingTime time.Time
}

func main() {
    /**
    对象指针匿名组合的第一种初始化方式:
        定义时直接初始化赋值。
    */
    d1 := Driver{&Car{
        Vehicle: Vehicle{
            Brand: "丰田",
            Wheel: 4,
        },
        Colour: "红色",
    }, time.Now(),
    }
    //打印结构体的详细信息,注意观察指针对象
    fmt.Printf("%+v
", d1)
    //我们可以直接调用对象的属性
    fmt.Printf("品牌:%s,颜色:%s
", d1.Brand, d1.Colour)
    fmt.Printf("驾驶时间:%+v

", d1.DrivingTime)
    time.Sleep(1000000000 * 3)

    /**
    对象指针匿名组合的第二种初始化方式:
        先声明,再赋值
    温馨提示:
        遇到指针的情况一定要避免空(nil)指针,未初始化的指针默认值是nil,可以考虑使用new函数解决。
    */
    var d2 Driver
    /**
    由于Driver结构体中有一个对象指针匿名组合Car,因此我们需要使用new函数申请空间。
    */
    d2.Car = new(Car)
    d2.Brand = "奔驰"
    d2.Colour = "黄色"
    d2.DrivingTime = time.Now()
    fmt.Printf("%+v
", d2)
    fmt.Printf("品牌:%s,颜色:%s
", d2.Brand, d2.Colour)
    fmt.Printf("驾驶时间:%+v
", d1.DrivingTime)
}

五.结构体成员方法案例

package main

import (
    "fmt"
)

//定义一个结构体
type Lecturer struct {
    Name string
    Age  uint8
}

//我们为Lecturer结构体封装Init成员方法
func (l *Lecturer) Init() {
    l.Name = "Jason Yin"
    l.Age = 20
}

/**
我为Lecturer结构体起一个别名
我们可以为Instructor类型添加成员方法,
通过别名和成员方法为原有类型赋值新的操作
*/
type Instructor Lecturer

/**
温馨提示:
    (1)我们为一个结构体创建成员方法时,如果成员方法有接收者,需要考虑以下两种情况:
        1>.如果这个接收者是对象的时候,是值传递;
        2>.如果这个接收者是对象指针,是引用传递;
    (2)只要函数接收者不同,哪怕函数名称相同,也不算同一个函数哟;
    (3)不管接收者变量名称是否相同,只要类型一致(包括对象和对象指针),那么我们就认为接收者是相同的,这时候不允许出现相同名称函数;
    (4)给指针添加方法的时候,不允许给指针类型添加操作(因为Go语言中指针类型是只读的);
*/
func (i *Instructor) Init() {
    i.Name = "尹正杰"
    i.Age = 18
}

func main() {

    var (
        l Lecturer
        i Instructor
    )

    //可以使用对象调用成员方法
    i.Init()
    fmt.Printf("%+v

", i)

    //可以用对象指针调用成员方法
    (&l).Init()
    fmt.Printf("%+v
", l)
}

六.结构体的方法继承和重写

package main

import (
    "fmt"
)

type Father struct {
    Name string
    Age  int
}

func (f *Father) Init() {
    f.Name = "成龙"
    f.Age = 66
}

//定义父类的Eat成员方法
func (f *Father) Eat() {
    fmt.Println("Jackie Chan is eating...")
}

//重写父类的Eat成员方法
func (s *Son) Eat() {
    fmt.Println("FangZuming is eating...")
}

//我们让Son类继承Father父类
type Son struct {
    Father //匿名组合能够继承父类的属性和方法
    Score  int
}

func main() {
    var s Son
    s.Init()
    fmt.Printf("%+v
", s)
    s.Eat()
    s.Name = "房祖名"
    s.Age = 38
    s.Score = 100
    fmt.Printf("%+v
", s)
}

七.方法值和方法表达式

package main

import (
    "fmt"
)

/**
定义函数,函数的返回值是函数类型
*/
func CallBack(a int) func(b int) int {
    return func(c int) int {
        fmt.Println("调用了CallBack这个回调函数...")
        return a + c
    }
}

type BigData struct {
    Name string
}

func (this *BigData) Init() {
    this.Name = "Hadoop"
}

func (this *BigData) PrinfInfo() {
    fmt.Printf("%v是大数据生态圈的基石。
", this.Name)
}

func (this BigData) SetInfoValue() {
    fmt.Printf("SetInfoValue : %p,%v
", &this, this)
}

func (this *BigData) SetInfoPointer() {
    fmt.Printf("SetInfoPointer : %p,%v
", this, this)
}

func main() {

    /**
      调用回调函数的返回值为函数类型
    */
    result := CallBack(10)
    fmt.Printf("result的类型是:[%T],result的值是:[%v]
", result, result)
    res1 := result(20) //我们对返回的函数再次进行调用
    fmt.Printf("res1的类型是:[%T],res1的值是:[%d]

", res1, res1)

    var hadoop BigData
    hadoop.Init()            //调用hadoop的初始化方法
    info := hadoop.PrinfInfo //我们可以声明一个函数变量info,我们称之为方法表达式

    /**
      我们对info函数变量进行调用,这样可以起到隐藏调用者hadoop对象的效果哟~(类似于回调函数的调用效果)
      方法值可以隐藏调用者,我们称为隐式调用。
    */
    info()

    /**
      方法表达式可以显示调用调用,必须传递方法调用者对象,在实际开发中很少使用这种方式,我们了解即可。
    */

    elk := BigData{"Elastic Stack"}
    fmt.Printf("main:%p,%v

", &elk, elk)
    s1 := (*BigData).SetInfoPointer
    s1(&elk) //显示把接收者传递过去
    s2 := (BigData).SetInfoValue
    s2(elk) //显示把接收者传递过去
}

八.面向接口编程(多态案例)

  博主推荐阅读:
    https://www.cnblogs.com/yinzhengjie2020/p/12542435.html

 

原文地址:https://www.cnblogs.com/yinzhengjie2020/p/12536401.html