结构体和接口
struct
go/types
包中,struct的底层实现。
一个结构体类型包含若干个字段、每个字段都需要有确切的名字和类型。结构体类型也可以不包含任何字段,这并不是没有意义,可以给结构体类型关联上一些方法,把方法看做是函数的特殊版本。
函数是独立的程序实体,可以声明有名字的函数或者匿名函数,还可以把函数当做值传递。可以把具有相同签名的函数抽象成独立的函数类型,作为一组输入、输出的代表。
方法与函数不同,它需要有名字,不能被当做值来看待,最重要的是,它必须隶属于某一个类型。方法所属的类型会通过其声明中的接收者声明体现出来。接收者声明就是在关键字func和方法名之间的圆括号包裹起来的内容,其中必须包含确切的名称和类型字面量。
在Go语言中,通过为一个类型编写名为String()
的方法,来自定义该类型的字符串表示形式。这个String()
方法不需要任何参数声明,但是需要有一个string类型的结果输出。
方法隶属的类型不局限于结构体类型,但必须是某个自定义的数据类型,并且不能是任何接口类型。
一个数据类型关联的所有方法,共同组成了该类型的方法集合
同一个方法集合中的方法不能重名,且不能与该类型中任何字段的名称重复
嵌入字段
面向对象编程的主要原则:将数据及其操作封装在一起。在Go语言中,把结构体类型中的一个字段看作是一项数据,把隶属它的方法看作是附加在其中数据之上的操作。
Go语言规范规定,如果一个字段的声明中只有字段的类型名,没有字段的名称,那么就是一个嵌入字段(也称为匿名字段)。通过此类型变量的名称后跟着“.”,在跟着嵌入字段类型的方式引用到该字段,即嵌入字段的类型既是类型也会名称。例如,下面的a.AnimalCategory
。
选择表达式:在某个代表变量的标识符的右边加上“.”,在加上字段名或方法名,表示选择了某个字段或方法。嵌入字段的方法集合会被无条件地合并进被嵌入类型的方法集合中。
如果Animal也编写自己的String()
方法,那么嵌入字段的String()
会被屏蔽。只要名称相同,无论方法的签名是否一致,嵌入字段的方法都会被屏蔽。
因为嵌入字段的字段和方法都可以“嫁接”到被嵌入类型上,所以即使在两个同名的成员一个是字段,另一个是方法的情况下,屏蔽现象依然存在。通过链式选择表达式选择被屏蔽的嵌入字段的字段或方法。
多层嵌入
屏蔽现象会以嵌入层级为依据,嵌入的层级越深的字段或方法越可能被屏蔽。如,调用Cat的String()
方法:
状态
Cat
Animal
AnimalCategory
1
存在,调用
屏蔽
屏蔽
2
不存在
调用
屏蔽
3
不存在
不存在
调用
根据是否存在String()
方法,来判断嵌入字段的String()
方法是否会被调用或屏蔽。
如果处于同一层级的多个嵌入字段有用同名的字段或方法,那么从被嵌入类型的值那里选择此名称时,会引发编译错,编译器无法确定被选择的成员到底是哪一个。
类型组合
Go语言中不存在继承的概念,它通过嵌入字段的方式实现了类型之间的组合。面向对象编程中的继承,通过牺牲一定的代码简洁性来换取可扩展性,这种可扩展性是通过侵入的方式来实现的。
类型之间的组合采用的是非声明的方式,是非侵入式的,它不会破坏类型的封装或加重类型之间的耦合。我们只是把类型当做字段嵌入进来,然后坐享其成地使用嵌入字段所拥有的一切。同时可以通过“包装”或“屏蔽”的方式来调整或优化嵌入字段。
类型组合非常灵活的通过嵌入字段把一个类型的属性和方法“嫁接”给另一个类型,被嵌入类型自然的实现了嵌入字段所实现的接口。组合比继承更加简洁和清晰,不会有多重继承那样复杂的层次结构和可观的管理成本。接口类型之间也可以组合,以此来扩展接口定义的型号或者标记接口的特征。
值方法与指针方法
方法的接收者类型必须是某个自定义的数据类型,不能是接口类型或者接口的指针类型。
值方法:接收者类型是非指针的自定义数据类型的方法
指针方法:接收者类型是指针类型的方法
取值表达式:
*
放在一个指针值的左边,来获取该指针指向的基本类型值取地址表达式:
&
放在一个可寻址的基本类型值的左边,来获取该基本类型的指针值
值方法与指针方法的区别:
差别
值方法
指针方法
方法接收者
该方法所属的那个类型值的一个副本,值方法内对该副本的修改不会体现在原值上,除非这个类型本身是某个引用类型的别名类型
该方法所属的那个基本类型值的指针的一个副本,指针方法内对该副本的修改会直接体现在原值上
方法集合
自定义数据类型的方法集合中仅包含所有值方法
该类型的指针类型的方法集合包括所有的值方法和指针方法
方法集合的解释:严格来说,基本类型的值上之只能调用它的值方法,但,Go语言会适时地为我们进行自动地转译,使得我们在这样的值上也能调用它的指针方法。例如,
cat.SetName("monster")
会自动转译为(&cat).SetName("monster")
,即先取cat的指针值,然后在指针值上调用指针方法SetName。
一个类型的方法集合中有哪些方法与它能实现哪些接口类型是息息相关的,如果一个基本类型和它的指针类型的方法集合是不同的,那么它们具体实现的接口类型的数量也会有差异,除非两个数量都是零。
interface
接口类型与其他数据类型不同,它没法被实例化。既不能通过调用new()
或make()
函数创建出一个接口类型的值,也无法用字面量来表示一个接口类型的值。
对于某一个接口类型,如果没有任何数据类型可以作为它的实现,那么该接口的值就不可能存在。
接口类型的类型字面量与结构体类型看起来相似,用花括号包裹一些核心信息:
结构体类型:包裹字段声明
接口类型:包裹方法定义
接口类型声明中的方法代表的就是该接口的方法集合,一个接口的方法集合就是它的全部特征。对于任何数据类型,只要它的方法集合中完全包含了一个接口的全部特征,那么它就一定是这个接口的实现类型,如下所示:
判断一个数据类型的某个方法实现的是某个接口类型中的方法:
两个方法的签名要完全一致
两个方法的名称要完全一致
pet的静态类型永远不变,而动态类型会随着赋值的变化而变化。在给一个接口类型的变量赋予动态值之前,它的动态类型是不存在的。
接口变量
如果使用一个变量给另外一个变量赋值,那么真正赋值给后者的,并不是前者持有的那个值,而是该值的一个副本。
接口类型本身是无法被值化的,在赋予接口变量动态值之前,它的值一定是nil
(接口类型的零值)。当给接口变量赋值时,该变量的动态类型和动态值一起被存储在一个专用的数据结构(iface)中。这个接口变量的值其实是这个专用数据结构的实例,而不是赋予给接口变量的那个动态值。所以接口变量与动态值肯定是不同的,无论是存储的内容还是存储的结构都不同。
专用数据结构(iface)实例会包含两个指针,一个是指向类型信息(这里的类型信息有另一个专用数据结构的实例承载,包含动态类型以及使他实现了接口的方法和调用它们的途径)的指针,另一个是指向动态值的指针。
在Go语言中,把字面量为nil表示的值叫做无类型的nil,这是真正的nil,它的类型也是nil。把一个有类型的nil值赋给接口变量,那么这个变量的值一定不会是那个真正的nil。
让接口变量的值为真正的nil的方法:
只声明接口变量但不初始化
直接把字面量nil赋予给接口变量
接口组合
接口类型之间的嵌入称为接口的组合,与结构体类型的组合相比,接口类型组合不会出现方法之间的屏蔽。只要组合的接口之间有同名的方法就会产生冲突,从而无法通过编译,即使同名方法的签名不同也不行。
与结构体组合相似,把接口类型的名称直接写到另一个接口类型的成员类别中,如下所示:
Go语言团队鼓励声明体量较小的接口,并建议通过接口组合来扩展程序、增加程序的灵活性。相比于包含很多方法的大接口,小接口可以专用地表达某一种能力或某一类特征,也更容易被组合在一起。
接口使用场景
在任何一门编程语言中,接口(方法或行为的集合),在功能和该功能的使用者之间构建了一层薄薄的抽象层。在使用接口时,并不需要了解底层函数是如何实现的,因为接口隔离了各个部分。跟不使用接口相比,使用接口的最大好处就是可以使代码变得简洁。
构造函数
很多编程语言都有构造函数。构造函数是定义自定义类型(即,面向对象语言中的类)时使用的一种建立对象的方法,它可以确保必须执行的任何初始化逻辑均已执行。然而Go语言没有构造函数这么一说。约定俗成就是写一个New函数。
在这里是输出的是空字符串,因为实例化w的时候,ID并没有被初始化,那么它就是字符串类型的零值(空字符串),这不是我们想要的结果。可以在widget包中增加New函数来充当构造函数,完成实例化对象时的初始化操作。
这样实例化w的时候,调用New函数就完成了必要的初始化操作,但是与其他编程语言不同,这里的New函数不是一个强制执行操作而只是一个可选操作。进一步修改,Widget修改为包内类型(也就是该位小写widget),那就只能通过New函数来实例化了,虽然编译器不会报错。
其他编程语言中,类是复用的基本单位,而在Go语言中,包是复用的基本单位。任何无法被包外部访问的内容实质上都是私有的,是这个包内部的实现细节,在使用
godoc
工具生成说明文档的时候,不会为私有函数和类型生成文档的。
唯一的解决方案就是使用接口,通过创建一个外部可访问的Widget接口,并使用包内私有的widget类型实现这个接口,New函数返回一个公开的类型实例,同时godoc
工具也能够生成文档。
总结一下就是,我们有一个类型,需要用户在实例化的时候使用该类提供的New函数(构造函数)进行初始化:
第一步将类型私有化,
第二步暴露一个接口类型(包含这个类型的全部方法,这样就实现了这个接口),New函数返回这个接口类型。这样就得到了效果。
最后更新于
这有帮助吗?