《Go 语言第一课》课程学习笔记(十四)

接口

认识接口类型

  • 接口类型是由 type 和 interface 关键字定义的一组方法集合,其中,方法集合唯一确定了这个接口类型所表示的接口。
    type MyInterface interface {M1(int) errorM2(io.Writer, ...string)
    }
    
    • 我们在接口类型的方法集合中声明的方法,它的参数列表不需要写出形参名字,返回值列表也是如此。也就是说,方法的参数列表中形参名字与返回值列表中的具名返回值,都不作为区分两个方法的凭据。
    • Go 语言要求接口类型声明中的方法必须是具名的,并且方法名字在这个接口类型的方法集合中是唯一的。
    • Go 1.14 版本以后,Go 接口类型允许嵌入的不同接口类型的方法集合存在交集,但前提是交集中的方法不仅名字要一样,它的函数签名部分也要保持一致,也就是参数列表与返回值列表也要相同,否则 Go 编译器照样会报错。
    • 在 Go 接口类型的方法集合中放入首字母小写的非导出方法也是合法的。如果接口类型的方法集合中包含非导出方法,那么这个接口类型自身通常也是非导出的,它的应用范围也仅局限于包内。
    • 方法集合为空的接口类型就被称为空接口类型,但通常我们不需要自己显式定义这类空接口类型,我们直接使用interface{}这个类型字面值作为所有空接口类型的代表就可以了。
  • 接口类型一旦被定义后,它就和其他 Go 类型一样可以用于声明变量,比如:
    var err error // err是一个error接口类型的实例变量
    var r io.Reader // r是一个io.Reader接口类型的实例变量
    
    • 这些类型为接口类型的变量被称为接口类型变量,如果没有被显式赋予初值,接口类型变量的默认值为 nil。
    • 如果要为接口类型变量显式赋予初值,我们就要为接口类型变量选择合法的右值。
    • 如果一个类型 T 的方法集合是某接口类型 I 的方法集合的等价集合或超集,我们就说类型 T 实现了接口类型 I,那么类型 T 的变量就可以作为合法的右值赋值给接口类型 I 的变量。
  • Go 语言还支持接口类型变量赋值的“逆操作”,也就是通过接口类型变量“还原”它的右值的类型与值信息,这个过程被称为“类型断言(Type Assertion)”。
    1 v, ok := i.(T)
    
    • 其中 i 是某一个接口类型变量,如果 T 是一个非接口类型且 T 是想要还原的类型,那么这句代码的含义就是断言存储在接口类型变量 i 中的值的类型为 T。
    • 如果接口类型变量 i 之前被赋予的值确为 T 类型的值,那么这个语句执行后,左侧“comma, ok”语句中的变量 ok 的值将为 true,变量 v 的类型为 T,它值会是之前变量 i 的右值。
    • 如果 i 之前被赋予的值不是 T 类型的值,那么这个语句执行后,变量 ok 的值为 false,变量 v 的类型还是那个要还原的类型,但它的值是类型 T 的零值。
  • 类型断言也支持下面这种语法形式:1 v := i.(T)
    • 但在这种形式下,一旦接口变量 i 之前被赋予的值不是 T 类型的值,那么这个语句将抛出 panic。
    • 如果变量 i 被赋予的值是 T 类型的值,那么变量 v 的类型为 T,它的值就会是之前变量 i 的右值。由于可能出现 panic,所以我们并不推荐使用这种类型断言的语法形式。
  • 接口类型的背后,是通过把类型的行为抽象成契约,建立双方共同遵守的约定,这种契约将双方的耦合降到了最低的程度。
    • 隐式契约,无需签署,自动生效
      • Go 语言中接口类型与它的实现者之间的关系是隐式的,不需要像其他语言(比如 Java)那样要求实现者显式放置“implements”进行修饰,实现者只需要实现接口方法集合中的
        全部方法便算是遵守了契约,并立即生效了。
    • 更倾向于“小契约
      • Go 选择了使用“小契约”,表现在代码上就是尽量定义小接口,即方法个数在 1~3 个之间的接口。
        • 接口越小,抽象程度越高。
        • 小接口易于实现和测试。
        • 小接口表示的“契约”职责单一,易于复用组合。

接口的静态特性与动态特性

  • 接口的静态特性体现在接口类型变量具有静态类型,比如 var err error中变量 err 的静态类型为 error。拥有静态类型,那就意味着编译器会在编译阶段对所有接口类型变量的赋值操作进行类型检查,编译器会检查右值的类型是否实现了该接口方法集合中的所有方法。
  • 接口的动态特性,就体现在接口类型变量在运行时还存储了右值的真实类型信息,这个右值的真实类型被称为接口类型变量的动态类型。
    • 首先,接口类型变量在程序运行时可以被赋值为不同的动态类型变量,每次赋值后,接口类型变量中存储的动态类型信息都会发生变化,这让 Go 语言可以像动态语言(比如 Python)那样拥有使用 Duck Typing(鸭子类型)的灵活性。
    • 所谓鸭子类型,就是指某类型所表现出的特性(比如是否可以作为某接口类型的右值),不是由其基因(比如 C++ 中的父类)决定的,而是由类型所表现出来的行为(比如类型拥有的方法)决定的。

接口类型变量的内部表示

  • 在运行时层面,接口类型变量有两种内部表示:iface和eface,这两种表示分别用于不同的接口类型变量:
    // $GOROOT/src/runtime/runtime2.go
    type iface struct {tab *itabdata unsafe.Pointer
    } 
    type eface struct {_type *_typedata unsafe.Pointer
    }
    
    • eface 用于表示没有方法的空接口(empty interface)类型变量,也就是 interface{} 类型的变量;
    • iface 用于表示其余拥有方法的接口 interface 类型变量。
    • 这两个结构的共同点是它们都有两个指针字段,并且第二个指针字段的功能相同,都是指向当前赋值给该接口类型变量的动态类型变量的值。
    • 那它们的不同点在哪呢?
      • eface 表示的空接口类型并没有方法列表,因此它的第一个指针字段指向一个 _type 类型结构,这个结构为该接口类型变量的动态类型的信息:
        // $GOROOT/src/runtime/type.go
        type _type struct {size uintptrptrdata uintptr // size of memory prefix holding all pointershash uint32tflag tflagalign uint8fieldAlign uint8kind uint8// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal func(unsafe.Pointer, unsafe.Pointer) bool// gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata *bytestr nameOffptrToThis typeOff
        }
        
      • iface 除了要存储动态类型信息之外,还要存储接口本身的信息(接口的类型信息、方法列表信息等)以及动态类型所实现的方法的信息,因此 iface 的第一个字段指向一个 itab类型结构。
        // $GOROOT/src/runtime/runtime2.go
        type itab struct {inter *interfacetype_type *_typehash uint32 // copy of _type.hash. Used for type switches._ [4]bytefun [1]uintptr // variable sized. fun[0]==0 means _type does not impleme
        }
        
        • itab 结构中的第一个字段 inter 指向 interfacetype 结构,存储着这个接口类型自身的信息。
        • 这个 interfacetype 结构由类型信息(typ)、包路径名(pkgpath)和接口方法集合切片(mhdr)组成。
          // $GOROOT/src/runtime/type.go
          type interfacetype struct {typ _typepkgpath namemhdr []imethod
          }
          
        • itab 结构中的字段 _type 则存储着这个接口类型变量的动态类型的信息,字段 fun 则是动态类型已实现的接口方法的调用地址数。
    • 对于空接口类型变量,只有 _type 和 data 所指数据内容一致的情况下,两个空接口类型变量之间才能划等号。
  • Go 在进行空接口类型变量和非空接口类型变量的等值比较时,类型比较使用的是 eface 的 _type 和 iface 的 tab._type。
  • 接口类型变量在运行时表示为 eface 和 iface,eface 用于表示空接口类型变量,iface 用于表示非空接口类型变量。只有两个接口类型变量的类型信息(eface._type/iface.tab._type)相同,且数据指针(eface.data/iface.data)所指数据相同时,两个接口类型变量才是相等的。

接口类型的装箱(boxing)原理

  • 装箱(boxing)是编程语言领域的一个基础概念,一般是指把一个值类型转换成引用类型。
  • 在 Go 语言中,将任意类型赋值给一个接口类型变量也是装箱操作。接口类型的装箱实际就是创建一个 eface 或 iface 的过程。
  • 编译器知道每个要转换为接口类型变量(toType)和动态类型变量的类型(fromType),它会根据这一对类型选择适当的 convT2X 函数,并在生成代码时使用选出的 convT2X 函数参与装箱操作。
    • 不过,装箱是一个有性能损耗的操作,因此 Go 也在不断对装箱操作进行优化,包括对常见类型如整型、字符串、切片等提供系列快速转换函数。
    • Go 建立了 staticuint64s 区域,对 255 以内的小整数值进行装箱操作时不再分配新内存,而是利用 staticuint64s 区域的内存空间。

一切皆组合

  • 如果 C++ 和 Java 是关于类型层次结构和类型分类的语言,那么 Go 则是关于组合的语言。
  • 构建 Go 应用程序的静态骨架结构有两种主要的组合方式:
    在这里插入图片描述
  • 垂直组合
    • Go 语言通过类型的组合而不是继承让单一类型承载更多的功能。由于这种方式与硬件配置升级的垂直扩展很类似,所以这里我们叫它垂直组合。
    • 垂直组合更多应用在新类型的定义方面。通过这种垂直组合,我们可以达到方法实现的复用、接口定义重用等目的。
    • 在实现层面,Go 语言通过类型嵌入(Type Embedding)实现垂直组合,组合方式主要有以下这么几种。
      • 通过嵌入接口构建接口。
      • 通过嵌入接口构建结构体类型。
      • 通过嵌入结构体类型构建新结构体类型。
  • 水平组合
    • 接口可以将各个类型水平组合(连接)在一起。通过接口的编织,整个应用程序不再是一个个孤立的“器官”,而是一幅完整的、有灵活性和扩展性的静态骨架结构。

接口应用的几种模式

  • 通过接口进行水平组合的基本模式就是:使用接受接口类型参数的函数或方法。
  • 基本模式
    • 接受接口类型参数的函数或方法是水平组合的基本语法,形式是这样的:func YourFuncName(param YourInterfaceType)
    • 函数 / 方法参数中的接口类型作为“关节(连接点)”,支持将位于多个包中的多个类型与 YourFuncName 函数连接到一起,共同实现某一新特性。
  • 创建模式
    • Go 社区流传一个经验法则:“接受接口,返回结构体(Accept interfaces, return structs)”,这其实就是一种把接口作为“关节”的应用模式。
    • 这里把它叫做创建模式,是因为这个经验法则多用于创建某一结构体类型的实例。
  • 包装器模式
    • 在基本模式的基础上,当返回值的类型与参数类型相同时,我们能得到下面形式的函数原型:func YourWrapperFunc(param YourInterfaceType) YourInterfaceType
    • 通过这个函数,我们可以实现对输入参数的类型的包装,并在不改变被包装类型(输入参数类型)的定义的情况下,返回具备新功能特性的、实现相同接口类型的新类型。
    • 这种接口应用模式我们叫它包装器模式,也叫装饰器模式。
    • 包装器多用于对输入数据的过滤、变换等操作。
  • 适配器模式
    • 适配器模式的核心是适配器函数类型(Adapter Function Type)。
    • 适配器函数类型是一个辅助水平组合实现的“工具”类型。
    • 它可以将一个满足特定函数签名的普通函数,显式转换成自身类型的实例,转换后的实例同时也是某个接口类型的实现者。
  • 中间件(Middleware)
    • 中间件就是包装模式和适配器模式结合的产物。
    • 尽量避免使用空接口作为函数参数类型。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探索KVM虚拟化技术:全面掌握虚拟机的创建与管理

文章目录 安装KVM开启cpu虚拟化安装KVM检查环境是否正常 KVM图形化创建虚拟机上传ISO创建虚拟机加载镜像配置内存添加磁盘能否手工指定存储路径呢?创建成功安装完成查看虚拟机 KVM命令行创建虚拟机创建磁盘通过命令行创建虚拟机手动安装虚拟机 KVM命令行创建虚拟机-…

攻防世界-WEB-ics-05

打开靶机 只有设备维护中心可以点开 点标签得到新的url pageindex 想到文件包含漏洞(URL中出现path、dir、file、pag、page、archive、p、eng、语言文件等相关关键字眼 利用php伪协议查看源码 出现一段base64源码,进行转码得出源码 ?pagephp://filter…

驱动开发--day2

实现三盏灯的控制,编写应用程序测试 head.h #ifndef __HEAD_H__ #define __HEAD_H__#define LED1_MODER 0X50006000 #define LED1_ODR 0X50006014 #define LED1_RCC 0X50000A28#define LED2_MODER 0X50007000 #define LED2_ODR 0X50007014#endif mychrdev.c #inc…

LeetCode刷题笔记【30】:动态规划专题-2(不同路径、不同路径 II)

文章目录 前置知识62.不同路径题目描述解题思路代码 63. 不同路径 II题目描述障碍信息传递法(比较复杂)被障碍物阻挡后直接清空计数法(更简洁) 总结 前置知识 参考前文 参考文章: LeetCode刷题笔记【29】:动态规划专题-1(斐波那契数、爬楼梯…

【深入理解Linux内核锁】七、互斥体

我的圈子: 高级工程师聚集地 我是董哥,高级嵌入式软件开发工程师,从事嵌入式Linux驱动开发和系统开发,曾就职于世界500强企业! 创作理念:专注分享高质量嵌入式文章,让大家读有所得! 文章目录 1、互斥体API2、API实现2.1 mutex2.2 mutex_init2.3 mutex_lock2.4 mutex_un…

2023外贸SEO推广怎么做?

答案是:2023外贸SEO推广可以选择谷歌SEO谷歌Ads双向运营。 外贸SEO的核心要素 外贸SEO不仅仅是关于关键词排名,它更多的是关于品牌建设和目标受众的吸引。 要想成功,必须认识到几个关键要素。 了解目标市场 首先,要深入了解目…

vue3+ts+vite项目引入echarts,vue3项目echarts组件封装

概述 技术栈:Vue3 Ts Vite Echarts 简介: 图文详解,教你如何在Vue3项目中引入Echarts,封装Echarts组件,并实现常用Echarts图例 文章目录 概述一、先看效果1.1 静态效果1.2 动态效果 二、话不多数,引入 …

redis持久化

Redis高可用 在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。 但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供…

ARM的异常处理

概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序 异常处理机制 不同的处理器对异常的处理的流程大体相似,但是不同的处理器…

面试如何回答弹性盒子布局这个问题呢?

在我们面试中如果被问道css方面的面试题 那么极有可能被问到的一道面试题就是弹性盒子,本篇文章通过一张图带你拿捏这道面试题。 1、首先需要说一说弹性盒子的基本概念:弹性盒子是一种用于网页布局中创建灵活和响应式设计的CSS布局模型。 2、其次需要说…

IDEA找不到Maven窗口

有时候导入项目或者创建项目时候Maven窗口找不到了 然后指定项目的pom.xml文件

力扣(LeetCode)算法_C++——有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图) …

JMeter压力测试 5分钟让你学会如何并发压测接口

文章目录 地址下载启动 使用 地址 JMeter官网下载:https://jmeter.apache.org/download_jmeter.cgi 下载 最新款的jmeter需要java8的支持,请自行安装jdk8或以上的版本 根据系统不同系统下载zip或者是tgz格式的压缩包,并解压,博…

错误: 找不到或无法加载主类 Main

在用git回退到上个版本后发现,无法运行项目并提示 错误: 找不到或无法加载主类 Main 可以看到Main前面的图标也是号。 查了半天没有解决,问了个大佬,大佬一下就解决掉了,本文记录下解决过程。 错误原因是编辑器无法找到代码位置&…

D361周赛复盘:模拟分割整数⭐+变为整除的最小次数⭐

文章目录 2843.统计对称整数的数目(模拟,分割整数为两部分)思路1.整数换成字符串版本2.直接用整数的版本 2844.生成特殊数字的最小操作(模拟,x能被Num整除的条件)思路完整版 2843.统计对称整数的数目(模拟,…

能直接运营的发接任务平台小程序搭建开发演示

有个项目估计做过互联网的小伙伴都听说过——发接任务平台。 基本每年都有发接任务平台关站,但又有新的平台出来,往复循环,无比热闹。这在互联网圈不常见,互联网项目很多都是风头过去了就结束了,但发接任务年年似乎都…

【django】Forbidden (CSRF cookie not set.)

CSRF 表示django全局发送post请求均需要字符串验证 功能: 防止跨站请求伪造的功能 工作原理: 客户端访问服务器端,在服务器端正常返回给客户端数据的时候,而外返回给客户端一段字符串,等到客户端下次访问服务器端时…

云计算在智能制造中的应用与前景

文章目录 云计算的基本概念智能制造的基本概念云计算在智能制造中的应用1. 数据存储和管理2. 大数据分析3. 机器学习和预测维护4. 跨地理分布的协作5. 资源弹性和成本优化 未来前景1. 智能工厂2. 预测性维护3. 定制化生产4. 绿色生产5. 全球制造协作 结论 🎉欢迎来到…

JavaWeb | 常用的HTML(JavaWeb)标签

目录: HTML简介HTML的基本结构HTML的常用标签:“标题” 标签“换行” 标签“段落” 标签“水平线” 标签“文字” 标签“粗体” 标签“下划线” 标签“斜体” 标签“上标” 标签“下标” 标签“闪烁” 标签表示 “空格”“列表” 标签:无序列…

mojo初体验

目录标题 mojo初体验试用地址变量定义参数可变性和所有权Structures后续 mojo初体验 试用地址 https://www.modular.com/get-started 与python基础语法很相似。 变量定义 let定义不可变变量var定义可变变量 参数可变性和所有权 下面是一个基本的函数: fn add…