2011-09-09 3 views
5

Se aiuta, la seguente domanda è nel contesto di un gioco che sto costruendo.Che cos'è un modo corretto di sostituire il blocco di interruttori e Enum in questa situazione (C#)?

In alcuni punti diversi ho il seguente scenario. Esiste una classe genitore, per questo esempio chiamata Skill, e ho un numero di sottoclassi che implementano i metodi dalla classe genitore. Esiste anche un'altra classe genitore che chiameremo vocazione. Le abilità devono essere elencate in diverse sottoclassi di vocazione. Tuttavia, queste abilità devono essere disponibili per qualsiasi cosa nel gioco che usi una determinata vocazione.

La mia configurazione attuale è di avere un Enum chiamato Skill.Id, in modo che Vocation contenga una raccolta di valori da quell'Enum e quando un'entità nel gioco assume quella Vocazione, la raccolta viene passata in un'altra classe, chiamata SkillFactory. Skill.Id ha bisogno di una nuova voce ogni volta che creo una nuova sottoclasse Skill, nonché un caso nel blocco switch per il costruttore della nuova sottoclasse.

cioè .:

//Skill.Id 
Enum{FireSkill,WaterSkill,etc} 

//SkillFactory 
public static Skill Create(Skill.Id id) 
{ 
    switch(id) 
    { 
     case Skill.Id.FireSkill: 
      return new FireSkill(); 
     //etc 
    } 
} 

Questo funziona perfettamente bene, ma utilizzando l'enumerazione e il blocco interruttore come un go tra sente come più in alto di quanto ho bisogno di risolvere questo problema. Esiste un modo più elegante per creare istanze di queste sottoclassi di abilità, ma consente comunque a Vocation di contenere una raccolta che identifichi le abilità che può utilizzare?

Modifica: Sto bene lanciando l'enumerazione e il blocco di switch associato, purché Vocation possa contenere una raccolta che consenta l'istanziazione arbitraria delle sottoclassi di Skill.

+0

Switch e logico se le dichiarazioni sono estremamente veloci oltre a quasi ogni applicazione di fabbrica (che ho visto) fa la stessa cosa utilizzando IMHO non lo cambierei –

+0

Sono d'accordo con questo, a patto di non duplicare lo switch tra più routine all'interno del tipo. A quel punto, una ricerca di dizionario è spesso un approccio migliore, come puoi metti lo "switch" in una singola posizione nel codice –

+0

questo è il modo più veloce e più leggibile, perché dovrebbe trovare un altro modo? – ktutnik

risposta

8

È possibile creare un Dictionary<Skill.Id, Func<Skill>> e utilizzarlo per creare un'istanza.

Nel costruttore:

Dictionary<Skill.Id, Func<Skill>> creationMethods = new Dictionary<Skill.Id, Func<Skill>>(); 
public SkillFactory() 
{ 
    creationMethods.Add(Skill.Id.FireSkill,() => new FireSkill()); 
    creationMethods.Add(Skill.Id.WaterSkill,() => new WaterSkill()); 
} 

Poi, il metodo Create diventa:

public static Skill Create(Skill.Id id) 
{ 
    return creationMethods[id](); 
} 

Certo, questo non è molto meglio - tranne che non permette di estendere questo per altre funzionalità questo è per ID senza duplicare il blocco switch se questo diventa un requisito. (Metti di più nel lato valore del dizionario.)

Detto questo, a lungo termine, eliminare completamente l'enum può essere un buon vantaggio per l'estensibilità. Ciò richiederà tuttavia un cambiamento più elaborato. Ad esempio, se si utilizza MEF, è possibile importare un set di tipi SkillFactory in fase di runtime e associarli a un nome (tramite metadati) tramite un singolo ImportMany. Ciò ti consentirebbe di aggiungere nuove sottoclassi di abilità senza modificare la tua fabbrica e fare riferimento ad esse per nome o altro meccanismo.

0

se questa funzione di creazione verrà utilizzata in modo tale che un "caso" genererà un sovraccarico, il dizionario con le chiavi di enumerazione genererà un sacco di spazzatura.

Nel contesto di un gioco xna, può essere peggio del "caso".

"Se si utilizza un tipo di enum come chiave del dizionario, le operazioni del dizionario interno causeranno il pugilato, che può essere evitato utilizzando i tasti integer e convertendo i valori dell'enumerazione in ints prima di aggiungerli al dizionario."Extracted from here

è possibile utilizzare un semplice array e cast enum a int per l'indicizzazione:

Enum {FireSkill=0,WaterSkill=1,etc} 

Func<Skill>[] CreationMethods = new Func<Skill>() 
{ 
    () => new FireSkill(), 
    () => new WaterSkill(), 
}