《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.mzph.cn/news/60863.shtml

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

相关文章

博士后申请有哪些技巧?

在博士后申请过程中,有一些关键的技巧可以帮助申请者提高成功的机会。以下是知识人网小编的一些建议: 1.精选合适的导师和研究课题:选择与自己研究方向相关且感兴趣的导师和课题非常重要。导师的声誉、研究成果和合作风格都会影响你的博士后经…

Java写作的规范篇(一)

为什么要写下此篇? 首先在我日常的开发中,总是感觉自己在使用一种面向对象的语言然后在面向过程编程。代码中十分不规范,虽然意识到了这个问题。但是想要解决还是需要花费很多的思路去构思如何像写一篇优雅的作文一样写一篇优雅的程序代码。话…

智慧工地源码带开发手册文档 app 数据大屏、硬件对接、萤石云

智慧工地解决方案依托计算机技术、物联网、云计算、大数据、人工智能、VR、AR等技术相结合,为工程项目管理提供先进技术手段,构建工地现场智能监控和控制体系,弥补传统方法在监管中的缺陷,最终实现项目对人、机、料、法、环的全方…

一、设计模式概述-设计模式概述

一、什么是设计模式 软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解…

黑马程序员上课笔记 P2 JAVA 前置课CMD的使用

引入: 一、人机交互 人机交互的故事: 计算机刚出现的时候的三个特点,占地广 造价高 耗电多 故事: 在计算机的历史中,人机交互(Human-Computer Interaction,简称HCI)一直在不断地…

Python:使用Resend发送邮件

官网&#xff1a;https://resend.com/ 很简单&#xff0c;只需调用api接口&#xff0c;即可发送邮件 需要提前准备好参数 api_key 从Resend申请的keyto_email 接收邮件的邮箱地址 import requestsheaders {Authorization: Bearer <api_key>,Content-Type: applicati…

自然语言处理(NLP)技术的例子

以下是几个自然语言处理&#xff08;NLP&#xff09;技术的例子&#xff1a; 机器翻译&#xff1a;机器翻译是将一种自然语言的文本转换成另一种语言的文本的过程。这种技术应用于在线翻译器、多语言聊天机器人、多语言搜索引擎等地方。 文本分类&#xff1a;文本分类将文本分…

ExpressLRS开源之RC链路性能测试

ExpressLRS开源之RC链路性能测试 1. 源由2. 分析3. 测试方案4. 测试设计4.1 校准测试4.2 实验室测试4.3 拉距测试4.4 遮挡测试 5. 总结6. 参考资料 1. 源由 基于ExpressLRS开源基本调试验证方法&#xff0c;对RC链路性能进行简单的性能测试。 修改设计总能够满足合理的需求&a…

CA证书颁发机构服务器

目录 一、CA证书颁发机构是什么&#xff1f; 二、数字证书可以干什么&#xff1f; 三、PKI&#xff1a;即公钥加密体系&#xff08;public key cryptography&#xff09; 四、CA在网络中的工作流程及原理&#xff08;以网站为例&#xff09; 五、HTTPS 的工作原理 六、CA私有证…

xml

1.xml 1.1概述【理解】 万维网联盟(W3C) 万维网联盟(W3C)创建于1994年&#xff0c;又称W3C理事会。1994年10月在麻省理工学院计算机科学实验室成立。 建立者&#xff1a; Tim Berners-Lee (蒂姆伯纳斯李)。 是Web技术领域最具权威和影响力的国际中立性技术标准机构。 到目前为…

哪里可以找到优质的文章?

我认为中外科技内容过去主要是“信息差”&#xff0c;即人们可以直接从国外文章中摄取信息并直接实践&#xff0c;谁快谁赢。 而现在主要是“观点差”&#xff0c;国内科技相关的理论和评论文章的数量和质量都还比较弱。 所以&#xff0c;优质文章建议多找外文。 参考风险投资人…

IBM Spectrum LSF Explorer 为要求苛刻的分布式和任务关键型高性能技术计算环境提供强大的工作负载管理

IBM Spectrum LSF Explorer 适用于 IBM Spectrum LSF 集群的强大、轻量级报告解决方案 亮点 ● 允许不同的业务和技术用户使用单一解决方案快速创建和查看报表和仪表板 ● 利用可扩展的库提供预构建的报告 ● 自定义并生成性能、工作负载和资源使用情况的报…

自动驾驶和辅助驾驶系统的概念性架构(一)

摘要&#xff1a; 本文主要介绍包括功能模块图&#xff0c;涵盖了底层计算单元、示例工作负载和行业标准。 前言 本文档参考自动驾驶计算联盟(Autonomous Vehicle Computing Consortium)关于自动驾驶和辅助驾驶计算系统的概念系统架构。 该架构旨在与SAE L1-L5级别的自动驾驶保…

MySQL之从单机到集群

写在前面 本文一起看下MySQL是单机存在的问题&#xff0c;以及为了解决这些问题所提出的各种解决方案。 1&#xff1a;从单机到集群 并非业务发展初期我们就直接使用集群来支撑业务&#xff0c;而是简单的使用单机版本&#xff0c;但是随着业务的发展&#xff0c;单机的各种…

docker打包部署

打包成容器命令 docker build -f ./Dockerfile-long -t 名称.打包镜像 tar docker save -o 名称.tar 名称:latest执行sudo -i&#xff0c;提示输入用户密码&#xff0c;输入密码后进入超级用户&#xff08;root&#xff09;模式 linux上传文件 rz -ytar恢复成镜像 sudo docker…

计算机网络(速率、宽带、吞吐量、时延、发送时延)

速率&#xff1a; 最重要的一个性能指标。 指的是数据的传送速率&#xff0c;也称为数据率 (data rate) 或比特率 (bit rate)。 单位&#xff1a;bit/s&#xff0c;或 kbit/s、Mbit/s、 Gbit/s 等。 例如 4 1010 bit/s 的数据率就记为 40 Gbit/s。 速率往往是指额定速率或…

https比http安全在哪

HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是HTTP的安全版本&#xff0c;它在HTTP的基础上添加了安全性和加密机制。以下是HTTPS相对于HTTP的主要安全性优势&#xff1a; 数据加密&#xff1a;HTTPS使用TLS&#xff08;Transport Layer Security&#x…

el-table中点击跳转到详情页的两种方法

跳转的两种写法: 1.使用keep-alive使组件缓存,防止刷新时参数丢失 keep-alive 组件用于缓存和保持组件的状态&#xff0c;而不是路由参数。它可以在组件切换时保留组件的状态&#xff0c;从而避免重新渲染和加载数据。 keep-alive 主要用于提高页面性能和用户体验&#xff0c;而…

vue2项目中el-input单独使用max和maxlength不生效问题

vue2项目中el-input单独使用max和maxlength不生效问题 今天在vue2的项目中使用element中的<el-input>组件&#xff0c;因为没有使用form所以max和maxlength属性没有生效&#xff0c;下面是解决办法 <el-input placeholder"请输入" v-model"holeDat…

django-发送邮件

一、业务场景 业务警告 邮箱验证 密码找回 二、邮件相关协议 1.SMYTP&#xff08;简答邮件传输协议 25端口&#xff09; 属于“推送”协议 负责发送 2.IMAP&#xff08;交互式邮件访问协议&#xff0c;应用层协议&#xff0c;143端口&#xff09; 用于从本地邮件客户端…