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 + 协议。

小结:一条决策流程图

相关推荐
初级代码游戏13 小时前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏16 小时前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui18 小时前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng113321 小时前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
zhyongrui2 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩2 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921434 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张4 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩5 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning5 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm