Go语言学习笔记—golang接口与实现面向对象特性

时间:2024-10-01 13:31:34

视频来源:B站《golang入门到项目实战 [2022最新Go语言教程,没有废话,纯干货!]》

文章为自己整理的学习笔记,侵权即删,谢谢支持!

文章目录

  • 一 golang接口简介
    • 1.1 语法
    • 1.2 实例演示
    • 1.3 注意事项
  • 二 golang接口值类型接收者和指针类型接收者
  • 三 golang接口和类型的关系
    • 3.1 一个类型实现多个接口
    • 3.2 多个类型实现同一个接口(多态特性的实现)
  • 四 golang接口嵌套
  • 五 golang通过接口实现OCP设计原则
  • 六 golang模拟OOP的属性和方法
  • 七 golang继承
    • 7.1 特性实现
    • 7.2 多重继承
  • 八 golang构造函数


一 golang接口简介

go语言的接口,是一种新的类型定义,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

1.1 语法

语法格式和方法非常类似。

/* 定义接口 */
type interface_name interface {
    method_name1 [return_type]
    method_name2 [return_type]
    method_name3 [return_type]
    ...
    method_namen [return_type]
}
 
/* 定义结构体 */
type struct_name struct {
    /* variables */
}
 
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name() [return_type] {
    /* 方法实现 */
}
...
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name() [return_type] {
    /* 方法实现 */
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在接口定义中定义,若干个空方法。这些方法都具有通用性。

1.2 实例演示

定义一个USB接口,有读read和写write两个方法,再定义一个电脑Computer和一个手机Mobile来实现这个接口。

定义USB接口:

type USB interface {
    read()
    write()
}
  • 1
  • 2
  • 3
  • 4

定义Computer结构体:

type Computer struct {
}
  • 1
  • 2

定义Mobile结构体:

type Mobile struct {
}
  • 1
  • 2

Computer实现USB接口方法:

func (c Computer) read() {
    fmt.Println("computer read...")
}
 
func (c Computer) write() {
    fmt.Println("computer write...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Mobile实现USB接口方法:

func (c Mobile) read() {
    fmt.Println("mobile read...")
}
 
func (c Mobile) write() {
    fmt.Println("mobile write...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试:

package main
 
import "fmt"
 
type USB interface {
    read()
    write()
}
 
type Computer struct {
}
 
type Mobile struct {
}
 
func (c Computer) read() {
    fmt.Println("computer read...")
}
 
func (c Computer) write() {
    fmt.Println("computer write...")
}
 
func (c Mobile) read() {
    fmt.Println("mobile read...")
}
 
func (c Mobile) write() {
    fmt.Println("mobile write...")
}
 
func main() {
    c := Computer{}
    m := Mobile{}
 
    c.read()
    c.write()
    m.read()
    m.write()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

运行结果:

computer read...
computer write...
mobile read...
mobile write...
  • 1
  • 2
  • 3
  • 4

1.3 注意事项

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)

  2. 接口中所有的方法都没有方法体,即都是没有实现的方法

  3. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口

  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型

  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

  6. 一个自定义类型可以实现多个接口

  7. Golang 接口中不能有任何变量

  8. 实现接口必须实现接口中的所有方法

    下面我们定义一个OpenClose接口,里面有两个方法open和close,定义个Door结构体,实现其中一个方法。

package main
 
import "fmt"
 
type OpenClose interface {
    open()
    close()
}
 
type Door struct {
}
 
func (d Door) open() {
    fmt.Println("open door...")
}
 
func main() {
    var oc OpenClose
    oc = Door{} // 这里编译错误,提示只实现了一个接口
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

二 golang接口值类型接收者和指针类型接收者

接口值类型接收者和指针类型接收者本质上与方法的值类型接收者和指针类型接收者的思考方法是一样的

值接收者是一个拷贝,是一个副本,而指针接收者,传递的是指针

如下实例演示:

定义一个Pet接口

type Pet interface {
    eat()
}
  • 1
  • 2
  • 3

定义一个Dog结构体:

type Dog struct {
    name string
}
  • 1
  • 2
  • 3

实现Pet接口(接收者是值类型):

func (dog Dog) eat() {
    fmt.Printf("dog: %p\n", &dog)
    fmt.Println("dog eat...")
    dog.name = "黑黑"
}
  • 1
  • 2
  • 3
  • 4
  • 5

测试:

package main
 
import "fmt"
 
type Pet interface {
    eat()
}
 
type Dog struct {
    name string
}
 
func (dog Dog) eat() {
    fmt.Printf("dog: %p\n", &dog)
    fmt.Println("dog eat...")
    dog.name = "黑黑"
}
 
func main() {
    dog := Dog{name: "花花"}
    fmt.Printf("dog: %p\n", &dog)
    dog.eat()
    fmt.Printf("dog: %v\n", dog)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果:

dog: 0xc000050230
dog: 0xc000050240
dog eat...
dog: {花花}
  • 1
  • 2
  • 3
  • 4

从运行结果,我们看出dog的地址变了,说明是复制了一份,dog的name没有变说明外面的dog变量没有被改变。

将Pet接口改为指针接收者:

package main
 
import "fmt"
 
type Pet interface {
    eat()
}
 
type Dog struct {
    name string
}
 
func (dog *Dog) eat() {
    fmt.Printf("dog: %p\n", dog)
    fmt.Println("dog eat...")
    dog.name = "黑黑"
}
 
func main() {
    dog := &Dog{name: "花花"}
    fmt.Printf("dog: %p\n", dog)
    dog.eat()
    fmt.Printf("dog: %v\n", dog)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果:

dog: 0xc000050230
dog: 0xc000050230
dog eat...
dog: &{黑黑}
  • 1
  • 2
  • 3
  • 4

从运行结果,我们看出dog的地址没变,说明传递的是指针,dog的name变了说明外面的dog变量被改变。

  1. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil
  2. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。

三 golang接口和类型的关系

golang接口和类型的关系为:

  1. 一个类型可以实现多个接口
  2. 多个类型可以实现同一个接口(在其他语言里即多态)

3.1 一个类型实现多个接口

例如:有一个Player接口可以播放音乐,有一个Video接口可以播放视频,一个手机Mobile实现这两个接口,既可以播放音乐,又可以播放视频。

定义一个Player接口:

type Player interface {
    playMusic()
}
  • 1
  • 2
  • 3

定义一个Video接口:

type Video interface {
    playVideo()
}
  • 1
  • 2
  • 3

定义Mobile接口体:

type Mobile struct {
}
  • 1
  • 2

实现两个接口:

func (m Mobile) playMusic() {
    fmt.Println("播放音乐")
}
 
func (m Mobile) playVideo() {
    fmt.Println("播放视频")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试:

package main
 
import "fmt"
 
type Player interface {
    playMusic()
}
 
type Video interface {
    playVideo()
}
 
type Mobile struct {
}
 
func (m Mobile) playMusic() {
    fmt.Println("播放音乐")
}
 
func (m Mobile) playVideo() {
    fmt.Println("播放视频")
}
 
func main() {
    m := Mobile{}
    m.playMusic()
    m.playVideo()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

运行结果:

播放音乐
播放视频
  • 1
  • 2

3.2 多个类型实现同一个接口(多态特性的实现)

例如:一个宠物接口Pet,猫类型Cat和狗类型Dog都可以实现该接口,都可以把猫和狗当宠物类型对待,这在其他语言中叫多态

定义一个Pet接口:

type Pet interface {
    eat()
}
  • 1
  • 2
  • 3

定义一个Dog结构体:

type Dog struct {
}
  • 1
  • 2

定义一个Cat结构体:

type Cat struct {
}
  • 1
  • 2

实现接口:

func (cat Cat) eat() {
    fmt.Println("cat eat...")
}
 
func (dog Dog) eat() {
    fmt.Println("dog eat...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试:

package main
 
import "fmt"
 
type Pet interface {
    eat()
}
 
type Dog struct {
}
 
type Cat struct {
}
 
func (cat Cat) eat() {
    fmt.Println("cat eat...")
}
 
func (dog Dog) eat() {
    fmt.Println("dog eat...")
}
 
func main() {
    var p Pet
    p = Cat{}
    p.eat()
    p = Dog{}
    p.eat()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

运行结果:

cat eat...
dog eat...
  • 1
  • 2

四 golang接口嵌套

接口可以通过嵌套,创建新的接口。

例如:飞鱼,既可以飞,又可以游泳。我们创建一个飞Fly接口,创建一个游泳接口Swim,飞鱼接口由这两个接口组成。

飞Fly接口:

type Flyer interface {
    fly()
}
  • 1
  • 2
  • 3

创建Swimmer接口:

type Swimmer interface {
    swim()
}
  • 1
  • 2
  • 3

组合一个接口FlyFish

type FlyFish interface {
    Flyer
    Swimmer
}
  • 1
  • 2
  • 3
  • 4

创建一个结构体Fish:

type Fish struct {
}
  • 1
  • 2

实现这个组合接口:

func (fish Fish) fly() {
    fmt.Println("fly...")
}
 
func (fish Fish) swim() {
    fmt.Println("swim...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试:

package main
 
import "fmt"
 
type Flyer interface {
    fly()
}
 
type Swimmer interface {
    swim()
}
 
type FlyFish interface {
    Flyer
    Swimmer
}
 
type Fish struct {
}
 
func (fish Fish) fly() {
    fmt.Println("fly...")
}
 
func (fish Fish) swim() {
    fmt.Println("swim...")
}
 
func main() {
    var ff FlyFish
    ff = Fish{}
    ff.fly()
    ff.swim()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

运行结果:

fly...
swim...
  • 1
  • 2

五 golang通过接口实现OCP设计原则

面向对象的可复用设计的第一块基石,便是所谓的“开-闭”原则(Open-Closed Principle,常缩写为OCP)。虽然,go不是面向对象语言,但是也可以模拟实现这个原则。

对扩展是开放的,对修改是关闭的。

下面通过一个人养宠物的例子,来解释OCP设计原则。

定义一个宠物接口Pet:

type Pet interface {
    eat()
    sleep()
}
  • 1
  • 2
  • 3
  • 4

该接口有吃和睡两个方法。

定义个Dog结构体:

type Dog struct {
    name string
    age  int
}
  • 1
  • 2
  • 3
  • 4

Dog实现接口方法:

func (dog Dog) eat() {
    fmt.Println("dog eat...")
}
 
func (dog Dog) sleep() {
    fmt.Println("dog sleep...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

定义一个Cat结构体:

type Cat struct {
    name string
    age  int
}
  • 1
  • 2
  • 3
  • 4

Cat实现接口方法:

func (cat Cat) eat() {
    fmt.Println("cat eat...")
}
 
func (cat Cat) sleep() {
    fmt.Println("cat sleep...")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

定义个Person结构体:

type Person struct {
    name string
}
  • 1
  • 2
  • 3

为Person添加一个养宠物方法:

func (per Person) care(pet Pet) {
    pet.eat()
    pet.sleep()
}
  • 1
  • 2
  • 3
  • 4

最后测试一下:

package main
 
import "fmt"
 
type Pet interface {
    eat()
    sleep()
}
 
type Dog struct {
    name string
    age  int
}
 
type Cat struct {
    name string
    age  int
}
 
type Person struct {
    name string
}
 
func (dog Dog) eat() {
    fmt.Println("dog eat...")
}
 
func (dog Dog) sleep() {
    fmt.Println("dog sleep...")
}
 
func (cat Cat) eat() {
    fmt.Println("cat eat...")
}
 
func (cat Cat) sleep() {
    fmt.Println("cat sleep...")
}
 
func (per Person) care(pet Pet) {
    pet.eat()
    pet.sleep()
}
 
func main() {
    dog := Dog{}
    cat := Cat{}
    per := Person{}
 
    per.care(dog)
    per.care(cat)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

运行结果:

dog eat...
dog sleep...
cat eat...
cat sleep...
  • 1
  • 2
  • 3
  • 4

六 golang模拟OOP的属性和方法

golang没有面向对象的概念,也没有封装的概念,可以通过结构体struct和函数绑定(在函数前加一个接收者receiver方法)来是想OOP的属性和方法等特性。

例如:想要定义一个Person类,有name和age属性,有eat/sleep/work方法。

package main
 
import "fmt"
 
type Person struct {
    name string
    age  int
}
 
func (per Person) eat() {
    fmt.Println("eat...")
}
 
func (per Person) sleep() {
    fmt.Println("sleep...")
}
 
func (per Person) work() {
    fmt.Println("work...")
}
 
func main() {
    per := Person{
        name: "tom",
        age:  20,
    }
 
    fmt.Printf("per: %v\n", per)
    per.eat()
    per.sleep()
    per.work()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

运行结果:

per: {tom 20}
eat...
sleep...
work...
  • 1
  • 2
  • 3
  • 4

七 golang继承

7.1 特性实现

golang本质上没有oop的概念,也没有继承的概念,但是可以通过结构体嵌套实现这个特性。

例如:

package main
 
import "fmt"
 
type Animal struct {
    name string
    age  int
}
 
func (a Animal) eat() {
    fmt.Println("eat...")
}
 
func (a Animal) sleep() {
    fmt.Println("sleep")
}
 
type Dog struct {
    Animal
}
 
type Cat struct {
    Animal
}
 
func main() {
    dog := Dog{
        Animal{
            name: "dog",
            age:  2,
        },
    }
 
    cat := Cat{
        Animal{
            name: "cat",
            age:  3,
        },
    }
 
    dog.eat()
    dog.sleep()
 
    cat.eat()
    dog.sleep()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

运行结果:

eat...
sleep
eat...
sleep
  • 1
  • 2
  • 3
  • 4

7.2 多重继承

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

package main

import "fmt"

type Goods struct {
	Name  string
	Price float64
}

type Brand struct {
	Name    string
	Address string
}

type TV struct {
	Goods
	Brand
}

func main() {
	tv := TV{
		Goods{
			Name:  "旗舰型",
			Price: 3000.00,
		},
		Brand{
			Name:    "小米",
			Address: "...",
		},
	}
	fmt.Printf("电视型号是: %v\n", tv.Goods.Name)
	fmt.Printf("该品牌的价格: %v\n", tv.Price)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

运行结果:

电视型号是: 旗舰型
该品牌的价格: 3000
  • 1
  • 2
  1. 一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必须将 B,C 接口的方法也全部实现。
  2. 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
  3. 为了保证代码的简洁性,建议大家尽量不使用多重继承

八 golang构造函数

golang没有构造函数的概念,可以使用函数来模拟构造函数的功能。

例如:

package main 
 
import "fmt" 
 
type Person struct { 
    name string 
    age  int 
} 
 
func NewPerson(name string, age int) (*Person, error) { 
    if name == "" { 
        return nil, fmt.Errorf("name 不能为空") 
    } 
 
    if age < 0 { 
        return nil, fmt.Errorf("age 不能小于0") 
    } 
    return &Person{name: name, age: age}, nil 
} 
 
func main() { 
    person, err := NewPerson("tom", 20) 
    if err == nil { 
        fmt.Printf("person: %v\n", *person) 
    } 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

运行结果:

person: {tom 20} 
  • 1