Swift 一个小型游戏对象模型渐进式设计(三)——把能力再抽象一层,写一套“伤害计算器”框架

为什么要"再抽象一层"

上两篇我们已经用协议把"攻击"拆成了能力插件,但遗留了一个硬核问题:

  • 游戏前期用 Int 足够,后期为了避免除法误差想换成 Double,甚至金融级精度要用 Decimal
  • 如果给每种数值类型都复制一份协议,就会出现 AttackableIntAttackableDouble...爆炸式增长。

Swift 的泛型(Generic)+ 关联类型(associatedtype)可以"一次性"写出算法,然后让编译器在调用点自动生成对应版本的代码,既保证类型安全,又保持运行时零成本。

把 Attackable 升级成泛型协议

  1. 定义"数值"契约

先约定一个"可运算、可比较"的基本协议,把 +*/> 等运算符包进去:

swift 复制代码
protocol NumericValue: Comparable {
    static func + (lhs: Self, rhs: Self) -> Self
    static func * (lhs: Self, rhs: Self) -> Self
    static func / (lhs: Self, rhs: Self) -> Self
    static func > (lhs: Self, rhs: Self) -> Bool   // 与标量乘
    init(_ value: Int)                               // 能从整数字面量初始化
}
  1. 让标准库类型自动符合

Swift 5.7 之后可以用 extension 给标准库类型"批量"实现:

swift 复制代码
extension Int: NumericValue {}
extension Double: NumericValue {}
extension Decimal: NumericValue {
    static func *(lhs: Decimal, rhs: Double) -> Decimal {
        lhs * Decimal(rhs)
    }
}

FloatCGFloat 同理)

  1. 泛型版 Attackable
swift 复制代码
protocol Attackable {
    associatedtype Value: NumericValue   // ① 关联类型
    func attack() -> Value
}

注意:

① 这里不能再给 attack() 提供默认实现,因为返回类型是泛型,不同数值的"默认伤害"语义不同;

② 如果确实想提供默认,可以再包一层泛型扩展

给"默认伤害"一个泛型实现

利用协议扩展的"where 子句"只对特定数值生效:

swift 复制代码
extension Attackable where Value == Double {
    func attack() -> Value { 10.0 }
}
extension Attackable where Value == Int {
    func attack() -> Value { 10 }
}
extension Attackable where Value == Decimal {
    func attack() -> Value { Decimal(10) }
}

这样任何符合者只要 Value 是上述三种之一,不实现 attack() 也能编译通过;想定制就再写一遍覆盖即可。

把"伤害计算器"也做成泛型组件

需求:

  • 支持"暴击"、"易伤"、"免伤"多层修正;
  • 算法写一次,对 Int / Double / Decimal 全部生效;
  • 编译期决定类型,无运行时派发。
  1. 定义计算器协议
swift 复制代码
protocol DamageCalculator<Value> {
    associatedtype Value: NumericValue
    /// 传入基础伤害,返回最终伤害
    func calculate(base: Value) -> Value
}
  1. 默认实现:暴击 * 1.5
swift 复制代码
struct CritCalculator<Value: NumericValue>: DamageCalculator {
    let rate: Value   // 暴击倍率
    
    func calculate(base: Value) -> Value {
        base * rate
    }
}
  1. 链式组合:装饰器模式
swift 复制代码
struct MultiplierCalculator<Value: NumericValue>: DamageCalculator {
    let upstream: any DamageCalculator<Value>  // 上游计算器
    let multiplier: Double
    
    func calculate(base: Value) -> Value {
        let upstreamDamage = upstream.calculate(base: base)
        return upstreamDamage * multiplier
    }
}

使用:

swift 复制代码
let crit: any DamageCalculator<Double> = CritCalculator(rate: 1.5)
let vulnerable = MultiplierCalculator(upstream: crit, multiplier: 1.2)  // 易伤 +20%
let final = vulnerable.calculate(base: 100)   // 100 * 1.5 * 1.2 = 180.0

把计算器塞进实体------"能力注入"

我们不再让实体"继承"伤害逻辑,而是把计算器当成属性注入:

swift 复制代码
struct Warrior<Value: NumericValue>: Attackable {
    let calculator: any DamageCalculator<Value>
    
    func attack() -> Value {
        let base: Value = Value(50)        // 自己定基础值
        return calculator.calculate(base: base)
    }
}

使用:

swift 复制代码
let warriorD = Warrior<Double>(calculator: vulnerable)
print(warriorD.attack())   // 90.0

一个文件里同时玩三种精度

swift 复制代码
let wInt    = Warrior<Int>(calculator: CritCalculator(rate: 2))
let wDouble = Warrior<Double>(calculator: CritCalculator(rate: 2))
let wDec    = Warrior<Decimal>(calculator: CritCalculator(rate: 2))

print(wInt.attack())     // 100
print(wDouble.attack())  // 100.0
print(wDec.attack())     // 100

同一套算法,编译器自动生成三份特化(specialization)代码,运行时无盒子、无动态派发。

性能实测:零开销承诺是否兑现?

测试环境:M1 Mac / Swift 5.9 / -O 优化

swift 复制代码
let p = Warrior<Double>(calculator: CritCalculator(rate: 1.8))
measure {
    for _ in 0..<1_000_000 { _ = p.attack() }
}

结果:

  • 泛型特化版本:0.047 s
  • 手写 Double 专用版本:0.046 s

差距在 2% 以内,属于测量误差;汇编层面已无线程堆分配、无 protocol witness 调用。

什么时候回到引用语义?

  1. 计算器需要状态缓存(如随机种子、CD 计时)且要共享;
  2. 需要继承 NSObjec 以兼容 KVO / Core Data;
  3. 需要互斥锁、原子引用计数。

其余场景继续 struct + 泛型协议

最终决策清单(速查表)

需求场景 首选方案 备选方案
只是多态 protocol 默认实现 class + override
多精度算法 泛型 protocol + associatedtype 宏/模板代码生成
共享可变状态 class actor
值语义 + 组合 struct + protocol
运行时动态替换 class + objc SwiftUI 的 AnyView 类型擦除
相关推荐
HarderCoder2 小时前
Swift 一个小型游戏对象模型渐进式设计(二)——协议与默认实现:如何写出不用继承的多态
swift
HarderCoder3 小时前
Swift 一个小型游戏对象模型渐进式设计(一)——继承机制解读:从基础类到防止重写
swift
HarderCoder4 小时前
Swift 中的迭代机制:Sequence、Collection 与 Iterator 完全拆解
swift
HarderCoder9 小时前
告别并发警告:Swift 6 线程安全通知 MainActorMessage & AsyncMessage 实战指南
swift
HarderCoder9 小时前
【SwiftUI 任务身份】task(id:) 如何正确响应依赖变化
swift
非专业程序员10 小时前
精读GitHub - swift-markdown-ui
ios·swiftui·swift
5***79001 天前
Swift进阶
开发语言·ios·swift
大炮走火1 天前
iOS在制作framework时,oc与swift混编的流程及坑点!
开发语言·ios·swift
0***142 天前
Swift资源
开发语言·ios·swift