《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.hqwc.cn/news/92148.html

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

相关文章

香港服务器快还是台湾服务器快?

​  基于机房位置不同,香港服务器相对于台湾服务器在访问速度方面有一定的优势。香港服务器拥有CN2线路,因此访问速度较快。在网络服务商方面,中华电信等台湾服务商提供的带宽也具有很高的性价比。 香港服务器对大陆用户的影响 对于大陆用户…

【docker】docker的一些常用命令-------从小白到大神之路之学习运维第92天

目录 一、安装docker-ce 1、从阿里云下载docker-cer.epo源 2、下载部分依赖 3、安装docker 二、启用docker 1、启动docker和不启动查看docker version 2、启动服务查看docker version 有什么区别?看到了吗? 3、看看docker启动后的镜像仓库都有什…

计算机毕设之基于python+echarts+mysql的图书馆可视化管理系统(文档+代码+部署教程)

系统阐述的是一款图书馆可视化管理系统的设计与实现,对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计,描述,实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体架构…

Flutter的未来与趋势,23年还学吗?

随着移动应用市场的不断扩大,跨平台开发框架的需求也越来越大。Flutter框架可以帮助开发者在不同平台上快速开发高质量的移动应用程序,这种趋势将进一步推动Flutter的发展和普及。 作为一名前端开发工程师,学习Flutter框架是非常有必要的。因…

财务数据分析?奥威BI数据可视化工具很擅长

BI数据可视化工具通常是可以用户各行各业,用于不同主题的数据可视化分析,但面对财务数据分析这块难啃的骨头,能够好好地完成的,还真不多。接下来要介绍的这款BI数据可视化工具不仅拥有内存行列计算模型这样的智能财务指标计算功能…

可拖拽编辑的流程图X6

先上图 //index.html&#xff0c;有时候可能加载失败&#xff0c;那就再找一个别的cdn 或者npm下载&#xff0c;如果npm下载&#xff0c; //那么需要全局引入或者局部引入&#xff0c;代码里面写法也会不同&#xff0c;详细的可以看示例<script src"https://cdn.jsdeli…

【SpringSecurity】九、Base64与JWT

文章目录 1、base64编码2、Base64Url3、JWT的产生背景4、JWT介绍5、JWT组成5.1 Header5.2 Payload5.3 Signature 6、JWT的使用方式7、JWT的几个特点 1、base64编码 base64是一种编码方式&#xff0c;不是加密方式。 所谓Base64&#xff0c;就是说选出64个字符&#xff1a;小写…

C# Winfrom通过COM接口访问和控制Excel应用程序,将Excel数据导入DataGridView

1.首先要创建xlsx文件 2.在Com中添加引用 3. 添加命名空间 using ApExcel Microsoft.Office.Interop.Excel; --这样起个名字方面后面写 4.样例 //点击操作excelDataTable dt new DataTable();string fileName "D:\desktop\tmp\test.xlsx";ApExcel.Application exA…

Vue2里监听localstorage里值的变化

有的时候,我们需要根据本地缓存在localstorage里值的变化做出相应的操作,这就需要我们监听localstorage: 首先,我们在src下的libs文件夹下新建一个stroage.js用于重写setItem事件,当使用setItem的时候,触发,window.dispatchEvent派发事件 const Stroage = {// 重写set…

Vue在表格中拿到该行信息的方式(作用域插槽-#default-scope-解决按钮与行点击的顺序问题)

遇到的问题 在做表格的时候&#xff0c;表格是封装好了的&#xff0c;用于展示数据。如果想给单行增加按钮&#xff0c;可以单独写一列存放按钮&#xff0c;最基本的需求是&#xff0c;点击按钮后要拿到数据然后发起请求。 且Vue的element-plus&#xff0c;当我们点击按钮之后…

OCR文字检测与识别系统:融合文字检测、文字识别和方向分类器的综合解决方案

1. PP-OCR系统简介与总览 前两章主要介绍了DBNet文字检测算法以及CRNN文字识别算法。然而对于我们实际场景中的一张图像&#xff0c;想要单独基于文字检测或者识别模型&#xff0c;是无法同时获取文字位置与文字内容的&#xff0c;因此&#xff0c;我们将文字检测算法以及文字…

2023最新 Electron.js 桌面应用开发教程(基础篇)更新中

Electron是什么&#xff1f; Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 嵌入 Chromium 和 Node.js 到 二进制的 Electron 允许您保持一个 JavaScript 代码代码库并创建 在Windows上运行的跨平台应用 macOS和Linux Electron Fiddle 运行实例 Ele…