类型擦除:我错过了什么吗?

Ver*_*con 6 generics swift

我决定通过编写一些简单的代码来更好地理解类型擦除.我有一个通用的士兵协议.士兵有武器,士兵可以战斗.我想创建不同类型士兵的军队.我认为类型擦除会为我提供一种拳击士兵采用者的方法,以便我可以将它们视为普通士兵(而不是狙击手,步兵等)但我发现中间,拳击类型(类型橡皮擦)必须在士兵的相关类型(即武器)上仍然是通用的.所以,我可以让步枪挥舞士兵,或者火箭挥舞士兵而不仅仅是普通的士兵.有没有关于我错过的类型擦除的使用?

import Foundation

// Soldiers have weapons and soldiers can fight

protocol Weapon {
    func fire()
}

protocol Soldier {
    associatedtype W: Weapon

    var weapon: W { get }

    func fight()
}

extension Soldier {
    func fight() { weapon.fire() }
}

// Here are some weapons

struct Rifle : Weapon {
    func fire() { print("Bullets away!") }
}

struct Rocket : Weapon {
    func fire() { print("Rockets away!") }
}

struct GrenadeLauncher : Weapon {
    func fire() { print("Grernades away!") }
}

// Here are some soldiers

struct Sniper : Soldier {
    var weapon = Rifle()
}

struct Infantryman : Soldier {
    var weapon = Rifle()
}

struct Artillaryman : Soldier {
    var weapon = Rocket()
}

struct Grenadier : Soldier {
    var weapon = GrenadeLauncher()
}

// Now I would like to have an army of soldiers but the compiler will not let me.
// error: protocol 'Soldier' can only be used as a generic constraint because it has Self or associated type requirements

class Army {
    var soldiers = [Soldier]()

    func join(soldier: Soldier) {
        soldiers.append(soldier)
    }

    func makeWar() {
        for soldier in soldiers { soldier.fight() }
    }
}

// So, let's try the type erasure technique:

struct AnySoldier<W: Weapon> : Soldier {
    var weapon: W
    private let _fight: () -> Void

    init<S: Soldier>(soldier: S) where S.W == W {
        _fight = soldier.fight
        weapon = soldier.weapon
    }

    func fight() { _fight() }
}

var s1 = AnySoldier(soldier: Sniper())
print (type(of: s1)) // AnySoldier<Rifle>
s1.fight() // Bullets away!
s1.weapon.fire() // Bullets away!
s1 = AnySoldier(soldier: Infantryman()) // Still good; Infantrymen use rifles
s1 = AnySoldier(soldier: Grenadier()) // Kaboom! Grenadiers do not use rifles


// So now I can have an army of Rifle wielding Soldiers

class Army {
    var soldiers = [AnySoldier<Rifle>]()

    func join(soldier: AnySoldier<Rifle>) {
        soldiers.append(soldier)
    }

    func makeWar() {
        for soldier in soldiers { soldier.fight() }
    }
}
let army = Army()
army.join(soldier: AnySoldier(soldier: Sniper()))
army.join(soldier: AnySoldier(soldier: Infantryman()))
army.join(soldier: AnySoldier(soldier: Grenadier())) // Kaboom! Rifles only
army.makeWar()

// But, what I really want is an army wherein the weapons are unrestricted.
class Army {
    var soldiers = [AnySoldier]()

    func join(soldier: AnySoldier) {
        soldiers.append(soldier)
    }

    func makeWar() {
        for soldier in soldiers { soldier.fight() }
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*ier 5

您还需要键入 - 擦除武器:

struct AnyWeapon: Weapon {
    private let _fire: () -> Void
    func fire() { _fire() }
    init<W: Weapon>(_ weapon: W) {
        _fire = weapon.fire
    }
}
Run Code Online (Sandbox Code Playgroud)

有了这个,AnySoldier不需要是通用的.

struct AnySoldier : Soldier {
    private let _fight: () -> Void
    let weapon: AnyWeapon

    init<S: Soldier>(_ soldier: S) {
        _fight = soldier.fight
        weapon = AnyWeapon(soldier.weapon)
    }

    func fight() { _fight() }
}
Run Code Online (Sandbox Code Playgroud)

但是,不要忽视另一种方法,即用简单的函数替换武器,使Soldier成为一个简单的结构.例如:

struct Soldier {
    private let weaponFire: () -> Void
    func fight() { weaponFire() }
    static let sniper = Soldier(weaponFire: { print("Bullets away!") })
}

let sniper = Soldier.sniper
sniper.fight()
Run Code Online (Sandbox Code Playgroud)

我将在Beyond Crusty:Real-World Protocols中进一步讨论其中的一些内容.有时您不需要协议.