2016-12-05 74 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