1 接口

1.1 接口介绍

接口(interface)是Go语言中核心部分,Go语言提供面向接口编程,那么接口是什么?

现实生活中,有许多接口的例子,比如说电子设备上的充电接口,这个充电接口能干什么,在接口设计时就定义好了,比如说这个接口既能充电可以进行数据的传输;之后只需电子设备是实现这个接口的功能,就像手机上的Type-C接口既可以充电又可以数据传输。

在Golang中接口(interface)是一种类型,一种抽象的类型。在接口类型中可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。对于某个自定义类型可以使用这个接口,但是必须实现这个接口中定义的所有方法。从这点来看,接口不关心事物的属性,只关心事物具有的行为。

1.2 为什么要使用接口

type Cat struct{}

func (c Cat) Say() string { return "喵喵喵" }

type Dog struct{}

func (d Dog) Say() string { return "汪汪汪" }

func main() {
c := Cat{}
fmt.Println("猫:", c.Say())
d := Dog{}
fmt.Println("狗:", d.Say())
}

上面的代码中定义二楼猫和狗,它们都会叫,都有Say()这个方法,你会发现main函数中明显有重复的代码,无果后续再有其他动物的话,这样的代码还会一直重复下去。那我们能不能这样考虑问题呢,把这些动物归结成“能叫的动物“,只是不同的动物有不同的叫法。

像类似的例子在编程中经常遇到:

比如一个网上商城可能使用支付宝、微信、银联等方式在线支付,那能不能把它们当成“支付方式“来处理呢?

再比如三角形、四边形、圆形都能计算周长和面积,那能不能把它们当成“图形”来处理呢?

对于上面的这些问题,Go语言中提供了接口类型。当看到一个接口类型的值是,我们不知道它是什么,唯一知道的是通过它的方法能做什么。

1.3 基本语法

type 接口名 interface {
method1(参数列表) 返回值列表
method2(参数列表) 返回值列表
} //实现接口所有方法
func (t 自定义类型) method1(参数列表) 返回值列表 {
//方法实现
}
func (t 自定义类型) method2(参数列表) 返回值列表 {
//方法实现
}

接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低耦合的思想。

Golang中的皆苦,不需要显式的实现,只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。

1.4 接口的使用案例

对于1.2中的示例,使用接口实现:

//定义一个Sayer的接口
type Sayer interface {
Say()
} //定义两个结构体Cat和Dog
type Cat struct {}
type Dog struct {} // Dog实现了Sayer接口
func (d Dog) Say() {
fmt.Println("汪汪")
} // Cat实现了Sayer接口
func (c cat) Say() {
fmt.Println("喵喵")
}

那实现了接口有什么用?

接口类型变量能够存储所有实现了该接口的实例。例如上面的示例中,Sayer类型的变量能够存储DogCat类型的变量。

func main() {
var x Sayer //声明一个Sayer类型的变量x
a := Cat{}
b := Dog{}
x = a //只有自定义类型实现了某个接口,才能将自定义类型的变量赋值给接口类型变量
x.Say()
x = b
x.Say()
}

1.5 值类型接收者和指针接收者实现接口的区别

通过下面的例子来看下二者的区别:

//声明一个Mover的接口
type Mover interface {
move()
} //声明一个dog的结构体
type dog struct {}

1.5.1 值类型接收者实现接口

值类型接收者dog实现Mover接口:

func (d dog) move() {
fmt.Println("狗会动")
} func main() {
var x Mover
var dog1 = dog{}
x = dog1 var dog2 = &dog{}
x = dog2 //x可以接收*dog类型
x.move
}

从上面的代码可以看出,对于struct如果接收者是值类型,不管是结构体还是结构体指针类型的变量,只要这个结构体实现对应的接口,那么这两个变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,*dog2等价于dog2

1.5.2 指针类型接收者实现接口

指针类型的接收者*dog实现接口:

func (d *dog) move() {
fmt.Println("狗会动")
} func main() {
var x Mover
var dog1 = dog{}
x = dog1 //x不可以接收dog类型,会报dog类型没有实现Mover接口 var dog2 = &dog{}
x = dog2 //x可以接收*dog类型
x.move
}

从上面的示例可知,如果struct的接收者是指针类型,只能将结构体指针赋值给接口类型的变量。

1.5.3 面试题

下面代码能否编译?为什么?

type People interface {
Speak(string) string
} type Student struct{} func (stu *Student) Speak(think string) (talk string) {
if think == "sb" {
talk = ""
} else {
talk = "您好"
}
return
} func main() {
var peo People = Student{}
think := ""
fmt.Println(peo.Speak(think))
}

1.6 空接口

1.6.1 空接口的定义

空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口。

空接口类型的变量可以存储任意类型的变量:

func main() {
// 定义一个空接口x
var x interface{}
s := "Hello viktor"
x = s
fmt.Printf("type:%T value:%v\n", x, x)
i := 100
x = i
fmt.Printf("type:%T value:%v\n", x, x)
b := true
x = b
fmt.Printf("type:%T value:%v\n", x, x)
}

1.6.2 空接口的应用

使用空接口可以接收任意类型的函数参数:

// 空接口作为函数参数
func show(a interface{}) {
fmt.Printf("type:%T value:%v\n", a, a)
}

使用空接口可以保存任意值的字典:

// 空接口作为map值
var studentInfo = make(map[string]interface{})
studentInfo["name"] = "viktor"
studentInfo["age"] = 18
studentInfo["married"] = false
fmt.Println(studentInfo)

1.7 接口总结及注意事项

  • 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(参考1.5中的示例);

  • 接口中的所有的方法都没有方法体,即都是没有实现的方法;

  • 在Golang中,一个自定义类型需要将某个接口的所有方法都实现,则称这个自定义类型实现了该接口;

  • 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型;

  • 一个自定义类型可以实现多个接口;

  • Golang接口中不能有任何变量;

  • 一个接口(比如A接口)可以嵌套多个别的接口(比如B,C接口),这时如果要实现A接口,必须将B,C接口的方法也全部实现;

// Sayer 接口
type Sayer interface {
say()
} // Mover 接口
type Mover interface {
move()
} // 接口嵌套
type animal interface {
Sayer
Mover
} //cat结构体实现animal接口
type cat struct {
name string
} func (c cat) say() {
fmt.Println("喵喵喵")
} func (c cat) move() {
fmt.Println("猫会动")
} func main() {
var x animal
x = cat{name: "花花"}
x.move()
x.say()
}
  • interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么就会输出nil

  • 空接口interface{}没有任何方法,所以所有类型都 实现了空接口,即我们可以把任何一个变量赋给空接口。

2 接口 vs 继承

从一个事物的角度来看,比如一个篮球运动员或者大学生:

篮球运动员或者大学生可以分别继承运动员或者学生的一些属性;但是,篮球运动员或者大学生有可能会有一些相同的行为,比如会说英语,那么就可以定义一个会说英语的接口,分别让二者实现接口。

接口和继承的区别:

  • 接口和继承解决的问题不同:继承的价值主要在于,解决代码的复用性和可维护性;接口的价值主要在于,设计,设计好各种规范(方法),让其它自定义类型去实现这些方法;

  • 接口比继承更加灵活。继承是满足is - a关系,而接口只需满足like - a的关系;

  • 接口在一定程度上实现代码解耦。

3 面向对象编程-多态

3.1 基本介绍

变量(实例)具有多种形态。面向对象的第三大特征,在Go语言中,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

3.2 快速入门案例

//声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
Start()
Stop()
} type Phone struct { } //让Phone 实现 Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作。。。")
} type Camera struct { }
//让Camera 实现 Usb接口的方法
func (c Camera) Start() {
fmt.Println("相机开始工作~~~。。。")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作。。。")
} //计算机
type Computer struct { } //编写一个方法Working 方法,接收一个Usb接口类型变量
//只要是实现了 Usb接口 (所谓实现Usb接口,就是指实现了 Usb接口声明所有方法)
func (c Computer) Working(usb Usb) {//通过usb接口变量来调用Start和Stop方法
usb.Start()
usb.Stop()
} func main() {
//测试
//先创建结构体变量
computer := Computer{}
phone := Phone{}
camera := Camera{} //关键点
computer.Working(phone)
computer.Working(camera)
}

在上面的代码中,Working(usb Usb)方法,既可以接收手机变量,又可以接收相机变量,就体现了Usb接口多态的特性

3.3 接口体现多态的两种形式

多态参数

在一个函数或者是一个方法的参数如果是一个接口类型,那么该参数可以接收实现了该接口的所有的自定义类型。如3.2中的案例。

多态数组

自定义类型只要实现了接口,那么都可以存放在接口的数组中。看如下案例:

package main
import (
"fmt"
) //声明/定义一个接口
type Usb interface {
//声明了两个没有实现的方法
Start()
Stop()
} type Phone struct {
name string
} //让Phone 实现 Usb接口的方法
func (p Phone) Start() {
fmt.Println("手机开始工作。。。")
}
func (p Phone) Stop() {
fmt.Println("手机停止工作。。。")
} type Camera struct {
name string
}
//让Camera 实现 Usb接口的方法
func (c Camera) Start() {
fmt.Println("相机开始工作。。。")
}
func (c Camera) Stop() {
fmt.Println("相机停止工作。。。")
} func main() {
//定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
//这里就体现出多态数组
var usbArr [3]Usb
usbArr[0] = Phone{"vivo"}
usbArr[1] = Phone{"小米"}
usbArr[2] = Camera{"尼康"} fmt.Println(usbArr) }

4 类型断言

4.1 基本介绍

在前面的所有示例中,都是将一个变量(示例)赋值给一个接口。由于一个接口可以被多个自定义类型实现,我们都知道在Go语言中不同类型之前是不能赋值的,此时与这样的一个需求,将接口类型的变量赋值给自定义类型的变量,比如下面的代码,该如何实现?

type Point struct {
x, y int
} func main() {
var a interface{}
var point Point = Point{1,2}
a = point //ok
//如何将a赋给一个Point变量?
var b Point
b = a //?
fmt.Println(b)
}

类比:可以将接口理解成一个很大的容器,当把一个自定义类型赋给接口,就相当于把它放入这个大容器里面,由于这个容器里面可以放很多不同的自定义类型,当想要把刚才的那个放入容器里面的自定义类型赋给其它的自定义类型,就需要先找到它。这个找的过程就是类型断言。

类型断言的基本语法:

x.(T)

其中:

  • x:表示类型为interface{}的变量

  • T:表示断言x可能是的类型

该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

对上面代码的改进:

type Point struct {
x, y int
} func main() {
var a interface{}
var point Point = Point{1,2}
a = point //ok
//如何将a赋给一个Point变量?
var b Point
// b = a //?
b = a.(Point) //类型断言,表示判断a是否指向Point类型的变量,如果是就转成Point类型并赋给b变量,否则报错
fmt.Println(b)
}

4.2 类型断言的使用

类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,具体如下:

func main() {
var x interface{}
var b float32 = 6.6
x = b2 //空接口,可以接收任意类型
//x -> flaot32 [使用类型断言]
y := x.(float32)
fmt.Printf("y的类型是 %T 值是%v\n", y, y)
}

对于上面代码,在进行类型断言时,如果类型不匹配,就会报panic,因此进行类型短延时,要确保原来的空接口指向的就是断言的类型。

断言时也可以带上检测机制,如下示例 :

func main() {
var x interface{}
var b2 float32 = 2.1
x = b2 //空接口,可以接收任意类型
// x=>float32 [使用类型断言] //类型断言(带检测的)
if y, ok := x.(float32); ok {
fmt.Println("convert success")
fmt.Printf("y 的类型是 %T 值是=%v", y, y)
} else {
fmt.Println("convert fail")
}
fmt.Println("继续执行...")
}

4.3 类型断言的最佳实践

循环判断传入参数的类型:

//编写一个函数,可以判断输入的参数是什么类型
func TypeJudge(items... interface{}) {
for index, x := range items {
switch x.(type) {
case bool :
fmt.Printf("第%v个参数是 bool 类型,值是%v\n", index, x)
case float32 :
fmt.Printf("第%v个参数是 float32 类型,值是%v\n", index, x)
case float64 :
fmt.Printf("第%v个参数是 float64 类型,值是%v\n", index, x)
case int, int32, int64 :
fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)
case string :
fmt.Printf("第%v个参数是 string 类型,值是%v\n", index, x)
default :
fmt.Printf("第%v个参数是 类型 不确定,值是%v\n", index, x)
}
}
} func main() {
var n1 float32 = 1.1
var n2 float64 = 2.3
var n3 int32 = 30
var name string = "tom"
address := "北京"
n4 := 300 TypeJudge(n1, n2, n3, name, address, n4, stu1, stu2)
}

Go语言基础之接口(面向对象编程下)的更多相关文章

  1. C# 篇基础知识3——面向对象编程

    面向过程的结构化编程,例如1972年美国贝尔研究所推出的C语言,这类编程方式重点放在在定函数上,将较大任务分解成若干小任务,每个小任务由函数实现,分而治之的思想,然而随着软件规模的不断扩张,软件的复杂 ...

  2. R语言基于S4的面向对象编程

    前言 本文接上一篇文章 R语言基于S3的面向对象编程,本文继续介绍R语言基于S4的面向对象编程. S4对象系统具有明显的结构化特征,更适合面向对象的程序设计.Bioconductor社区,以S4对象系 ...

  3. Java基础教程:面向对象编程[2]

    Java基础教程:面向对象编程[2] 内容大纲 访问修饰符 四种访问修饰符 Java中,可以使用访问控制符来保护对类.变量.方法和构造方法的访问.Java 支持 4 种不同的访问权限. default ...

  4. Java基础教程:面向对象编程[1]

    Java基础教程:面向对象编程 内容大纲 Java语言概述 Java语言特点 1.Java为纯面向对象的语言,它能够直接反映现实生活中的对象.总之,Everything is object! 2.平台 ...

  5. Go语言基础之接口

    Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口介绍 在Go语言中接口(interface)是一种类型,一种抽象的类 ...

  6. Java基础教程:面向对象编程[3]

    Java基础教程:面向对象编程[3] 内容大纲 基础编程 获取用户输入 java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入.我们可以查看Ja ...

  7. GO学习-(14) Go语言基础之接口

    Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口类型 在Go语言中接口(interface)是一种类型,一种抽象的类 ...

  8. [.net 面向对象编程基础] (2) 关于面向对象编程

    [.net 面向对象编程基础]  (2)  关于面向对象编程 首先是,面向对象编程英文 Object-Oriented Programming 简称 OOP 通俗来说,就是 针对对象编程的意思 那么问 ...

  9. Python语言基础07-面向对象编程基础

    本文收录在Python从入门到精通系列文章系列 1. 了解面对对象编程 活在当下的程序员应该都听过"面向对象编程"一词,也经常有人问能不能用一句话解释下什么是"面向对象编 ...

随机推荐

  1. [题解]CSP2019 Solution - Part A

    至于为什么是 \(\text{Part A}\) 而不是 \(\text{Day 1}\) 那是因为 Day1 T3 还没改 (那这六题的 \(\text{solution}\) 就按难度顺序写吧) ...

  2. Java&Eclipse&Maven的折腾

    趁着寒假对于Java的余热,继续了对Java的征途.这次并没有太多琐碎的事情打断我的学习,于是借着这股热情去图书馆借了两本国外的书(中译版),对于程序的爱好一定要坚持下来,才开通了这个博客以坚持学习.

  3. C++封装的基于WinSock2的TCP服务端、客户端

    无聊研究Winsock套接字编程,用原生的C语言接口写出来的代码看着难受,于是自己简单用C++封装一下,把思路过程理清,方便自己后续翻看和新手学习. 只写好了TCP通信服务端,有空把客户端流程也封装一 ...

  4. linux pycharm 安装

    Ubuntu安装之python开发   什么??公司要用Ubuntu(乌班图)?不会用??怎么进行python开发??? 乌班图操作系统下载地址:http://releases.ubuntu.com/ ...

  5. JSTL (标准标签库)

    JSTL(标准标签库) 作用: Web程序员能够利用JSTL和EL来开发Web程序,取代传统直接在页面上嵌入Java程序(Scripting)的做法,以提高程序的阅读性.维护性和方便性. 使用方法:J ...

  6. screen配置窗口显示

    screen的下方不显示,可以复制如下的代码 cd /root && vim .screenrc 贴上如下内容 hardstatus on hardstatus alwayslastl ...

  7. 分析Ajax爬取今日头条街拍美图-崔庆才思路

    站点分析 源码及遇到的问题 代码结构 方法定义 需要的常量 关于在代码中遇到的问题 01. 数据库连接 02.今日头条的反爬虫机制 03. json解码遇到的问题 04. 关于response.tex ...

  8. selenium 多表单切换

    frame/iframe表单嵌套页面的应用.WebDrivr只能在一个页面上对元素识别与定位,对于在frame/iframe表单内嵌页面上的元素无法直接定位.这时需要使用 switch_to.fram ...

  9. vagrant相关

    无法挂载共享目录,报错如下 Vagrant was unable to mount VirtualBox shared folders. This is usually because the fil ...

  10. Altium Designer 14安装破解

    Altium Designer 14简称AD14,是一款专业的PCB设计软件,利用他可以计出专业的PCB元件.Altium Designer 14.3.10是目前的最新版本. Altium Desig ...