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

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()
}

http://www.xdnf.cn/news/3844.html

相关文章:

  • 基于大模型的隐睾(睾丸可触及)预测及临床干预策略研究报告
  • spring中的@Profile注解详解
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】2.3 窗口函数与高级聚合(ROW_NUMBER()/RANK()/SUM() OVER())
  • 民法学学习笔记(个人向) Part.3
  • Python 库 petrel_client.client 浅入浅出
  • 【CISCO】什么是静态路由(Static Route)?ip route 192.0.1.0 255.255.255.0 200.0.0.1
  • 一周学会Pandas2 Python数据处理与分析-Pandas2复杂数据查询操作
  • 【前端】【面试】在 Nuxt.js SSR/SSG 应用开发的 SEO 优化方面,你采取了哪些具体措施来提高页面在搜索引擎中的排名?
  • NPP库中libnppist模块介绍
  • 利用flask设计接口
  • 学习黑客 week1周测 复盘
  • AIDC智算中心建设:计算力核心技术解析
  • 0.0973585?探究ts_rank的score为什么这么低
  • Spring AI 实战:第十章、Spring AI RAG之博学多才
  • 构建“设备数据抽取 + 可视化”华为云rest、soap、roma和自定义的这些连接器类型和作用说明
  • 【人工智能学习笔记 二】 MCP 和 Function Calling的区别与联系
  • OpenCV入门指南:从环境搭建到第一个图像处理程序
  • ios systeam introduction
  • 机器学习和深度学习的对比
  • 科普简洁版:同态加密——密码学的未来瑰宝
  • 五一作业-day01
  • STM32Cube-FreeRTOS任务管理工具函数-笔记
  • 【QT】QT中的网络编程(TCP 和 UDP通信)
  • ES6入门---第二单元 模块五:模块化
  • 【Godot】使用 Shader 实现可配置圆角效果
  • 34.多点求均值的模拟信号抗干扰算法使用注意事项
  • word批量转pdf工具
  • Semaphore的详细源码剖析
  • 函数递归+函数栈帧(简)
  • chili3d调试10 网页元素css node deepwiki 生成圆柱体 生成零件图片