Golang接口
接口是Golang中定义的一组方法的集合,但不提供实现的细节。我认为Golang接口实质其实是高阶函数的聚合,本质对应一组行为或操作。
示例:
type Animal interface {
Eat()
Sleep()
}
type Shape interface {
Area() float64
Perimeter() float64
}
接口的使用原则
尽量定义小接口
接口本身保证单一职责原则,有利于接口之间进行组合、复用。
接口的动态和静态特性
静态接口,指的是编译器会对接口的右值做静态检查,判断右值对象是否正确实现了接口中所有的方法。
动态接口,指的是接口对象的值,会在运行时被动态定义和决定。
接口使用示例
通过依赖注入实现接口
在 Go 语言里,依赖注入是一种有效的设计模式,它能增强代码的可扩展性。依赖注入是把依赖对象作为参数传递给需要它的对象,而非在对象内部创建依赖。下面为你展示如何通过依赖注入来实现 Animal 接口。
package main
import "fmt"
// Animal 定义一个动物接口,包含 Eat 和 Sleep 方法
type Animal interface {
Eat()
Sleep()
}
// Panda 定义熊猫结构体
type Panda struct{}
// Eat 实现 Animal 接口的 Eat 方法
func (p *Panda) Eat() {
fmt.Println("Panda is eating bamboo.")
}
// Sleep 实现 Animal 接口的 Sleep 方法
func (p *Panda) Sleep() {
fmt.Println("Panda is sleeping.")
}
// Lion 定义狮子结构体
type Lion struct{}
// Eat 实现 Animal 接口的 Eat 方法
func (l *Lion) Eat() {
fmt.Println("Lion is eating meat.")
}
// Sleep 实现 Animal 接口的 Sleep 方法
func (l *Lion) Sleep() {
fmt.Println("Lion is sleeping.")
}
// PerformActions 通用函数,接收一个实现了 Animal 接口的对象,执行其 Eat 和 Sleep 方法
func PerformActions(animal Animal) {
animal.Eat()
animal.Sleep()
}
func main() {
// 创建一个 Panda 实例
panda := &Panda{}
// 将 panda 注入到 PerformActions 函数中
PerformActions(panda)
// 创建一个 Lion 实例
lion := &Lion{}
// 将 lion 注入到 PerformActions 函数中
PerformActions(lion)
}
接口的水平组合
一个函数接收 Movable、Attackable 和 Defendable 三个接口类型的参数,然后依次调用它们的方法,实现了对不同接口行为的组合调用。这样,这一个方法是非常好扩展的。
package main
import "fmt"
// Movable 定义可移动的接口
type Movable interface {
Move()
}
// Attackable 定义可攻击的接口
type Attackable interface {
Attack()
}
// Defendable 定义可防御的接口
type Defendable interface {
Defend()
}
// 实现 Movable 接口的具体结构体
type WarriorMovable struct{}
func (wm *WarriorMovable) Move() {
fmt.Println("Warrior is moving.")
}
// 实现 Attackable 接口的具体结构体
type WarriorAttackable struct{}
func (wa *WarriorAttackable) Attack() {
fmt.Println("Warrior is attacking with a sword.")
}
// 实现 Defendable 接口的具体结构体
type WarriorDefendable struct{}
func (wd *WarriorDefendable) Defend() {
fmt.Println("Warrior is defending with a shield.")
}
// PerformAllActions 定义一个函数,接收三个接口类型的参数,调用它们的方法
func PerformAllActions(m Movable, a Attackable, d Defendable) {
m.Move()
a.Attack()
d.Defend()
}
func main() {
// 创建实现各个接口的实例
movable := &WarriorMovable{}
attackable := &WarriorAttackable{}
defendable := &WarriorDefendable{}
// 调用 PerformAllActions 函数,传入实现接口的实例
PerformAllActions(movable, attackable, defendable)
}
接口的垂直组合
可以通过往结构体里嵌入接口的方式,实现更大的接口。而不是往结构体里组合结构体。好处是,可以根据不同的需求传入不同的接口实现,而不是固定使用某一种实现。例如,你可以创建不同的 Movable 实现(如 FastMovement、SlowMovement),在不同场景下选择合适的实现传入 SuperWarrior 实例中。
package main
import "fmt"
// Movable 定义可移动的接口
type Movable interface {
Move()
}
// Attackable 定义可攻击的接口
type Attackable interface {
Attack()
}
// Defendable 定义可防御的接口
type Defendable interface {
Defend()
}
// Movement 实现 Movable 接口的结构体
type Movement struct{}
func (m *Movement) Move() {
fmt.Println("Moving forward...")
}
// Attack 实现 Attackable 接口的结构体
type Attack struct{}
func (a *Attack) Attack() {
fmt.Println("Launching an attack!")
}
// Defense 实现 Defendable 接口的结构体
type Defense struct{}
func (d *Defense) Defend() {
fmt.Println("Raising a shield to defend.")
}
// SuperWarrior 更大的结构体,组合了 Movable、Attackable 和 Defendable 接口
type SuperWarrior struct {
Movable
Attackable
Defendable
}
func main() {
// 创建实现各个接口的实例
movement := &Movement{}
attack := &Attack{}
defense := &Defense{}
// 创建 SuperWarrior 实例,并将实现接口的实例赋值给其组合的接口字段
superWarrior := SuperWarrior{
Movable: movement,
Attackable: attack,
Defendable: defense,
}
// 调用 SuperWarrior 实例的接口方法
superWarrior.Move()
superWarrior.Attack()
superWarrior.Defend()
}
怎么选取水平组合和垂直组合?
核心选择原则
- 生命周期
- 水平组合:适用于临时性功能组合(如一次性的任务处理)
- 垂直组合:适用于长期存在的复合能力(如角色基础能力体系)
- 扩展性
- 当新增功能需要修改超过3个接口时,应转为垂直组合
- 当接口组合出现重复模式时(如多个函数都需要
Movable+Attackable组合),标志垂直组合时机
- 多态实现
- 需要运行时动态切换不同实现时,优先选择垂直组合
- 固定实现组合时可采用水平组合
水平和垂直组合的详细对比
最后送上一个详细对比的表格:
| 对比维度 | 水平组合 | 垂直组合 |
|---|---|---|
| 定义方式 | 一个函数接收多个不同接口类型的参数,在函数内部依次调用这些接口的方法。 | 在结构体中嵌入多个接口,使该结构体拥有这些接口定义的所有方法。 |
| 适用场景 | - 功能临时组合:在特定逻辑中临时需要同时使用多个接口功能。 - 功能相互独立:各接口功能关联性弱,仅在某一操作中协同使用。 | - 构建复杂类型:创建具有多种能力的复合类型。 - 多态与替换:需根据不同场景灵活替换接口实现。 - 代码复用:多个结构体有共同功能需求。 |
| 代码灵活性 | 相对灵活,可根据调用需求传入不同实现的接口对象,但仅在调用函数时生效。 | 高度灵活,可在实例化结构体时动态选择接口的具体实现,且结构体实例可在多处使用不同组合。 |
| 代码扩展性 | 添加新功能时,需修改调用函数的参数列表和内部逻辑。 | 添加新功能只需在结构体中嵌入新接口,并提供对应实现。 |
| 可维护性 | 随着接口数量增加,函数参数列表变长,可读性和维护性降低。 | 接口与结构体分离,逻辑清晰,易于维护和理解。 |
| 示例场景(维护与使用) | 游戏任务系统:在游戏里有一个护送任务,任务执行函数需要角色具备移动、对话、防御三种能力。通过水平组合,定义 Movable、Talkable、Defendable 三个接口,任务执行函数接收这三个接口类型的参数。 使用:在该护送任务中,传入对应接口的实现对象即可开始任务。 维护:若后续要给任务增加一个 “侦查” 功能,就得修改任务执行函数的参数列表,添加 Scoutable 接口类型的参数,并且在函数内部添加调用侦查方法的逻辑。如果接口数量持续增多,函数会变得复杂难读。 | 游戏任务系统(垂直组合视角) :同样是游戏护送任务,若采用垂直组合,定义 Movable、Talkable、Defendable 接口后,创建一个 EscortCharacter 结构体嵌入这些接口。 使用:先实例化 EscortCharacter 结构体并传入各接口的具体实现,之后在任务中使用该结构体实例。不过,若只是临时执行这个护送任务,创建专门的结构体略显繁琐,且结构体的复用性在该场景下可能不高。 维护:若后续任务需求变化大,比如不同任务需要不同的能力组合,每次都要修改或新增结构体,会造成结构体数量增多,管理成本上升。 游戏角色养成系统:要创建不同类型的角色,如战士、法师等,这些角色都需要移动、攻击、释放技能的能力。通过垂直组合,定义 Movable、Attackable、SkillCaster 接口,创建 Character 结构体嵌入这些接口。 使用:在创建战士角色时,传入快速移动、近身攻击、狂暴技能的实现;创建法师角色时,传入漂浮移动、远程攻击、魔法技能的实现。不同场景下可以方便地切换角色的能力组合。 维护:若后续要给角色增加 “飞行” 能力,只需定义 Flyable 接口并嵌入到 Character 结构体中,再提供飞行能力的具体实现,无需修改大量现有代码,维护成本低。 |