Swift 一个小型游戏对象模型渐进式设计(一)——继承机制解读:从基础类到防止重写

为什么必须有"继承"

在真实世界里,我们习惯把事物归类:车 → 自行车 → 双人自行车。

Swift 的 class 类型允许我们用同样的层级方式建模,把公共的代码放在"上层",把差异化的代码放在"下层",这就是继承(Inheritance)。

它带来的三大价值:

  1. 代码复用:公共逻辑写一次。
  2. 统一接口:上层可用"父类指针"操作一切子类。
  3. 多态:运行时才决定到底执行哪段代码。

基础概念速览

  1. 基类(Base Class):不继承任何类的类。
  2. 子类(Subclass):写在冒号后面的类,它自动拥有父类所有成员。
  3. 重写(Override):子类对继承来的成员重新实现,需加关键字 override。
  4. super:在子类内部访问"父类实现"的前缀。
  5. final:阻止后面的人再继续重写或继承。

基类长什么样

swift 复制代码
/// 基类:最普通的"车"
class Vehicle {
    var currentSpeed = 0.0          // 存储属性,默认 0
    
    /// 计算属性:只读,返回人类可读的描述
    var description: String {
        return "traveling at \(currentSpeed) mph"
    }
    
    /// 实例方法:基类里什么都不做,留给子类去"填坑"
    func makeNoise() {
        // 空实现
    }
}

// 使用
let someVehicle = Vehicle()
print("Vehicle: \(someVehicle.description)")
// 打印:Vehicle: traveling at 0.0 mph

子类化(Subclassing)

语法:

swift 复制代码
class 子类: 父类 { /* 新增或覆盖 */ }

示例 1:自行车

swift 复制代码
class Bicycle: Vehicle {
    var hasBasket = false   // 新增属性
}

let bike = Bicycle()
bike.hasBasket = true
bike.currentSpeed = 15
print("Bicycle: \(bike.description)")
// 打印:Bicycle: traveling at 15.0 mph

示例 2:双人自行车(子类还能再被继承)

swift 复制代码
class Tandem: Bicycle {
    var currentNumberOfPassengers = 0
}

let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22
print("Tandem: \(tandem.description)")
// 打印:Tandem: traveling at 22.0 mph

重写(Override)全规则

  1. 方法重写:必须写 override;否则编译器报错。
swift 复制代码
class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}
Train().makeNoise()   // Choo Choo
  1. 属性重写

    子类"看不到"父类属性到底是存储型还是计算型,只能按"名字 + 类型"去匹配。

    a) 只读变读写:可以补充 setter。

    b) 读写变只读:❌ 不允许。

示例:给车加"档位"描述

swift 复制代码
class Car: Vehicle {
    var gear = 1
    
    override var description: String {
        // 先拿父类描述,再拼接自己的
        return super.description + " in gear \(gear)"
    }
}

let car = Car()
car.currentSpeed = 25
car.gear = 3
print("Car: \(car.description)")
// 打印:Car: traveling at 25.0 mph in gear 3
  1. 属性观察器重写

    可以为任何继承来的属性(无论存储/计算)添加 willSet/didSet。

    典型场景:自动档根据速度换挡。

swift 复制代码
class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {
            gear = Int(currentSpeed / 10) + 1   // 自己算档位
        }
    }
}

let auto = AutomaticCar()
auto.currentSpeed = 35
print("AutomaticCar: \(auto.description)")
// 打印:AutomaticCar: traveling at 35.0 mph in gear 4

防止继承与重写------final

  1. 防重写
swift 复制代码
class Parent {
    final var id = 1          // 子类不能 override
    final func foo() {}       // 子类不能 override
}
  1. 防继承
swift 复制代码
final class Tool {}         // 任何人写 class MyTool: Tool {} 都会编译失败

super 的使用场景小结

  1. 在 override 方法里:super.someMethod()
  2. 在 override 属性 getter/setter 里:super.someProperty
  3. 在 override 下标里:super[index]

容易踩的坑

  1. 忘记写 override → 编译期报错。
  2. 重写时把只读属性改成只写 setter, Swift 不允许。
  3. 在 init 里访问 super 属性前,必须保证本类存储属性已初始化(初始化器规则)。
  4. 把 struct 拿去继承 → Swift 只有 class 支持继承,struct/enum 不行。

继承的边界与替代方案

继承是"白盒复用",子类会依赖父类实现细节,容易造成"脆弱基类"问题。Swift 提供了更轻量的组合手段:

  1. 协议(protocol)+ 默认实现 → 无需继承即可获得多态。
  2. 值类型(struct)+ 组合 → 把"能力"做成属性,而非父类。
  3. 面向协议编程(POP)→ 把"is-a"转成"can-do",降低耦合。

实战扩展:一个小型游戏对象模型

需求:

  • 所有游戏实体都有血量 hp 与坐标 (x,y)。
  • 玩家可以攻击,造成随机伤害。
  • BOSS 血量低于 20% 时进入狂暴模式,攻击力翻倍。

代码:

swift 复制代码
// 1. 基类
class Entity {
    var hp: Double
    var x: Double, y: Double
    
    init(hp: Double, x: Double, y: Double) {
        self.hp = hp; self.x = x; self.y = y
    }
    
    func attack() -> Double {
        return 10.0   // 默认伤害
    }
}

// 2. 普通小怪
class Monster: Entity {
    override func attack() -> Double {
        let damage = Double.random(in: 0..<6) + 5
        return damage
    }
}

// 3. BOSS
class Boss: Entity {
    override func attack() -> Double {
        let base = super.attack()
        // 狂暴判断
        let rage = hp < 20   // 假设 maxHp = 100
        return rage ? base * 2 : base
    }
}

// 4. 使用
let boss = Boss(hp: 15, x: 0, y: 0)
print("BOSS 伤害:\(boss.attack())")  // 血量<20,伤害翻倍

总结与思考

  1. 继承是 Swift 面向对象体系的基石,但"能用"不等于"该用"。
  2. 优先把"共性"做成协议或工具函数,再考虑是否抽象出基类。
  3. 重写时始终加 override,既安全又自文档化。
  4. 用 final 明确"设计边界",让后来者少踩坑。
  5. 与值类型、协议、组合搭配,才能发挥 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
z***I3942 天前
Swift Tips
开发语言·ios·swift
J***Q2922 天前
Swift Solutions
开发语言·ios·swift