ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Go语言之面向对象编程(三)

2022-01-21 09:35:11  阅读:101  来源: 互联网

标签:func 语言 fmt 接口 面向对象编程 Go main type 结构


Golang也拥有面向对象编程的封装、继承、多态特性。

一、封装

封装就是将抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的方法才能对字段进行操作。那么如何来实现封装呢?

  • 将结构体、首字母小写(这样就是私有变量,只能在本包使用)
  • 在结构体所在的包中提供一个工厂模式的函数,首字母大写,这时一个对外公开的函数

比如,对私有属性赋值,可以提供一个SetAttr属性工厂函数,在内部可以进行业务验证,进行属性赋值;获取属性的值,可以提供一个GetAttr属性工厂函数。

案例:员工包括姓名、年龄、薪资,其中薪资、年龄对外是隐私的,年龄大于等于18岁。

分析:薪资、年龄通过工厂函数进行赋值,并且年龄赋值时进行判断。

  • model/person.go
package model

import "fmt"

type person struct {
    Name   string
    age    int
    salary int
}

// 结构体时小写变量名,提供工厂方法
func CreatePerson(name string) *person {

    return &person{
        Name: name,
    }
}

// 提供年龄赋值工厂方法
func (p *person) SetAge(age int) {
    if age >= 18 {
        p.age = age
    } else {
        fmt.Println("年龄输入不合法")
    }
}

// 提供获取年龄的方法
func (p *person) GetAge() int {
    return p.age
}

// 提供薪资赋值工厂方法
func (p *person) SetSalary(salary int) {
    p.salary = salary
}

// 提供获取薪资的方法
func (p *person) GetSalary() int {
    return p.salary
}
  • main/main.go
package main

import (
    "fmt"
    "go_tutorial/day12/example/02/model"
)

func main() {

    // 创建一个person的结构体变量
    p := model.CreatePerson("ariry")
    p.SetAge(30)
    p.SetSalary(5000)
    fmt.Println("姓名:", p.Name, "年龄:", p.GetAge(), "薪资:", p.GetSalary())

}

可以看到person结构体、字段age、salary都是私有的,通过提供工厂方法进行创建、赋值、获取值操作。

二、继承

(一)基础

1、快速上手

   继承主要解决的就时代码复用问题,不至于出现过多的代码冗余问题。当多个结构体存在相同的属性、方法时,可以从这些结构体中抽象出一个拥有共同属性、方法的结构体。其余结构体可以通过匿名结构体的方式来继承这个结构体即可。

案例:假如学生分为中学生、大学生,这些学生都有姓名、年龄、考试成绩,平时需要进行各种考试。不过大学生平日里空闲时间比较多,所以去图书馆看书。所以这里面可以抽象出:

  • 属性有姓名、年龄、成绩
  • 方法有考试
  • 另外大学生额外的方法有看书

package main

import "fmt"

type Student struct {
    Name  string
    Age   int
    Score float64
}

func (student *Student) Examing() {
    fmt.Print("姓名:", student.Name, "年龄:", student.Age, "考试成绩:", student.Score)
}

type MiddleStudent struct {
    Student // 学生匿名结构体
}

type UgStudent struct {
    Student // 学生匿名结构体
}

// 大学生特有读书方法
func (ugs *UgStudent) ReadBook() {
    fmt.Print(ugs.Name, "正在读书...")
}

func main() {
    // MiddleStudent继承Student,所以可以通过mt.Student.Name来进行属性的调用,
    // 同时也可以mt.Name,如果在本结构体中没找到该属性,会去匿名结构体中查找
    mt := MiddleStudent{}
    mt.Student.Name = "中学生" //mt.Name = "中学生"
    mt.Student.Age = 15     //mt.Age = 15
    mt.Student.Score = 85.5 //mt.Score = 85.5
    mt.Examing()

    // 调用大学生特有的方法
    ugs := UgStudent{}
    ugs.Name = "大学生"
    ugs.Age = 25
    ugs.Score = 95.5
    ugs.ReadBook()

}

 2、深入理解

  • 结构体可以使用匿名结构体中的所有字段和方法,包括首字母小写的结构体、属性、方法等私有变量
  • 匿名结构体字段访问可简化(mt.Student.Name可简化为mt.Name)
  • 当结构体于匿名结构体有相同字段和方法时,采用就近原则,如若希望访问匿名结构体字段需要指明匿名结构体
  • 如若结构体有两个或者多个匿名结构体时,并且匿名结构体都拥有相同字段和方法,但是结构体本身无同名字段和方法时,需指明匿名结构体,否则编译报错
package main

import "fmt"

type animal struct {
    name  string
    hobby string
}

func (a *animal) showHobby() {
    fmt.Println("hobby:", a.hobby)
}

type cat struct {
    animal
    name string
}

func (c *cat) showHobby() {
    fmt.Println("hobby:", c.hobby)
}

type tigger struct {
    animal
    cat
}

func main() {
    /*
        1、结构体可以使用匿名结构体中的所有字段和方法,包括首字母小写的结构体、属性、方法等私有变量
        2、匿名结构体字段访问可简化
        3、当结构体于匿名结构体有相同字段和方法时,采用就近原则,如若希望访问匿名结构体字段需要指明匿名结构体
    */
    // 声明一个cat结构体变量
    var c cat
    c.name = "猫"      // 就近原则,使用的name时cat结构体中的name,而非animal中的name
    c.hobby = "吃鱼..." // 匿名结构体可简写,查找顺序:本结构体-->各个匿名结构体-->如若没有报错
    c.showHobby()

    /*
        4、如若结构体有两个或者多个匿名结构体时,并且匿名结构体都拥有相同字段和方法,但是结构体本身无同名字段和方法时,需指明匿名结构体,否则编译报错
    */
    // 声明一个tigger结构体
    t := tigger{}
    t.animal.name = "老虎..."    // 需要指明具体的匿名结构体
    t.animal.hobby = "吃小动物..." // 需要指明具体的匿名结构体
    t.animal.showHobby()

}

(二)进阶

1、组合

如果一个结构体嵌套了一个有名结构体,这种模式就是组合,如果时组合关系,那么在访问组合结构体的字段、方法时,必须带上结构体的名字。

package main

type Student struct {
    Name string
    Age  int
}

type MiddleStudent struct {
    s Student // 有名结构体,组合关系
}

func main() {
    var ms MiddleStudent
    // 必须带上有名结构体的名字s
    ms.s.Name = "Alice"
    ms.s.Age = 25
}

2、匿名结构体初始化值

 嵌套匿名结构体后,也可以在创建结构体变量时,直接指定各个匿名结构体字段的值。

package main

import "fmt"

type Student struct {
    Name string
    Age  int
}

type MiddleStudent struct {
    Student
}

type UgStudent struct {
    *Student
}

func main() {
    // 直接给匿名结构体初始化赋值
    ms := MiddleStudent{
        Student{Name: "kity", Age: 21},
    }

    ugs := UgStudent{
        &Student{Name: "jack", Age: 25},
    }

    fmt.Print(ms)
    fmt.Print(*ugs.Student)

}

3、基本数据类型的匿名字段

结构体的匿名字段时基本数据类型,又该如何访问呢?

package main

import "fmt"

type Student struct {
    username string
    age      int
    int      // 匿名字段(基本数据类型)
}

func main() {
    // 匿名字段
    s := Student{}
    s.username = "peter"
    s.age = 20
    s.int = 85
    fmt.Println(s)
}

注意:如果一个结构体有int类型的匿名字段,就不能有第二个;如果需要有多个int的字段,则必须给int字段指定名称。

4、多继承

一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现多继承。

package main

import "fmt"

type A struct {
    x1 string
    x2 int
}

type B struct {
    x1 float64
}

type C struct {
    A
    B
}

func main() {
    c := C{}
    // 必须指明匿名结构体的
    c.A.x1 = "abc"

    fmt.Println(c)
}

如果结构体嵌套多个匿名结构体,并且拥有相同的字段,而本结构体又没有该字段,则进行属性操作时必须指明匿名结构体。

三、多态

 多态的特性主要是通过接口来体现的,所以需要先了解清楚接口相关知识。

(一)接口基础

 接口(interface)类型就是定义一组方法,但是这些方法都不需要实现。并且interface不能包含任何任何变量,如果某个变量要使用interface时,再去实现具体的方法。

基本语法:

type 接口名称 interface {
  
    method1(参数列表) 返回值列表
    method2(参数列表)返回值列表
    ...   
   
}

接口中的所有方法都是没有方法体,即都是没有实现的方法。只要一个变量事项了接口类型中的所有方法,那么这个变量就实现了这个接口。

案例:

  我们电脑上有USB接口,这个接口可以插入多种设备,比如相机、手机、硬盘等。如果插入的的是硬盘那么就会读取硬盘空间的操作,如果是手机,那么就会执行手机对应的操作等。

package main

import "fmt"

// 定义一个接口
type Usb interface {
    // 声明两个未实现的方法
    start()
    stop()
}

// 声明一个Phone的结构体
type Phone struct {
}

// Phone结构体变量接口实现方法
func (phone Phone) start() {
    fmt.Println("手机开始工作...")
}

func (phone Phone) stop() {
    fmt.Println("手机结束工作...")
}

// 声明一个Camera的结构体
type Camera struct {
}

// Camera结构体变量接口实现方法
func (camera Camera) start() {
    fmt.Println("相机开始工作...")
}

func (camera Camera) stop() {
    fmt.Println("相机结束工作...")
}

// 声明一个Computer结构体
type Computer struct {
}

// usb会自动根据传递过来的来判断是phone还是camera然后调用对应的结构体变量中实现的方法
func (computer Computer) use(usb Usb) {
    usb.start()
    usb.stop()
}

func main() {
    // 创建结构体变量
    computer := Computer{}

    phone := Phone{}
    camera := Camera{}

    computer.use(phone)
    computer.use(camera)

}

/*
手机开始工作...
手机结束工作...
相机开始工作...
相机结束工作...
*/

(二)深入理解接口

  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义数据类型的实例
  • 空接口interface{}没有任何方法,所有类型都实现了空接口,即可以把任何一个类型的变量赋值给空接口
  • 接口中所有的方法都没有方法体,即都是没有实现的方法
  • Golang中,一个自定义类型将接口中所有的方法都实现了,才能说这个自定义类型实现了该接口
  • 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
  • 一个自定义类型可以实现多个接口
  • Golang接口中不能存在任何变量
  • 一个接口(A接口)可以继承多个其它接口(B、C接口),此时如果要实现A接口,则必须也实现B、C接口中的全部方法
  • 接口类型是一个指针(引用类型),如果没有对接口初始化就使用,就会输出nil
package main

import "fmt"

// 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义数据类型的实例
type A interface {
    m1()
}

type S1 struct {
}

func (s1 S1) m1() {
    // 实现A接口中的m1方法
}

func main() {
    s1 := S1{}
    var a1 A = s1
    fmt.Println(a1)
}
View Code
package main

import "fmt"

// 空接口interface{}没有任何方法,所有类型都实现了空接口,即可以把任何一个类型的变量赋值给空接口
type A interface {
}

func main() {
    var x int
    var i A = x
    fmt.Println(i)
}
View Code
package main

import "fmt"

// 一个自定义类型可以实现多个接口
type A interface {
    a1()
}

type B interface {
    b1()
}

type integer int

func (i integer) a1() {

}

func (i integer) b1() {

}

func main() {
    var i integer = 10
    var a A = i
    var b B = i
    fmt.Println(a, b)
}
View Code
package main

import "fmt"

// Golang中,一个自定义类型将接口中所有的方法都实现了,才能说这个自定义类型实现了该接口
type Animal interface {
    eat()
}

type Cat struct {
}

/*
这里如果写成这样,就是错误的,因为是*Cat指针类型实现了接口,而非Cat类型实现接口
func (c *Cat) eat() {

}

*/
func (c Cat) eat() {

}

func main() {

    c := Cat{}
    var a Animal = c
    fmt.Println(a)

}
View Code

(三)接口最佳实践

1、引入

如果对一个切片中都是int类型的数组或者切片进行排序,想必是一件比较简单的事情,比如可以通过golang中sort包Ints进行排序。

package main

import (
    "fmt"
    "sort"
)

func main() {
    // 声明一个元素是int类型的切片
    var intSlice = []int{2, 1, 10, 5}

    // 对切片进行排序
    sort.Ints(intSlice)

    // 打印排序好的切片
    fmt.Println(intSlice)
}

但是如果实现对结构体根据某个字段进行排序,又当如何呢,如下面:

type UserInfo struct {
    Username string
    Age int
}

将多个用户信息通过年龄进行排序。

2、结构体排序

此时可以通过sort包中Sort方法来实现:

func Sort(data Interface)

Sort方法的参数接收一个接口类型的参数data,只要自定义的数据类型实现了data接口类型的中的方法就可以传入,那么实现哎什么方法呢?

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int

    // Less reports whether the element with index i
    // must sort before the element with index j.
    //
    // If both Less(i, j) and Less(j, i) are false,
    // then the elements at index i and j are considered equal.
    // Sort may place equal elements in any order in the final result,
    // while Stable preserves the original input order of equal elements.
    //
    // Less must describe a transitive ordering:
    //  - if both Less(i, j) and Less(j, k) are true, then Less(i, k) must be true as well.
    //  - if both Less(i, j) and Less(j, k) are false, then Less(i, k) must be false as well.
    //
    // Note that floating-point comparison (the < operator on float32 or float64 values)
    // is not a transitive ordering when not-a-number (NaN) values are involved.
    // See Float64Slice.Less for a correct implementation for floating-point values.
    Less(i, j int) bool

    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

即:Len、less、Swap三个方法即可,所以将UserInfo切片类型实现这三个方法,然后传入到 Sort方法即可。

package main

import (
    "fmt"
    "math/rand"
    "sort"
)

// 声明一个UserInfo的结构体
type UserInfo struct {
    UserName string
    Age      int
}

// 声明一个UserInfo的切片
type UserInfoSlice []UserInfo

// 实现interface Len Less Swap
func (uis UserInfoSlice) Len() int {
    // 返回切片的长度
    return len(uis)
}

func (uis UserInfoSlice) Less(i, j int) bool {
    // 按照什么样的顺序进行排序
    return uis[i].Age > uis[j].Age
}

func (uis UserInfoSlice) Swap(i, j int) {
    // 交换,更简单的交换方式 uis[i], uis[j] = uis[j], uis[i]
    temp := uis[i]
    uis[i] = uis[j]
    uis[j] = temp

}

func main() {
    // 随机生成不同年龄的UserInfo切片
    var uis UserInfoSlice
    for i := 0; i < 20; i++ {
        ui := UserInfo{
            UserName: fmt.Sprintf("NO~%d", rand.Intn(50)),
            Age:      rand.Intn(100),
        }

        // 将ui加入到uis中
        uis = append(uis, ui)
    }

    // 排序前顺序
    for _, v := range uis {
        fmt.Println(v)
    }

    // 调用sort.Sort
    sort.Sort(uis)

    // 排序后顺序
    fmt.Println()
    for _, v := range uis {
        fmt.Println(v)
    }

}

(四)接口VS继承

接口于继承之间有什么区别呢?

不同点在于体现的价值不同,继承着重解决代码的复用问题,接口着重解决代码的设计和规范;联系是接口是对继承的一种扩展,当继承这些功能的同时,又希望不破环关系时,可以通过接口的方式来补充功能。

例如:猴子这种动物天性就是会爬树,从父辈那里继承过来的本领,但是小猴子不甘如此,希望会游泳,于是通过后天的不断努力终于学会了。这个例子中小猴子继承的就是父辈会爬树的本领,但是这个个例它还会游泳,如果将游泳加入到父辈哪里显然你不合理。

package main

import "fmt"

// 声明一个Monkey的结构体
type Monkey struct {
    Name string
}

// Monkey结构体天生拥有climbing方法
func (m *Monkey) Climbing() {
    fmt.Printf("%s爬树...", m.Name)
}

// 声明一个LittleMonkey结构体
type LittleMonkey struct {
    Monkey
}

// 声明一个接口
type OtherSkills interface {
    Swimming()
}

// LittleMonkey通过后天不断努力实现了OtherSkills
func (lk *LittleMonkey) Swimming() {
    fmt.Printf("%s学会了游泳...", lk.Name)
}

func main() {
    // 创建一个LittleMonkey的结构体变量
    lk := LittleMonkey{}
    lk.Name = "小猴子"

    // 调用天生的Climbing方法
    lk.Climbing()

    // 通过后天的努力实现了OtherSkills,学会了Swimming
    lk.Swimming()

}

  那么这个例子就体现出了接口是对继承功能的一种扩展,可能有人有疑问了,为什么不将Swimming方法直接写在LittleMonkey结构体变量中呢?实际上要这样讲的话也可以说通,但是写代码还是需要注重规范性和灵活性,使用接口不是显得更好吗?那个特殊个例实现了这个OtherSkills ,就拥有里面的本领。

(五)多态

多态就是通过接口来实现的,统一的接口,不同的表现,此时接口变量体现不同的形态。比如usb Usb接口变量,既可以接收手机变量,又可以接收相机变量,体现了Usb接口的多态特性。

接口体现多态的两种形式:

  • 多态参数
  • 多态数组

1、多态参数

参考之前Usb接口案例,既可以接收手机变量,又可以接收相机变量,体现了Usb接口的多态特性。

2、多态数组

package main

import "fmt"

// 定义一个接口
type Usb interface {
    // 声明两个未实现的方法
    start()
    stop()
}

// 声明一个Phone的结构体
type Phone struct {
    Name string
}

// Phone结构体变量接口实现方法
func (phone Phone) start() {
    fmt.Println("手机开始工作...")
}

func (phone Phone) stop() {
    fmt.Println("手机结束工作...")
}

// 声明一个Camera的结构体
type Camera struct {
    Name string
}

// Camera结构体变量接口实现方法
func (camera Camera) start() {
    fmt.Println("相机开始工作...")
}

func (camera Camera) stop() {
    fmt.Println("相机结束工作...")
}

func main() {
    // 创建Usb结构体数组变量
    var usbArr [3]Usb
    usbArr[0] = Phone{"iphone11"}
    usbArr[1] = Phone{"xiaomi"}
    usbArr[2] = Camera{"Canon"}
    fmt.Println(usbArr)

}

/*
[{iphone11}
{xiaomi}
{Canon}]
*/

可以看到将实现了接口的不同数据类型放到一个数组中,数组本身应该是放一种数据类型的,但是这里就体现了多态特性。

 

标签:func,语言,fmt,接口,面向对象编程,Go,main,type,结构
来源: https://www.cnblogs.com/shenjianping/p/15824129.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有