• 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

     

  • 相关阅读:
    html-----018----HTML Web Server/HTML URL 字符编码
    html-----017
    SQL Server 2008 R2评估期已过的解决办法和sqlserver 服务器打不开问题
    Eclipse快捷键大全
    with递归
    PIVOT使用
    SSH框架搭建
    更换开发环境后设置Tomcat和jdk版本
    MyBatis 一对多和多对一关联查询
    MyBatis 使用接口增删改查和两表一对一级联查询
  • 原文地址:https://www.cnblogs.com/yinzhengjie2020/p/12536401.html
Copyright © 2020-2023  润新知