2016-12-05 72 views
6

我决定通过编写一些简单的代码来更好地理解类型擦除。我有一个通用的士兵协议。士兵有武器和士兵可以战斗。我想创建不同类型的士兵的军队。我认为这种类型的擦除会为我提供一种拳击士兵使用者的手段,这样我就可以将他们当作纯粹的士兵(而不是狙击手,步兵等)对待。但是我发现中间拳击类型(类型橡皮擦)必须仍然可以通过士兵的相关类型(即武器)进行通用化。所以,我可以让步枪挥舞士兵,或火箭挥舞士兵,但不是纯粹的士兵。有没有关于我错过的类型擦除的使用?类型擦除:我错过了什么?

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() } 
    } 
} 

回答

5

您需要键入擦除武器以及:

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

有了这个,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() } 
} 

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

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

let sniper = Soldier.sniper 
sniper.fight() 

我在Beyond Crusty: Real-World Protocols中进一步讨论了这一点。有时你不想要一个协议。

+0

这样做,罗布。谢谢。顺便说一句:我喜欢你的谈话。我会再次观看Beyond Crusty--只要我抽空观看原作。 – Verticon