当前位置: 首页 > news >正文

Go语言接口实现面对对象的三大特征

一.知识回顾

在 Go 语言中,接口是一种强大的抽象机制,它允许我们定义一组方法签名,任何类型只要实现了这些方法,就被视为实现了该接口。接口的实现是隐式的,这意味着类型不需要显式声明它实现了某个接口,只要提供了接口所需的所有方法即可。

接口:就是一组方法的签名,任何类型只要实现这些方法,就可以被视为实现了该接口

package mainimport "fmt"type speaker interface {speak()//只要实现了speak方法的,都可以是speaker类型
}
type cat struct{}
type dog struct{}func (d dog) speak() {fmt.Println("汪汪汪")
}
func (c cat) speak() {fmt.Println("喵喵喵")
}func da(x speaker) {//接收参数,传入什么,我就打印什么x.speak()
}// 在编程存在
func main() {var c1 catvar d1 dogda(c1)da(d1)
}

所以接口是比较抽象的东西,他的这个类型只是存储了共同的方法,这样的目的就是为了实现不管传入是什么类型的,都可以实现他的方法

1.1 接口的实现和实现

type 接口名 interfacce{方法名1(参数1,参数2....)(返回值1,返回值2....)方法2.....
}

接口的实现:

一个变量如果实现了接口中规定的所有的方法,那么这个变量就实现了这个接口,可以称为这个接口类型的变量。

(这一句话很重要,你要实现了所有的方法才算是说是接口,如果你只是实现了接口内所定义的个别方法的话,就不能称之为接口)

就比如猫狗,这些就是接口类型,也就是speaker类型。

如果一个接口没有方法呢?

这样的接口被称为是空接口

它的主要作用:

  • 作为函数的参数(可以是任意类型,因为所有类型都实现了空接口)
  • 作为map的值,可以是字典保存任意类型的值

正是因为有了空接口的概念,就会出现分不清1和 '1'的情况,这个时候就需要类型断言

package mainimport "fmt"func assign(a interface{}) {str, ok := a.(string)if ok {fmt.Println(str)} else {fmt.Println("错误")}//fmt.Printf("type:%T,value:%v\n", a, a)//这里其实用switch猜合适
}func assign2(a interface{}) {fmt.Printf("type:%T,value:%v\n", a, a)switch a.(type) {case int:fmt.Println("int")case string:fmt.Println("string")case bool:fmt.Println("bool")case float64:fmt.Println("float64")}
}
func main() {var m1 map[string]interface{}m1 = make(map[string]interface{}, 16)m1["name"] = "cxy"m1["age"] = 18m1["married"] = truem1["hobby"] = [...]string{"唱", "跳"}fmt.Println(m1)assign2("100")
}

1.2 接口和类型的关系

多个类型可以实现一个接口,是上述我们经常使用的。

除此之外还可以一个类型对应多个接口

1.3 接口的嵌套

接口的嵌套,也就是接口里包含接口,要实现的话,父接口,就必须实现所有的子接口才可以。

二.接口实现面对对象的三大特征

面对对象的3个概念:封装,继承,多态

2.1 封装

封装的好处:

  1. 隐藏实现细节;
  2. 可以对数据进行校验,保证安全合理;

go语言的封装和其他语言稍微有点不同。

主要就是go语言的封装是包级别的,在同一个包内均是可见的,其它包内是不可见的,除非是大写字母开头才是可见的。否则只可以通过封装来实现。

接下来写一个封装的例子:

package mainimport "fmt"type Person struct {name stringage  int
}func (p *Person) SetName(name string) {p.name = name
}func (p *Person) SetAge(age int) {p.age = age
}func (p *Person) GetName() string {return p.name
}func (p *Person) GetAge() int {return p.age
}func main() {//p := &Person{}p := new(Person)p.SetName("Alice")p.SetAge(18)fmt.Println(p.GetName())	//Alicefmt.Println(p.GetAge())		//18
}

2.2 继承

go里面的继承可能和之前学习的不太一样,但是大致内容是一致的,只是他的形式不太一样而已。

这里我们会发现一个问题:

就是结构体的嵌套和继承是有区别的,要注意

在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,这就是所谓的继承。

package mainimport "fmt"type animal struct {name string
}func (a animal) wang() {fmt.Printf("%d会动", a.name)
}type dog struct {animal //这样的话animal内有的属性,这样的话他的后代dog也会有name属性,feet   int
}func (d dog) wang() {fmt.Printf("%s会汪汪汪", d.name)//这里的查找如果dog没有的话就会找上一级结构体。
}// 返回尽量使用指针,如果返回变量的话开销比较大
func Init(name string) *dog {return &dog{animal: animal{name: name,},feet: 4,}
}func main() {a1 := Init("haha\n")a1.wang()fmt.Println(a1)
}
package mainimport "fmt"type animal struct {name string
}func (a animal) wang() {fmt.Printf("%d会动", a.name)
}type dog struct {animalfeet int
}func (d dog) wang() {fmt.Printf("%s会汪汪汪\n", d.name)//这里的查找如果dog没有的话就会找上一级结构体。
}func (a *dog) Init(name string, feet int) {a.name = namea.feet = feet
}func main() {a1 := &dog{}a1.Init("小狗", 4)a1.wang()fmt.Println(a1.name)fmt.Println(a1.feet)
}小狗会汪汪汪
小狗
4

而结构体的嵌套是什么样子呢?举一个简单的例子

就是加上类型。

继承存在的问题是非常多的,在后续都会介绍。

2.3 多态

多态啥意思?

就是在不同继承关系的类对象,调用同一函数时,产生的不同行为。

暂时先这样理解

变量(实例)具有多种形态,在Go语言中,多态特征是通过接口实现的。

可以按照统一的接口来调用不同的接口实现。这时接口变量就呈现出不同的形态。

这就是go里面多态的实现。

package mainimport "fmt"type Speaker interface {Speak()
}type Person struct {name string
}func (p *Person) Speak() {fmt.Println("Hello, I am a person")
}type Cat struct {name string
}func (c *Cat) Speak() {fmt.Println("Hello, I am a cat")
}func main() {speaker1 := &Person{}speaker2 := &Cat{}speaker1.Speak()speaker2.Speak()
}Hello, I am a person
Hello, I am a cat

虽然这里也实现了,但是实际上,不能完全被称为多态,为什么?

因为这里没有做到父类指针(或者父类引用)指向子类对象

func main() {var speaker Speakerspeaker = &Cat{}speaker.Speak()speaker = &Person{}speaker.Speak()
}

三.多继承和二义性

3.1 多继承

多继承:就是指一个结构体里面有着多个匿名结构体,从而实现了一个结构体的多个继承

那么随之而来就会有很多问题:

比如:如果我继承的多个父类,他们(包括本身)有相同的字段怎么办呢?

这里go语言提供了一种解决办法,就是通过加匿名字段的类型,从而实现对相同属性的区分

(这里也被称之为同名二义性)

并且继承之后,也可以调用父类的方法,不过虽然是调用父类的方法,但是本质你还是修饰的父类,所以打印的内容还是属于是父类的。

package mainimport "fmt"type Father struct {Name stringAge  int
}type Mother struct {Name string
}type Son struct {Name string// 结构体的匿名字段可以是基础数据类型,这种没有名字的字段就称为匿名字段,调用时基于该基础数据类型调用即可;//这里匿名字段的说法并不代表没有字段名,而是默认会采用类型名作为字段名;// 结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个。// 继承多个结构体,尽管Go语言支持多继承,但推荐少用。很多语言就将多重继承去除了,因为容易造成逻辑混乱。FatherMother
}func (f *Father) DriveCar() {fmt.Printf("%s开车很稳~\n", f.Name)
}func (m *Mother) Sing() {fmt.Printf("%s唱歌很好听~\n", m.Name)
}func (s *Son) Dance() {fmt.Printf("%s跳舞很好看\n", s.Name)
}func main() {// 构建Son结构体实例// s := Son{"唐三", 18, Father{"唐昊", 30}, Mother{"阿银"}}s := Son{"唐三",18,// 在创建嵌套匿名结构体变量(实例)时,可以直接指定各个匿名结构体字段的值;Father{Name: "唐昊",Age:  30,},Mother{Name: "阿银",},}fmt.Printf("s = %v\n", s)// 通过Son结构体实例的确可以调用多个继承结构体的方法s.Sing()s.Dance()s.DriveCar()// 如嵌入的匿名结构体有相同的字段名或者方法名称,则在访问时,需要通过匿名结构体类型名来区分;fmt.Printf("[%s]的今年[%d]岁, 父亲是: [%s], 今年[%d]岁, 母亲是: [%s]\n", s.Name, s.int, s.Father.Name, s.Age, s.Mother.Name)
}s = {唐三 18 {唐昊 30} {阿银}}
阿银唱歌很好听~
唐三跳舞很好看
唐昊开车很稳~
[唐三]的今年[18]岁, 父亲是: [唐昊], 今年[30]岁, 母亲是: [阿银]

3.2 二义性

二义性:主要分为同名二义性和路径二义性

同名二义性就是字段名字一样,通过加上匿名字段来分隔就可以

其次就是路径二义性:举个例子:

子类调用祖父类的属性,父类有两个,编译器不知道这个属性是从哪一个路径来的,被称为是路径二义性。

package mainimport "fmt"type A struct {X int
}type B struct {A
}type C struct {A
}type D struct {BC
}func main() {test := D{B: B{A{X: 1}}, C: C{A{X: 2}}}fmt.Println(test.X)//fmt.Println(test.B.X) // 输出: 1//fmt.Println(test.C.X) // 输出: 2
}

对于这种情况,就会出现一个报错的问题。

至于怎么解决,其实和解决上述方法一样,加上前缀匿名字段即可

四.多态的进一步了解

4.1 多态实现的三个条件

多态:总结一句话:同一对象的不同表现形式

在C++的学习里面,常说多态有三个条件嘛:

  1. 要有继承
  2. 要有虚函数重写
  3. 父类指针(或者父类引用)指向子类对象

但是在go语言稍微有点区别,其实继承已经改为实现接口,虚函数重写其实就是类似一个接口,通过对接口定义的方法进行重写,最后通过接口对象获取不同的类型,从而达到一个多态的效果。

package mainimport "fmt"type Speaker interface {Speak()
}type Person struct {name string
}func (p *Person) Speak() {fmt.Println("Hello, I am a person")
}type Cat struct {name string
}func (c *Cat) Speak() {fmt.Println("Hello, I am a cat")
}func main() {var speaker Speakerspeaker = &Cat{}speaker.Speak()speaker = &Person{}speaker.Speak()
}

相关文章:

  • OpenHarmony平台驱动开发(二),CLOCK
  • JavaScript性能优化实战(9):图像与媒体资源优化
  • Java设计模式: 实战案例解析
  • 装饰模式(Decorator Pattern)
  • 注意力机制
  • 学习黑客 week1周测 复盘
  • QT | 常用控件
  • 洛谷 P1495:【模板】中国剩余定理(CRT)/ 曹冲养猪
  • 马小帅面试遇“灵魂拷问“
  • 【前端】【面试】在 Nuxt.js SSR/SSG 应用开发的 SEO 优化方面,你采取了哪些具体措施来提高页面在搜索引擎中的排名?
  • Ubuntu22.04及以上版本buildroot SIGSTKSZ 报错问题
  • Java大厂面试:Java技术栈中的核心知识点
  • Java并发编程-多线程基础(三)
  • 集成算法学习
  • 具身系列——比较3种vpg算法方式玩CartPole游戏(强化学习)
  • 【Unity】使用XLua进行热修复
  • Ray开源程序 是用于扩展 AI 和 Python 应用程序的统一框架。Ray 由一个核心分布式运行时和一组用于简化 ML 计算的 AI 库组成
  • 4电池_基于开关电容的均衡
  • 项目实战-25年美赛MCM/ICM-基于数学建模与数据可视化的动态系统模型
  • agent初识
  • 巴菲特第60次股东大会开场点赞库克:他为伯克希尔赚的钱比我还多
  • 我国首个少数民族非遗纺织类国标正式实施
  • 49:49白热化,美参议院对新关税政策产生巨大分歧
  • 人民日报钟声:国际社会应共同维护科学溯源的正确方向
  • 新华时评:防范安全事故须臾不可放松
  • 朝鲜海军新型驱逐舰进行首次武器系统测试