一、接口
1.1 声明
type Namer interface{
MethodName1(param_list) return_type
MethodName2(param_list) return_type
...
}
1.2 GO 版本的 "多态"
当用实现接口的类型的实例变量赋值给接口变量时,
接口变量包含实例变量的值和指向对应方法表的指针,
此时可用接口变量直接调用接口中的方法集。
多个类型实现同一接口表现出不同行为,这就是 GO 版本中的"多态"。
1.3 接口类型
接口类型实际上是描述了一些列方法的集合,一个实现了这些方法
的具体类型是这个接口类型的实例。
1.3.1 动态类型
1.3.2 类型判断
1.3.2.1 类型断言
if v,ok := varI.(T);ok{
// TO DO
}
1.3.2.2 type-switch
可以使用 type-switch 进行运行时类型分析,
但是 type-switch 不允许有 fallthrough
switch t:= varI.(type){
case *T1:
//TO DO
case * T2:
// TO DO
default:
// TO DO
}
1.4 实现接口
1.4.1 实现
类型实现了接口的方法集就实现了该接口
1.4.2 接口赋值
1.4.2.1 对象实例赋值给接口变量
对象实例所属类型实现接口的方法集
1.4.2.2 接口变量赋值给接口变量
(1)接口方法集完全等价可相互赋值
(2)接口 A 的方法集是接口 B 方法集的子集,B 类型接口变量
可赋值给接口 A 变量
1.4.3 接口查询
查询接口变量指向的对象实例是否实现了某个接口
var field1 Writer = ...
if field2,ok := field1.(T);ok{
// TO DO
}
1.4.4 接口的嵌套、组合
1.4.5 函数作为 GO 第一对象,可通过定义别名使其实现某个接口
在 GO 语言出现之前,接口主要作为不同组件之间的契约存在。对契约的是实现是强制的,开发者必须声明实现该接口。先看下 Java
中是如何定义并是实现一个接口的。
package com.lemon;
//定义一个接口名字为: IFOO
interface IFOO
{
public void playgame();
}
//定义一个 Person 实现了 IFOO 接口
class Person implements IFOO
{
@Override
public void playgame() {
System.out.println("playgame");
}
}
public class test {
public static void main(String[] args) {
//调用
IFOO jack = new Person();
jack.playgame();
}
}
从上面的例子可以看出 类 Person
显示声明自己实现了IFOO
接口,并在其内部实现了 public void playgame()
方法。这类接口有人将其称之为"侵入式"接口,其主要表现在:实现类需要明确且显示声明自己实现了某个接口。而对于 GO 而言,却做了改良,接口被"隐式"实现了,其隐式表现为: 只要类型实现了接口中的全部方法,它就实现了此接口。
package main
import "fmt"
//定义了 IFOO 接口
type IFOO interface {
playgame()
}
//定义接口题,实现了接口 IFOO(并未显示声明)
type Man struct {
}
func (m *Man) playgame() {
fmt.Println("playgame")
}
//调用
func main() {
var jack IFOO
jack = new(Man)
jack.playgame()
}
GO 语言有非常灵活的接口概念,通过它可以实现很多面向对象的特性。接口提供了一种方式来说明对象的行为: 如果谁能搞定这件事,它就可以在这里调用。
一、接口
1.1 声明
接口定义了一组方法(方法集),但是这些方法不包含(实现)代码,它们没有被实现(是抽象的),接口中不能包含变量。定义格式如下:
type Namer interface{
MethodName1(param_list) return_type
MethodName2(param_list) return_type
...
}
约定 :
( 1 ) 只包含一个方法的接口的名字由方法名加 [e]r
后缀组成,例如 :Writer、Reader
( 2 ) 一个接口一般包含 0 ~ 3 个方法
( 3 ) GO 语言中接口可以有值,一个接口类型的变量或者一个接口值,例如:var in Namer
( 4 ) 类型(例如:结构体) 实现接口方法集中的方法,每一个方法的实现说明了此方法是如何作用于该类型:即实现接口,同时方法集也构成了该类型的接口。实现了 Namer
接口的类型的变量可以赋值给 in
(接收者值),此时方法表中的指针会被指向被实现接口的方法。
var jack IFOO
//注意: 接口变量包含了接收者实例的值和指向对应方法表的指针
jack = new(Man)
( 5 ) 类型不需要显示声明它实现了某个接口:接口被隐式实现。
多个类型可以实现同一个接口,一个类型可以实现多个接口。
实现了某个接口的类型还可以有自己的其他方法
( 6 ) 只要类型实现了接口中的方法,它就实现了该接口,即便接口在类型实现之后才定义,二者处于不同包中。
( 7 ) 接口是动态类型
备注: 具体类型实现接口必须要有接收者(方法)。至于接收者是指针类型还是值类型无所谓。如果仅定义跟接口方法集一样的函数集,则该类型未实现接口
1.2 GO 版本的 "多态"
面向对象中的多态: 根据当前类型选择正确的方法,或者说同一类型在不同的实例上表现出不同的行为。
对于 GO 而言,则是 不同类型 实现同一接口表现出不同的行为。
package main
import "fmt"
//定义一个接口
type Shaper interface {
Area() float64
}
//定义一个结构体 Square 并实现 Shaper 接口
type Square struct {
side float64
}
func (receiver *Square) Area() float64 {
return receiver.side * receiver.side
}
//定义一个结构体 Rectangle 并实现 Shaper 接口
type Rectangle struct {
length,width float64
}
func (receiver *Rectangle) Area() float64 {
return receiver.length * receiver.width
}
func main() {
//通过字面量创建结构体实例,方法接收者是指针类型,需要 & (通过 new(T) 则不需要)
square := &Square{
side: 5}
rectangle := &Rectangle{
length: 5,width: 6}
// Square 和 Rectangle 都实现了 Shaper 接口,也是 Shaper 类型
shapers := []Shaper{
square,rectangle}
for index,_ := range shapers{
fmt.Printf("参数:%v \n",shapers[index])
fmt.Printf("面积:%f\n",shapers[index].Area())
}
}
输出结果:
参数:&{
5}
面积:25.000000
参数:&{
5 6}
面积:30.000000
在调用 shapers[index]
时,只知道 shapers[index]
是 一个 Shaper
对象,最后它摇身一变成为了 Square
或 Rectangle
对象,并且表现出相对应的行为。
1.3 接口类型
接口类型实际上是描述一系列方法的集合,一个实现了这些方法的具体类型是这个接口类型的实例。
1.3.1 动态类型
一个接口类型的变量 varI
中可以包含任何类型的值,必须有一种方式来检测它的动态类型,即运行时在变量中存储的值的实际类型。在执行过程中可能会有所不同,但是它一定是可以分配给接口变量的类型。
1.3.2 类型判断
1.3.2.1 类型断言
类型断言是 GO 语言内置的一种智能推断类型的功能来测试某个时刻 varI
是否包含类型 T
的值。一般使用格式如下:
if v,ok := varI.(T);ok{
//TO DO
}
如果转化合法,v
是varI
转换到类型T
的值,ok
会是true
;否则 v
是类型T
的零值,ok
值为 false
接上面的例子基础上举例说明下:
func main() {
var varI Shaper
varI = &Square{
side: 5}
if v,ok := varI.(*Square);ok{
// %T 输出值的类型
fmt.Printf("varI 的类型是:%T \n",v)
//fmt.Println(v.Area())
}
if u,ok := varI.(*Rectangle);ok {
fmt.Printf("varI 的类型是:%T \n",u)
}else {
fmt.Println("varI 不是 Rectangle 类型")
}
}
//输出结果:
varI 的类型是:*main.Square
25
varI 不是 Rectangle 类型
1.3.2.2 type-switch
接口类型的变量也可以使用 type-switch
来检测,但是 type-switch
不允许有 fallthrough
。改造下上面的例子
func main() {
var varI Shaper
varI = &Square{
side: 5}
switch t:= varI.(type) {
case *Square:
fmt.Printf("t 的类型是 %T\n",t)
//fmt.Println(t.Area())
case *Rectangle:
fmt.Printf("t 的类型是 %T\n",t)
default:
fmt.Printf("未知类型:%T\n",t)
}
}
如果仅仅是判断变量的类型,而不使用它的值,那么就可以不需要赋值语句
func main() {
var varI Shaper
varI = &Square{
side: 5}
switch varI.(type) {
case *Square:
fmt.Println("varI 是 Square 类型")
case *Rectangle:
fmt.Println("varI 是 Rectangle 类型")
default:
fmt.Println("varI 是 未知类型")
}
}
1.4 实现接口
1.4.1 实现
一个类型如果拥有一个接口需要的所有方法,那么这个类型就实现了这个接口。 GO 语言的程序员经常会简要地把一个具体类型描述成一个特定的接口类型。
1.4.2 接口赋值
1.4.2.1 将对象实例赋值给接口
将对象的实例赋值给接口变量,这要求对象实例实现了接口要求的所有方法。
我们前面有提到 如何扩展已存在类型的方法
一般有两种方式: 1、定义别名 2、使用组合
demo 回顾下,定义类型别名的方法
package main
import "fmt"
//定义 int 的别名为 Integer
type Integer int
//定义一个接口: 比较大小 和 添加
type LessAdder interface {
Less(b Integer) bool
Adder(b Integer)
}
//接收者类型为值接收者
func (a Integer) Less(b Integer) bool {
return a < b
}
//接收者类型为指针接收者
func (a *Integer) Adder(b Integer) {
*a += b
}
//main 方法
func main() {
var a Integer = 1
var b LessAdder = &a //语句 1
//var b LessAdder = a //语句 2
fmt.Println(b.Less(0))
}
上述代码的main()
方法中,将对象实例赋值给接口变量应该是 语句1 还是语句2 呢?答案 应该是 语句1 。原因在于 GO 语言可以根据函数 func (a Integer) Less(b Integer) bool
自动生成 一个新的Less()
方法
func (a *Integer) Less(b Integer) bool {
return (*a) < b
}
这样 *Integer
既包含了 Less()
方法也包含了Adder()
方法,满足了LessAdder()
接口的要求。从另外一角度来看,根据func (a *Integer) Adder(b Integer)
方法无法自动生成以下这个成员方法
func (a Integer) Adder(b Integer) {
(&a).Add(b)
}
因为 (&a).Add(b)
改变的只是方法参数 a,对外部实际要操作的对象并无影响,这不符合用户预期,所以 GO 不会 自动生成该方法。因此类型 Integer
只存在less()
方法,缺少Adder()
方法,不满足LessAdder
接口,故上面的 语句2 不能被赋值。
如果将其接口去掉Adder(b Integer)
,那么 语句1 和 语句2 都可成立
type LessAdder interface {
Less(b Integer) bool
//Adder(b Integer)
}
//main 方法
func main() {
var a Integer = 1
var b LessAdder = &a //语句 1
var c LessAdder = a //语句 2
fmt.Println(b.Less(0))
fmt.Println(c.Less(2))
}
1.4.2.2 将一个接口赋值给另一个接口
在 GO 语言中,只要两个接口拥有相同的方法列表,那么它们就是等同的,可以相互赋值。接口赋值并不要求两个接口必须等价,如果接口 A 的方法列表 是 B 接口方法列表的子集,那么 接口 B 可以赋值给接口 A。
举个例子
// 分别在两个包中定义 ReaderWriter 和 IStream 接口,方法集一样,顺序不同
package one
type ReaderWriter interface {
Read(buf []byte)(n int,err error)
Writer(buf []byte)(n int,err error)
}
package two
type IStream interface {
Writer(buf []byte)(n int,err error)
Read(buf []byte)(n int,err error)
}
package main
func main() {
var field1 two.IStream = new(os.File)
var field2 one.ReaderWriter = field1
var field3 two.IStream = field2
fmt.Println(filed3)
}
再看下 接口 B 方法集 是 接口 B 方法集的子集的情况
type Writer interface{
Writer(buf []byte)(n int,err error)
}
func main{
var field1 two.IStream = new(os.File)
var field2 Writer = field1
/* 这段代码无法通过编译,原因: field3 没有 Read()方法
var field3 Writer = new(os.File)
var field4 two.IStream = field3
*/
}
1.4.3 接口查询
检查某个接口变量指向的对象实例是否实现了某个接口,如果是实现了,则执行特定的代码。
func main() {
var filed1 io.Writer = new(os.File)
if _,ok := filed1.(two.IStream);ok{
fmt.Println("filed1 实现了 two.IStream 接口")
}else {
fmt.Println("filed1 未实现了 two.IStream 接口")
}
}
1.4.2 接口的嵌套、组合
其实这个只是个写法方法转变而已,简单举个例子说面下
看下 GO 语言包中的 io.Reader()
和 io.Writer()
接口
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
//接口组合
type ReadWriter interface {
Reader
Writer
}
//接口内嵌
type ReadWriter interface {
Reader
Write(p []byte) (n int, err error)
}