接口是面向对象编程实现多态的基操。
在绝大多数情况下,数据可能是不同类型,不同类型却可能存在着一个和多个共通点。
这些共通点就是抽象的基础。
接口的本质就是某一个抽象的共通点,实际类型可能是一对多(一个类型实现多个接口)或多对一(多种类型实现一个接口),这就是多态的体现。
举个例子:
从编程的角度来看,如果调取人这个接口让其执行睡觉方法,最终结果就是老王趴着睡、老张侧着睡。
所以,接口是什么?
接口是一组包含方法名(入参、返回值)的未进行具体实现的方法集。
判断一个类型是否实现了接口,就看该类型是否全部实现了接口所定义的方法。
Go 语言中接口的独特之处在于隐式实现。
具体的类型并不需要声明其实现了哪些接口,实现接口必须的方法即可完成接口的实现。
我们先看看 Java 语言中如何实现一个接口:
// 接口
public interface People {
boolean Sleep();
}
// 实现(类需要显式申明实现接口)
public class PeopleImpl implements People {
@Override
public boolean Sleep() {
System.out.println("睡了!");
return true;
}
}
这里有一个关键字 implements,用来声明实现某接口。
Java 中是类实现接口而不是具体类型。
在 Go 中,不需要声明实现,只需要实现了接口的全部方法,便隐式的实现了接口。
依旧上面那段逻辑,修改成 Go 语言形式去实现。
// 接口
type People interface {
Sleep() bool
}
// 具体类型
type Man struct {
Name string
}
// 类型实现接口方法
func (man Man) Sleep() bool {
fmt.Println("我是" + man.Name + "!我睡了!")
return true
}
上文我们提到,在 Go 中只要实现了接口的全部方法就隐式的实现了接口。
上述代码中,People 接口只有一个 Sleep() 方法,Man 类型实现了该方法,那 Man 类型是否实现了接口 People 呢?
func main() {
// 定义一个接口对象
var p People
// 定义个类型对象
lw := Man{"老王"}
// 将类型赋给接口
p = lw
// 调用接口方法
p.Sleep()
}
输出结果如下:
我是老王!我睡了!
Go 中接口实现并不仅仅是类型直接实现方法,还有指针实现形式。
指针实现的方法尽可以通过指针调用,比如下方代码:
// 具体类型
type Women struct {
Name string
}
// 类型实现接口方法
func (women *Women) Sleep() bool {
fmt.Println("我是" + women.Name + "!我睡了!")
return true
}
func main() {
// 定义一个接口对象
var p People
// 定义个类型对象
lw := Women{"王阿姨"}
// 将类型赋给接口
// 编译报错:Cannot use 'lw' (type Women) as type People Type does not implement 'People' as 'Sleep' method has a pointer receiver
// 提示您 Women 类型并没有实现 People 接口
p = lw
// 将指针赋给接口对象,才能正确实现接口
p = &lw
// 调用接口方法
p.Sleep()
}
可以通过 x.(T) 的方式验证是否已实现,x 只适用于 interface 类型。
// 检查实现
func checkPeople(p interface{}) bool {
if _, ok := p.(People); ok {
return true
}
return false
}
func main() {
// 定义个类型对象
lw := Man{"老王"}
// 判断检查结果
if checkPeople(lw) {
fmt.Println(lw.Name + "是个人!")
} else {
fmt.Println(lw.Name + "不是人!")
}
}
输出结果如下:
老王是个人!
Go 中是可以定义空接口的,比如上文中 checkPeople 方法的入参就是空接口。
因为空接口中不包含任何方法,就意味着任何类型都实现了空接口,所以可以利用空接口存储任意类型的数据。
func main() {
i := 1
s := "string"
var o interface{}
o = i
fmt.Printf("O值:%v
", o)
o = s
fmt.Printf("O值:%v
", o)
}
输出结果如下:
O值:1
O值:string
既然空接口可以接受任意类型的数据,那自然会涉及到如何区分类型的需要。
目前有两种方式去区分:断言、判断。
相当于对上方 x.(T) 的多重判断。
func checkDataType(p interface{}) {
// 类型强转
if _,ok := p.(People); ok {
fmt.Println("TA是个人!")
} else if _,ok := p.(int); ok {
fmt.Println("TA是个数字!")
} else if _,ok := p.(string); ok {
fmt.Println("TA是个字符串儿!")
}
}
将语法改成 switch 本质没什么区别。
func checkDataType(p interface{}) {
// 获取类型
switch v := p.(type) {
case People:
fmt.Printf("TA是个人!%v
", v)
case int:
fmt.Printf("TA是个数字!%v
", v)
case string:
fmt.Printf("TA是个字符串儿!%v
", v)
}
}
嵌套接口有点类似于继承的感觉,当方法很多的时候,我们新建的接口不一定需要写出所有的方法。
type People interface {
Sleep() bool
}
type WorkMan interface {
GoWork() int
}
// 该接口直接嵌套其他接口形成新的接口
// 表示该接口具备所嵌入接口的全部方法
type Technician interface {
People
WorkMan
}
当前还没有观点发布,欢迎您留下足迹!
在 Go 中函数可以接受值传递和指针传递,使用时就涉及到 & 内存地址(指针)与 * 指针赋值的使用,它们的区别是什么?在实际业务使用中,值传递和指针传递的分别应对什么场景需要?针对使用时机进行分析。
GoLand 在保存代码时,可以自动调用 gofmt 和 goimports 实现自动格式化代码,在新版本中可以通过 File Watchers 插件来完成这些配置,配置位置位于File
Go1.11版本开始支持包依赖管理工具,新增了GOPROXY环境变量,用于配置依赖包下载代理,通过代理配置可以实现翻墙下载一些所需的依赖包,可以说相当实用
对于绝大多数新的 Go 项目而言,因为使用 go modules 管理包依赖从而无需要关注工程的目录位置,但是对于一些旧/历史工程在导入 GoLand 之后会出现全面飘红,这个时候就需要逐一排查问题
Go 中比较常见的 int、string、bool、float 基本数据类型之外还有其他的数据类型可以应用在特殊场景,比如 rune 就是类似于 int32,因为其可表示的字符范围更大,实际工作中可以用来计算字符串的真实长度
调试 Go 程序报错 xx is shadowed during return,方法在返回的时候不是预期的返回结果,错误的产生应当是在相同作用域中出现了同名的变量导致,根据实际业务场景进行修改