07-抚摸抽象边界:Golang 接口的多彩展现

news2024/11/15 18:02:38

在这里插入图片描述

📃个人主页:个人主页
🔥系列专栏:Golang基础
💬Go(又称Golang)是由Google开发的开源编程语言。它结合了静态类型的安全性和动态语言的灵活性,拥有高效的并发编程能力和简洁的语法。Go被设计用于构建可扩展、高性能的软件系统,具有优秀的内存管理和快速的编译速度,适用于Web开发、系统编程和云计算等领域。

文章目录

  • 什么是interface
  • interface类型
  • interface值
  • 空interface
  • interface函数参数
  • 变量存储的类型
    • 1.Comma-ok断言
    • 2.switch测试
  • 嵌入interface

Go语言里面设计最精妙的应该算 interface ,它让面向对象,内容组织实现非常的方便

什么是interface

简单的说, interface 是一组 method 签名的组合,通过 interface 来定义对象的一组行为。
前面例子中 StudentEmployee 都能 SayHi ,虽然他们的内部实现不一样,但是那不重要,重要的是他们都能 say hi
继续做更多的扩展, StudentEmployee 实现另一个方法 Sing ,然后 Student 实现方法 BorrowMoneyEmployee 实现 SpendSalary
这样 Student 实现了三个方法: SayHiSingBorrowMoney ;而 Employee 实现了 SayHiSingSpendSalary
上面这些方法的组合称为 interface (被对象 StudentEmployee 实现)。例如 StudentEmployee 都实现了 interface : SayHiSing ,也就是这两个对象是该 interface 类型。而 Employee 没有实现这个 interface:SayHiSingBorrowMoney ,因为 Employee 没有实现 BorrowMoney 这个方法。

interface类型

interface 类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子:

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段Human
	school string
	loan float32
}
type Employee struct {
	Human //匿名字段Human
	company string
	money float32
}
//Human对象实现Sayhi方法
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {
	fmt.Println("La la, la la la, la la la la la...", lyrics)
}
//Human对象实现Guzzle方法
func (h *Human) Guzzle(beerStein string) {
	fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
	e.company, e.phone) //此句可以分成多行
}
//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
	s.loan += amount // (again and again and...)
}
//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
	e.money -= amount // More vodka please!!! Get me through the day!
}
// 定义interface
type Men interface {
	SayHi()
	Sing(lyrics string)
	Guzzle(beerStein string)
}
type YoungChap interface {
	SayHi()
	Sing(song string)
	BorrowMoney(amount float32)
}
type ElderlyGent interface {
	SayHi()
	Sing(song string)
	SpendSalary(amount float32)
}

通过上面的代码可以知道,interface 可以被任意的对象实现。看到上面的 Men interfaceHumanStudentEmployee 实现。
同理,一个对象可以实现任意多个 interface,例如上面的 Student 实现了 MenYoungChap 两个 interface
最后,任意的类型都实现了空 interface (这样定 义:interface{}),也就是包含 0methodinterface

interface值

那么 interface 里面到底能存什么值呢?如果定义了一个 interface 的变量,那么这个变量里面可以存实现这个 interface 的任意类型的对象。例如上面例子中,定义了一个 Men interface 类型的变量 m,那么 m 里面可以存 HumanStudent 或者 Employee 值。
因为 m 能够持有这三种类型的对象,所以可以定义一个包含 Men 类型元素的 slice,这个 slice 可以被赋予
实现了 Men 接口的任意结构的对象,这个和传统意义上面的 slice 有所不同。
来看一下下面这个例子:

package main
import "fmt"

type Human struct {
	name string
	age int
	phone string
}
type Student struct {
	Human //匿名字段
	school string
	loan float32
}
type Employee struct {
	Human //匿名字段
	company string
	money float32
}
//Human实现SayHi方法
func (h Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human实现Sing方法
func (h Human) Sing(lyrics string) {
	fmt.Println("La la la la...", lyrics)
}
//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
	e.company, e.phone)
}
// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
	SayHi()
	Sing(lyrics string)
}
func main() {
	mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
	paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
	sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
	tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}
	//定义Men类型的变量i
	var i Men
	//i能存储Student
	i = mike
	fmt.Println("This is Mike, a Student:")
	i.SayHi()
	i.Sing("November rain")
	//i也能存储Employee
	i = tom
	fmt.Println("This is tom, an Employee:")
	i.SayHi()
	i.Sing("Born to be wild")
	//定义了slice Men
	fmt.Println("Let's use a slice of Men and see what happens")
	x := make([]Men, 3)
	//这三个都是不同类型的元素,但是他们实现了interface同一个接口
	x[0], x[1], x[2] = paul, sam, mike
	for _, value := range x{
		value.SayHi()
	}
}

通过上面的代码,发现 interface 就是一组抽象方法的集合,它必须由其他非 interface 类型实现,而不能自我实现, Go通过 interface 实现了 duck-typing 即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。

空interface

interface ( interface{} )不包含任何的 method ,正因为如此,所有的类型都实现了 空interface空interface 对于描述起不到任何的作用(因为它不包含任何的 method ),但是 空interface 需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的 void* 类型。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

一个函数把 interface{} 作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回 interface{} ,那么也就可以返回任意类型的值。

interface函数参数

interface 的变量可以持有任意实现该 interface 类型的对象,这给编写函数(包括 method )提供了一些额外的思考,是不是可以通过定义 interface 参数,让函数接受各种类型的参数。
举个例子:·fmt.Println· 是常用的一个函数,是否注意到它可以接受任意类型的数据。
打开fmt的源码文件,会看到这样一个定义:

type Stringer interface {
	String() string
}

也就是说,任何实现了 String 方法的类型都能作为参数被 fmt.Println 调用,来试一试:

package main
import (
	"fmt"
	"strconv"
)
type Human struct {
	name string
	age int
	phone string
}
// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
	return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱"
}
func main() {
	Bob := Human{"Bob", 39, "000-7777-XXX"}
	fmt.Println("This Human is : ", Bob)
}

现在再回顾一下前面的 Box 示例,发现 Color 结构也定义了一个 method:String 。其实这也是实现了 fmt.Stringer 这个 interface ,即如果需要某个类型能被 fmt 包以特殊的格式输出,就必须实现 Stringer 这个接口。如果没有实现这个接口,fmt 将以默认的方式输出。

//实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

注:实现了 error 接口的对象(即实现了 Error() string 的对象),使用 fmt 输出时,会调用 Error() 方法,因此不必再定义String() 方法了。

变量存储的类型

interface 的变量里面可以存储任意类型的数值(该类型实现了 interface )。那么怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:

1.Comma-ok断言

Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T) ,这里 value 就是变量的值,ok 是一个 bool 类型,elementinterface 变量,T 是断言的类型。
如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false
通过一个例子来更加深入的理解:

package main
import (
	"fmt"
	"strconv"
)

type Element interface{}
type List [] Element
type Person struct {
	name string
	age int
}
//定义了String方法,实现了fmt.Stringer
func (p Person) String() string {
	return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}
func main() {
	list := make(List, 3)
	list[0] = 1 // an int
	list[1] = "Hello" // a string
	list[2] = Person{"Dennis", 70}
	for index, element := range list {
		if value, ok := element.(int); ok {
			fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
		} else if value, ok := element.(string); ok {
			fmt.Printf("list[%d] is a string and its value is %s\n", index,
			value)
		} else if value, ok := element.(Person); ok {
			fmt.Printf("list[%d] is a Person and its value is %s\n", index,
			value)
		} else {
			fmt.Printf("list[%d] is of a different type\n", index)
		}
	}
}

是否注意到了多个 if 里面,if 里面允许初始化变量。断言的类型越多,那么 if else 也就越多,所以才引出了下面要介绍的 switch

2.switch测试

重写上面的这个实现:

package main

import (
	"fmt"
	"strconv"
)

type Element interface{}
type List []Element
type Person struct {
	name string
	age  int
}

// 打印
func (p Person) String() string {
	return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
}
func main() {
	list := make(List, 3)
	list[0] = 1       //an int
	list[1] = "Hello" //a string
	list[2] = Person{"Dennis", 70}
	for index, element := range list {
		switch value := element.(type) {
		case int:
			fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
		case string:
			fmt.Printf("list[%d] is a string and its value is %s\n", index,
				value)
		case Person:
			fmt.Printf("list[%d] is a Person and its value is %s\n", index,
				value)
		default:
			fmt.Println("list[%d] is of a different type", index)
		}
	}
}

这里有一点需要强调的是: element.(type) 语法不能在 switch 外的任何逻辑里面使用,如果要在
switch 外面判断一个类型就使用 comma-ok

嵌入interface

Go里面真正吸引人的是它内置的逻辑语法,就像在学习 Struct 时学习的匿名字段,那么相同的逻辑引入到 interface 里面,更加完美了。如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1 里面的 method
可以看到源码包 container/heap 里面有这样的一个定义:

type Interface interface {
	sort.Interface //嵌入字段sort.Interface
	Push(x interface{}) //a Push method to push elements into the heap
	Pop() interface{} //a Pop elements that pops elements from the heap
}

看到 sort.Interface 其实就是嵌入字段,把 sort.Interface 的所有 method 给隐式的包含进来了。
也就是下面三个方法:

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less returns whether the element with index i should sort
	// before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

另一个例子就是 io 包下面的 io.ReadWriter ,它包含了 io 包下面的 ReaderWriter 两个 interface

// io.ReadWriter
type ReadWriter interface {
	Reader
	Writer
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/658015.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 操作 Excel 全攻略 | 包括读取、写入、表格操作、图像输出和字体设置

文章目录 前言Python 操作 Excel 教程1. Excel 文件的读取与写入2. Excel 表格的操作2.1 插入和删除行和列2.2 遍历表格中的单元格并修改值 3. 图像的输出3.1 输出柱状图 4. 字体的设置4.1 设置单元格的字体大小和颜色4.2 设置单元格的加粗和斜体4.3 设置单元格的边框和填充颜色…

android实现无root获取其它应用data私有数据

实现原理就是反编译app的AndroidManifest文件,注意是反编译应用的资源文件,而不是编译整个app,这个操作不需要动应用的dex,难度上要容易得多。解码资源文件要用到一些工具,android下推荐ARSCLib。接下来是对目标应用重…

04_Linux设备树DTB文件OF函数

目录 创建小型模板设备树 添加cpus节点 添加soc节点 添加ocram节点 添加aips1、aips2和aips3这三个子节点 添加eespil、usbotg1和rngb这三个外设控制器节点 设备树在系统中的体现 根节点“/”各个属性 根节点“/”各子节点 特殊节点 aliases子节点 chosen子节点 L…

转专业之我见

写在前面 如果你点进来看这篇文章,说明你的至少有想转专业的想法甚至心里是趋向于转专业的。 但是或许是因为学校只有一次转专业的机会或者有别的原因让你犹豫不决,那么你首先要明确你为什么想要转专业,是因为本专业是天坑专业,…

UI 自动化测试 —— selenium的简单介绍和使用

selenium 是 web 应用中基于 UI 的自动化测试框架,支持多平台、多浏览器、多语言。 提到 UI 自动化就先了解什么是自动化测试? 目录 1. 自动化测试 2. UI 自动化 2.1 UI 自动化的特点 2.2 UI 自动化测试的优缺点 2.3 UI 自动化测试的使用对象 2.4…

PPG信号和ECG信号检测血管年龄

PAT 通常用作动脉硬度的间接测量值或心血管健康的指标。它与各种生理和病理状况有关,例如高血压、动脉硬化和内皮功能障碍。 通过脉搏到达时间进行测量,简单来说就是 先从脉冲传输时间 PPG 数据集中提取数据,提取此数据集中每个对象的脉冲到…

【Python从入门到进阶】24、urllib获取网站电影排行

接上篇《23、urllib使用post请求百度翻译》 上一篇我们讲解了如何使用urllib实现百度翻译的效果。本篇我们来讲解如何使用urllib抓取某某电影排行榜信息。 一、某某电影介绍 1、某某电影网站 某某电影成立于2005年,最初只是一个小型的电影社区,但随着…

【备战秋招】每日一题:2023.05-B卷-华为OD机试 - 报文重排序

为了更好的阅读体检,可以查看我的算法学习博客报文重排序 题目描述 对报文进行重传和重排序是常用的可靠性机制,重传缓中区内有一定数量的子报文,每个子报文在原始报文中的顺序已知,现在需要恢复出原始报文。 输入描述 输入第…

改进YOLOv8 | 优化器篇 | YOLOv8 引入谷歌 Lion 优化器

论文地址:https://arxiv.org/pdf/2302.06675.pdf 代码地址:https://github.com/google/automl/tree/master/lion 我们提出了一种将算法发现作为程序搜索的方法,并将其应用于发现用于深度神经网络训练的优化算法。我们利用高效的搜索技术来探索一个无限且稀疏的程序空间。为了…

【SCADA】测试用KingIOServer采集杰控OPC DA服务器数据

Hello,大家好,我是雷工! 现场做数据采集时经常会遇到需要通过OPC采集数据的情况,本篇测试KingIOServer采集北京杰控组态软件的OPCDA服务器数据。 以下为测试记录过程。 一、KingIOServer的OPC DA数据采集介绍 KingIOServer可以作…

Vue中如何进行分布式日志管理与日志分析

Vue中如何进行分布式日志管理与日志分析 在现代应用程序中,日志是一项重要的功能,用于帮助开发人员和运维人员了解应用程序的行为并进行故障排除。随着应用程序的规模和复杂性的增加,日志管理和分析变得越来越困难。本文将介绍如何在Vue应用…

JSON对象花样进阶

JSON 对象使用在大括号{ }中书写。 对象可以包含多个 key/value(键/值)对。 key 必须是字符串,value 可以是合法的 JSON 数据类型(字符串, 数字, 对象, 数组, 布尔值或 null)。 key 和 value 中使用冒号(:)分割。 每个…

Postman接口自动化之postman脚本编写

这是之前搞的接口自动化方案,已经在业务测试中实现了使用postman编写接口脚本,通过GitHubJenkinsemail html report实现了接口自动化,现在分块整理一下。 postman脚本编写 1、创建集合 和 目录: 一条业务线下的接口可以放到一个…

ACL2023 | 大模型如何快速构建指令遵循数据集?self-instruct:用175条种子数据追上InstructGPT001效果

一、概述 title:SELF-INSTRUCT: Aligning Language Models with Self-Generated Instructions 论文地址:https://arxiv.org/abs/2212.10560 代码:GitHub - yizhongw/self-instruct: Aligning pretrained language models with instruction…

chatgpt赋能python:Python怎么输出Unicode值

Python怎么输出Unicode值 Python 是一种高级编程语言,因其简单易学和快速开发已成为许多开发者的首选。Python 可以输出多种数据类型,包括字符串和数字。 在许多情况下,输出 Unicode 值是必需的,本文将介绍在 Python 中如何输出 …

shardingsphere第二课-shardingsphere-jdbc的基本使用及各种分片策略

第一章介绍 一、ShardingJDBC客户端分库分表 ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动…

chatgpt赋能python:Python中如何输入一个列表

Python中如何输入一个列表 输入一个列表是Python编程的基本任务之一。列表可以看做是一种序列,其中包含多个元素,用逗号隔开,并用方括号括起来。在Python中,列表是一种非常常见的数据类型,常用于存储和处理一系列相关…

断言操作符介绍

目录 1.延时操作符(##) 1.1 ##m 1.2 ##[m:n] 2.蕴含操作符(|>,|->) 2.1 |>操作符 2.2 |->操作符 3 重复操作符 ([*m][->m][m]) 3.1 连续重复操作符([*m][*m:n]) 3.2 跟随重复操作…

Java选择题刷题记录5

Java堆栈 图片来自https://www.cnblogs.com/cici-new/p/14963762.html 数组、String都在堆里 枚举类 1.枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承java.lang.Enum类,而不是默认继承Object类,其中 java.lang.Enum类实现了…

机器视觉硬件的选择-标定板

康耐视智能相机Insight-缺陷检测 一>棋盘格的作用 a>畸变校正 径向畸变,径向畸变就是沿着透镜半径方向分布的畸变,产生原因是光线在原理透镜中心的地方比靠近中心的地方更加弯曲,这种畸变在短焦镜头中表现更加明显,径 向畸变主要包括桶形畸变和枕形畸变两种。以下分别…