在 Go 语言中,接口(Interface)是一种强大的类型,它通过隐式实现和鸭子类型(Duck Typing)机制,为多态和代码抽象提供了独特而灵活的解决方案。
鸭子类型(Duck Typing)是一种动态类型风格,它判断一个对象的有效性,不是看它的继承关系或实现的接口,而是看它是否拥有特定方法和属性的集合。这个概念来源于"鸭子测试":"如果它走起路来像鸭子,叫起来也像鸭子,那么它就是一只鸭子。" 换句话说,它关注对象的行为(能做什么),而不是对象的类型。
1、接口定义与隐式实现
Go 语言的接口是一种抽象类型,它定义了一组方法签名的集合,但不包含具体实现。任何类型只要实现了接口中声明的所有方法,就被视为隐式实现了该接口,无需像 Java 那样使用 implements关键字显式声明。
go
// 定义汽车接口
type Car interface {
// 引擎启动
EngineStart() string
// 引擎关闭
TurnOff() string
// 加速
Accelerate() string
// 刹车
Brake() string
// 按喇叭
SoundHorn() string
// 大灯打开
TurnOnHeadlights() string
// 大灯关闭
TurnOffHeadlights() string
// 手刹
HandBrake() string
}
2、具体类型实现接口
下面是'别摸我'和'本厮'对汽车接口的隐式实现:
go
// '别摸我'汽车
type Bmw struct {
Name string
}
func (b Bmw) EngineStart() string {
return "别摸我" + b.Name + "引擎启动"
}
func (b Bmw) TurnOff() string {
return "别摸我" + b.Name + "引擎关闭"
}
func (b Bmw) Accelerate() string {
return "别摸我" + b.Name + "加速"
}
func (b Bmw) Brake() string {
return "别摸我" + b.Name + "刹车"
}
func (b Bmw) SoundHorn() string {
return "别摸我" + b.Name + "按喇叭"
}
func (b Bmw) TurnOnHeadlights() string {
return "别摸我" + b.Name + "大灯打开"
}
func (b Bmw) TurnOffHeadlights() string {
return "别摸我" + b.Name + "大灯关闭"
}
func (b Bmw) HandBrake() string {
return "别摸我" + b.Name + "手刹"
}
// '本厮'汽车
type Benz struct {
Name string
}
func (b Benz) EngineStart() string {
return "本厮" + b.Name + "引擎启动"
}
func (b Benz) TurnOff() string {
return "本厮" + b.Name + "引擎关闭"
}
func (b Benz) Accelerate() string {
return "本厮" + b.Name + "加速"
}
func (b Benz) Brake() string {
return "本厮" + b.Name + "刹车"
}
func (b Benz) SoundHorn() string {
return "本厮" + b.Name + "按喇叭"
}
func (b Benz) TurnOnHeadlights() string {
return "本厮" + b.Name + "大灯打开"
}
func (b Benz) TurnOffHeadlights() string {
return "本厮" + b.Name + "大灯关闭"
}
func (b Benz) HandBrake() string {
return "本厮" + b.Name + "手刹"
}
func main() {
bmw := Bmw{Name: "X100"}
fmt.Println(bmw.EngineStart())
fmt.Println(bmw.TurnOnHeadlights())
fmt.Println(bmw.SoundHorn())
fmt.Println(bmw.Accelerate())
fmt.Println(bmw.Brake())
fmt.Println(bmw.HandBrake())
fmt.Println(bmw.TurnOffHeadlights())
fmt.Println(bmw.TurnOff())
benz := Benz{Name: "S1000"}
fmt.Println(benz.EngineStart())
fmt.Println(benz.TurnOnHeadlights())
fmt.Println(benz.SoundHorn())
fmt.Println(benz.Accelerate())
fmt.Println(benz.Brake())
fmt.Println(benz.HandBrake())
fmt.Println(benz.TurnOffHeadlights())
fmt.Println(benz.TurnOff())
}
//输出:
// 别摸我X100引擎启动
// 别摸我X100大灯打开
// 别摸我X100按喇叭
// 别摸我X100加速
// 别摸我X100刹车
// 别摸我X100手刹
// 别摸我X100大灯关闭
// 别摸我X100引擎关闭
// 本厮S1000引擎启动
// 本厮S1000大灯打开
// 本厮S1000按喇叭
// 本厮S1000加速
// 本厮S1000刹车
// 本厮S1000手刹
// 本厮S1000大灯关闭
// 本厮S1000引擎关闭
注意 :Bmw和 Benz类型并没有在任何地方声明它们实现了 Car接口,但 Go 编译器会自动识别它们满足了接口的要求
3、接口实现多态
多态允许我们使用统一的接口类型来处理不同的具体类型。以下是 Car 接口的多态应用:
go
// 多态函数,驾驶任何一辆车
func DriveACar(c Car) {
fmt.Println(c.EngineStart())
fmt.Println(c.TurnOnHeadlights())
fmt.Println(c.SoundHorn())
fmt.Println(c.Accelerate())
fmt.Println(c.Brake())
fmt.Println(c.HandBrake())
fmt.Println(c.TurnOffHeadlights())
fmt.Println(c.TurnOff())
}
// 多态函数,车辆秀场
func CarShow(cars []Car) {
for _, car := range cars {
DriveACar(car)
}
}
func main() {
bmw := Bmw{Name: "X200"}
//单一车辆的多态调用
DriveACar(bmw)
benz := Benz{Name: "S2000"}
//单一车辆的多态调用
DriveACar(benz)
// 车辆集合的多态处理
cars := []Car{bmw, benz}
CarShow(cars)
}
当调用 DriveACar(bmw)时,Go 语言会在运行时自动确定bmw的具体类型,并调用其对应的 EngineStart(),...,HandBrake()方法,这就是动态分发机制。
4、接口的高级特性
- 4.1 接口组合
鼓励把接口定义得小而美,然后通过组合构建复杂接口
go
type EngineSystem interface {
// 引擎启动
EngineStart() string
// 引擎关闭
TurnOff() string
// 加速
Accelerate() string
}
type BreakSystem interface {
// 刹车
Brake() string
// 手刹
HandBrake() string
}
type LightSystem interface {
// 大灯打开
TurnOnHeadlights() string
// 大灯关闭
TurnOffHeadlights() string
}
type Car interface {
EngineSystem
BreakSystem
LightSystem
// 按喇叭
SoundHorn() string
}
- 4.2 空接口 空接口 interface{}可以容纳任何类型,常用于需要处理未知类型的场景
go
func JudgeAnyType(i interface{}) {
// 类型断言
if car, ok := i.(Car); ok {
fmt.Println("这是一辆车,可以驾驶")
DriveACar(car)
return
}
if str, ok := i.(string); ok {
fmt.Println("这是个字符串", str)
}
}
func JudgeAnyTypeWay2(i interface{}) {
// 类型开关(type switch)
switch v := i.(type) {
case Car:
fmt.Println("这是一辆车,可以驾驶")
DriveACar(v)
case string:
fmt.Println("这是个字符串", v)
case int:
fmt.Println("这是个整数", v)
default:
fmt.Println("无法识别的类型")
}
}
5、接口的核心作用与设计哲学
- 解耦与抽象:接口在调用方和实现方之间建立了抽象屏障,使得代码模块之间的依赖关系更加松散。例如,你可以定义一个 DataStore接口,然后为其提供 MySQL、Mongo或内存存储等不同实现,而业务逻辑代码无需修改。
- 可测试性:通过接口,我们可以轻松创建模拟对象(mock)进行单元测试。例如,测试一个需要数据库操作的函数时,可以传入一个实现了数据库接口的内存模拟对象,而不是真实的数据库连接。
- 可扩展性:当系统需要添加新功能时,只需创建实现了相应接口的新类型,无需修改现有代码。这种对扩展开放、对修改封闭的设计符合开闭原则。
6、实践时间
把小米su7也加进来试试,源码如下:
go
package main
import "fmt"
type EngineSystem interface {
// 引擎启动
EngineStart() string
// 引擎关闭
TurnOff() string
// 加速
Accelerate() string
}
type BreakSystem interface {
// 刹车
Brake() string
// 手刹
HandBrake() string
}
type LightSystem interface {
// 大灯打开
TurnOnHeadlights() string
// 大灯关闭
TurnOffHeadlights() string
}
type Car interface {
EngineSystem
BreakSystem
LightSystem
// 按喇叭
SoundHorn() string
}
// '别摸我'汽车
type Bmw struct {
Name string
}
func (b Bmw) EngineStart() string {
return "别摸我" + b.Name + "引擎启动"
}
func (b Bmw) TurnOff() string {
return "别摸我" + b.Name + "引擎关闭"
}
func (b Bmw) Accelerate() string {
return "别摸我" + b.Name + "加速"
}
func (b Bmw) Brake() string {
return "别摸我" + b.Name + "刹车"
}
func (b Bmw) SoundHorn() string {
return "别摸我" + b.Name + "按喇叭"
}
func (b Bmw) TurnOnHeadlights() string {
return "别摸我" + b.Name + "大灯打开"
}
func (b Bmw) TurnOffHeadlights() string {
return "别摸我" + b.Name + "大灯关闭"
}
func (b Bmw) HandBrake() string {
return "别摸我" + b.Name + "手刹"
}
// '本厮'汽车
type Benz struct {
Name string
}
func (b Benz) EngineStart() string {
return "本厮" + b.Name + "引擎启动"
}
func (b Benz) TurnOff() string {
return "本厮" + b.Name + "引擎关闭"
}
func (b Benz) Accelerate() string {
return "本厮" + b.Name + "加速"
}
func (b Benz) Brake() string {
return "本厮" + b.Name + "刹车"
}
func (b Benz) SoundHorn() string {
return "本厮" + b.Name + "按喇叭"
}
func (b Benz) TurnOnHeadlights() string {
return "本厮" + b.Name + "大灯打开"
}
func (b Benz) TurnOffHeadlights() string {
return "本厮" + b.Name + "大灯关闭"
}
func (b Benz) HandBrake() string {
return "本厮" + b.Name + "手刹"
}
type XiaomiCar struct {
Name string
}
func (b XiaomiCar) EngineStart() string {
return "小米" + b.Name + "引擎启动"
}
func (b XiaomiCar) TurnOff() string {
return "小米" + b.Name + "引擎关闭"
}
func (b XiaomiCar) Accelerate() string {
return "小米" + b.Name + "加速"
}
func (b XiaomiCar) Brake() string {
return "小米" + b.Name + "刹车"
}
func (b XiaomiCar) SoundHorn() string {
return "小米" + b.Name + "按喇叭"
}
func (b XiaomiCar) TurnOnHeadlights() string {
return "小米" + b.Name + "大灯打开"
}
func (b XiaomiCar) TurnOffHeadlights() string {
return "小米" + b.Name + "大灯关闭"
}
func (b XiaomiCar) HandBrake() string {
return "小米" + b.Name + "手刹"
}
// 多态函数,驾驶任何一辆车
func DriveACar(c Car) {
fmt.Println(c.EngineStart())
fmt.Println(c.TurnOnHeadlights())
fmt.Println(c.SoundHorn())
fmt.Println(c.Accelerate())
fmt.Println(c.Brake())
fmt.Println(c.HandBrake())
fmt.Println(c.TurnOffHeadlights())
fmt.Println(c.TurnOff())
}
// 多态函数,车辆秀场
func CarShow(cars []Car) {
for _, car := range cars {
DriveACar(car)
}
}
func JudgeAnyType(i interface{}) {
// 类型断言
if car, ok := i.(Car); ok {
fmt.Println("这是一辆车,可以驾驶")
DriveACar(car)
return
}
if str, ok := i.(string); ok {
fmt.Println("这是个字符串", str)
}
}
func JudgeAnyTypeWay2(i interface{}) {
// 类型开关(type switch)
switch v := i.(type) {
case Car:
fmt.Println("这是一辆车,可以驾驶")
DriveACar(v)
case string:
fmt.Println("这是个字符串", v)
case int:
fmt.Println("这是个整数", v)
default:
fmt.Println("无法识别的类型")
}
}
func main() {
bmw := Bmw{Name: "X200"}
//单一车辆的多态调用
DriveACar(bmw)
benz := Benz{Name: "S2000"}
//单一车辆的多态调用
DriveACar(benz)
su7 := XiaomiCar{Name: "SU7"}
DriveACar(su7)
// 车辆集合的多态处理
cars := []Car{bmw, benz, su7}
CarShow(cars)
}
掌握 Go 接口的正确使用方法是编写高质量、可维护 Go 代码的关键,它体现了 Go 语言"组合优于继承"的设计哲学。