Swift 一个小型游戏对象模型渐进式设计(二)——协议与默认实现:如何写出不用继承的多态

用 protocol + extension 把上一篇的 BOSS 战例彻底重构,让代码轻量、可测试、易扩展

为什么"不用继承"

上一篇我们用 class Entity → Monster / Boss 的经典继承树完成了需求,但留下几个隐痛:

  1. 值类型无法参与:Swift 的 struct 不能继承 class。
  2. 多继承死路:一个 BOSS 既要"可攻击"又要"可飞行"还要"可分裂",Swift 不支持多类继承。
  3. 隐式共享状态:父类新增的存储属性,所有子类被迫买单,造成"胖基类"。
  4. 单元测试困难:想单独测"狂暴逻辑"必须把整个 Boss 实例 new 出来,还要喂血量。

协议(protocol)是什么

一句话:协议只定义"契约",不关心"怎么存"。

swift 复制代码
protocol Attackable {
    func attack() -> Double
}

任何类型(class / struct / enum / actor)只要实现 attack(),就自动"符合" Attackable,从而获得多态能力。

协议本身不能存属性,但可以通过"关联属性"或"协议扩展"给出默认实现,达到"代码复用"而"不强制继承"。

协议扩展:给协议加"默认实现"

语法:

swift 复制代码
extension Attackable {
    func attack() -> Double { 10.0 }   // 默认伤害
}

现在任何符合者如果不自己写 attack(),就自动拿到 10 点伤害。

想定制?在自己的类型里重新实现即可,不需要 override 关键字------因为协议不涉继承链。

拆成"能力插件"

  1. 可攻击
swift 复制代码
protocol Attackable {
    func attack() -> Double
}
extension Attackable {
    func attack() -> Double { 10.0 }
}
  1. 可定位
swift 复制代码
protocol Locatable {
    var x: Double { get set }
    var y: Double { get set }
}
  1. 可受伤
swift 复制代码
protocol Woundable {
    var hp: Double { get set }
    var maxHp: Double { get }
}
extension Woundable {
    var isRage: Bool { hp < maxHp * 0.2 }   // 狂暴判断
}
  1. 可随机伤害
swift 复制代码
protocol RandomDamage {
    func randomDamage(base: Int, range: Int) -> Double
}
extension RandomDamage {
    func randomDamage(base: Int, range: Int) -> Double {
        Double.random(in: 0.0..<Double(range)) + Double(base)
    }
}

用 struct 组装各种实体

Swift 的 struct 可以符合多个协议,享受所有默认实现,零继承。

  1. 普通小怪
swift 复制代码
struct Monster: Attackable, Locatable, Woundable, RandomDamage {
    var hp: Double
    let maxHp: Double
    var x: Double
    var y: Double
    
    // 自己定制伤害
    func attack() -> Double {
        randomDamage(base: 5, range: 6)
    }
}
  1. BOSS
swift 复制代码
struct Boss: Attackable, Locatable, Woundable {
    var hp: Double
    let maxHp: Double
    var x: Double
    var y: Double
    
    // 狂暴机制
    func attack() -> Double {
        let base: Double = 10
        return isRage ? base * 2 : base
    }
}
  1. 飞行小怪(新增能力,无需改旧代码)
swift 复制代码
protocol Flyable {
    var altitude: Double { get set }
}
struct FlyingMonster: Attackable, Locatable, Woundable, Flyable, RandomDamage {
    var hp: Double
    let maxHp: Double
    var x: Double
    var y: Double
    var altitude: Double
    
    func attack() -> Double {
        randomDamage(base: 4, range: 5) + 2   // 空对地加 2
    }
}

多态依旧可用:协议作为类型

swift 复制代码
let army: [any Attackable & Woundable] = [
    Monster(hp: 30, maxHp: 30, x: 0, y: 0),
    Boss(hp: 100, maxHp: 100, x: 1, y: 1),
    FlyingMonster(hp: 20, maxHp: 20, x: 2, y: 2, altitude: 10)
]

for unit in army {
    print("伤害=\(unit.attack()), 狂暴=\(unit.isRage)")
}

打印示例:

arduino 复制代码
伤害=8.857546603881572, 狂暴=false
伤害=10.0, 狂暴=false
伤害=9.333377580674401, 狂暴=false

把 Boss 的血量打到 19 再跑一次,就能看到伤害翻倍,逻辑与继承版完全一致。

单元测试变得多简单?

想测"狂暴判断"只要 new 一个符合 Woundable 的伪对象即可,完全不用构造整个 Boss:

swift 复制代码
struct Mock: Woundable {
    var hp: Double
    let maxHp: Double = 100
}

let mock = Mock(hp: 19)
XCTAssertTrue(mock.isRage)

协议组合(Protocol Composition)的语法糖

swift 复制代码
typealias GameUnit = Attackable & Woundable & Locatable
func move(_ unit: inout GameUnit, toX x: Double, y: Double) {
    unit.x = x
    unit.y = y
}

一个类型别名即可把"能力包"当成一个整体使用,比继承树清爽得多。

什么时候仍需要 class 继承

  1. 需要 Objective-C 运行时动态替换(KVO、Swizzle)。
  2. 需要析构器 deinit 做资源清理。
  3. 需要共享引用语义(多个指针指向同一对象)。
  4. 需要互斥锁、原子操作等"引用计数"场景。

其余场景,优先 struct + 协议。

小结:一条决策流程图

相关推荐
大熊猫侯佩15 小时前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
大熊猫侯佩1 天前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift
游戏开发爱好者81 天前
iPhone真机调试有哪些方法?一次定位推送权限问题时整理出来的几种方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
大熊猫侯佩2 天前
WWDC26 最被忽视的王炸:告别“伪并发”陷阱,Swift 6.4 的 async defer
ios·swift·编程语言
人月神话-Lee4 天前
WWDC26 深度解析:如何在 iOS 27 中打造“秒开”的相机体验
ios·swift·相机·wwdc·用户体验
Tr2e4 天前
🐱 从 0 到 1:用 Swift 手搓一个 macOS 桌面宠物(附源码)
macos·ios·swift
人月神话-Lee5 天前
【WWDC】Core AI:iOS 端侧大模型新纪元
人工智能·ios·ai·swift·wwdc·core ai
2501_916007475 天前
iOS 开发工具选择指南 从编辑器、编译器到自动化构建
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
Fatbobman(东坡肘子)5 天前
WWDC 2026 初印象:符合预期,但更务实 -- 肘子的 Swift 周报 #139
人工智能·macos·ios·swiftui·swift·wwdc