2012-01-22 10 views
5

Ho un gruppo di classi (seguendo il modello di strategia) nel mio progetto. Nella funzione principale, ricevo un valore enum dal server e in base a ciò creo un oggetto del tipo di classe base.Come risolvere il tipo di classe usando enum

Sto usando l'istruzione interruttore/caso per ottenere ciò. Ho letto da qualche parte che il principio Open/Closed non consente l'apertura di una funzione per aggiungere una nuova dichiarazione caso ogni volta che viene aggiunta una nuova classe.

Sto pensando di utilizzare un Activator.CreateInstance(). C'è qualche svantaggio ad esso.

C'è un altro modo per creare un oggetto dal tipo enum?

Aggiunta di esempio riportato di seguito, anche se non è un completo modello di strategia vera e propria

abstract public class Mammal 
{ 
    public abstract void MakeSound() 
} 

class Cat:Mammal 
{  
    public override void MakeSound() 
    { 
     Console.WriteLine("Meow");   
    }  
} 

class Dog:Mammal 
{ 

    public override void MakeSound() 
    { 
     Console.WriteLine("Bow");   
    }  
} 

Main() 
{ 

    MammalTypes mammalType = RecieveValueFromServer(); 
    Mammal mammalBase 
    switch(mammalType) // need to make this dynamic depending upon Enum type 
    { 
     case MammalTypes.Cat:mammalBase = new Cat() 
          break; 
     case MammalTypes.Dog:mammalBase = new Dog() 
          break;    
    } 

    mammalBase.MakeSound() 
} 
+2

È ** NON ** Orientato agli oggetti. La classe base non dovrebbe essere a conoscenza delle sue classi derivate. – gdoron

+1

È difficile visualizzare ciò che hai fatto. Aggiungi il codice che hai scritto alla tua domanda. – Abbas

risposta

1

È possibile utilizzare un dizionario dal tipo enum a una funzione. Le funzioni crea la tua strategia di oggetto:

public delegate Strategy StrategyFactory(); 
var strategyFactories = new Dictionary<MyEnum, StrategyFactory>(); 

Questo dizionario utilizzato per creare i vostri oggetti in base ai valori enum:

var newStategy = strategyFactories[myEnumValue](); 

fabbrica funzioni devono essere aggiunti al dizionario in qualche modo. Per questo è possibile esporre i metodi di registrazione (e forse annullare la registrazione).

0

si potrebbe creare un attributo che prende il tipo che il valore enum rappresenta e applicarlo al campo enum in questo modo:

enum MyEnum { 
    [Creates(typeof(FooStrategy))] 
    Foo, 
    [Creates(typeof(BarStrategy))] 
    Bar, 
    // etc. 
} 

[AttributeUsage(AttributeTargets.Field, Inherited=false, AllowMultiple=false)] 
sealed class CreatesAttribute : Attribute { 
    public Type TypeToCreate { get; private set; } 
    public CreatesAttribute(Type typeToCreate) { 
     TypeToCreate = typeToCreate; 
    } 

    public static IDictionary<T, Func<U>> GenerateLookup<T,U>() { 
     var query = from field in typeof(T).GetFields() 
        let creates = field.GetCustomAttriubtes(typeof(CreatesAttribute), false) as CreatesAttribute[] 
        let method = CreationMethod(typeof(U)) // create your type here 
        let key = (T)field.GetValue(null) 
        select new { Key = key, Method = method }; 
     return q.ToDictionary(item => item.Key, item => item.Method); 
    } 
} 

La parte lasciato a voi è come si desidera creare un'istanza di la tua classe. Se tutti hanno lo stesso costruttore, allora questo metodo sarà facile, come puoi chiamare Type.GetConstructor(Type[]) e quindi chiamare Invoke l'istanza ConstructorInfo oppure potresti usare un contenitore IoC e risolvere un'istanza dal tipo, senza preoccuparti tanto dei costruttori con differenti parametri.

Quindi è possibile creare una classe statica per i metodi di estensione sul tuo enum:

public static class MyEnumExtensions { 
    static readonly IDictionary<MyEnumType, MyBaseStrategyType> lookup = 
     CreatesAttribute.GenerateLookup<MyEnumType, MyBaseStrategyType>(); 

    public static MyBaseStrategyType CreateInstance(this MyEnumType key) { 
      return lookup[key](/* pass any common constructor values */); 
    } 
} 

Infine si potrebbe chiamare in questo modo:

var myEnum = MyEnumType.Foo; 
var strategy = myEnum.CreateInstance(); 
// use your strategy 

Questo dovrebbe impedirti di violare la aperto/chiuso principio e ti permettono di aggiungere tutte le classi che vuoi e modificare solo l'Enum che può essere reso abbastanza generico da creare un'istanza della strategia direttamente dal valore enum.

4

Un metodo per raggiungere la vera OCP potrebbe essere il seguente:

definire un metodo astratto Is per forzare ogni sottotipo concreto di Mammifero per specificare se sia opportuno per un dato valore della enum:

abstract public class Mammal 
{ 
    public abstract void MakeSound(); 

    public abstract bool Is(MammalTypes mammalType); 
} 

le implementazioni di Is nelle sottoclassi sarebbe simile:

class Cat : Mammal 
{ 
    // other specific members 

    public override bool Is(MammalTypes mammalType) 
    { 
     return mammalType == MammalTypes.Cat; 
    } 
} 

class Dog : Mammal 
{ 
    // other specific members 

    public override bool Is(MammalTypes mammalType) 
    { 
     return mammalType == MammalTypes.Dog; 
    } 
} 

fatto questo, possiamo ora creare un MammalF Classe actory che, quando somministrato un mammifero scansioni valore enum attraverso le classi disponibili e, quando trova una corrispondenza, restituisce un'istanza di quella classe:

public class MammalFactory 
{ 
    private readonly IEnumerable<Type> _mammalTypes; 

    public MammalFactory() 
    { 
     var currentAssembly = Assembly.GetExecutingAssembly(); 

     _mammalTypes = currentAssembly.GetTypes() 
      .Where(t => typeof(Mammal).IsAssignableFrom(t) && !t.IsAbstract); 
    } 

    public Mammal Create(MammalTypes mammalType) 
    { 
     return _mammalTypes 
      .Select(type => CreateSpecific(type, mammalType)) 
      .First(mammal => mammal != null); 
    } 

    public Mammal CreateSpecific(Type type, MammalTypes mammalEnumType) 
    { 
     var mammalInstance = (Mammal)Activator.CreateInstance(type); 

     return mammalInstance.Is(mammalEnumType) ? mammalInstance : null; 
    } 
} 

L'utilizzo finale sarà simile a questa:

var mammalFactory = new MammalFactory(); 

var guessWhatMammal = mammalFactory.Create(MammalTypes.Cat); 

Questo è pienamente conforme a OCP. È necessario solo creare una nuova classe Mammal per essere automaticamente cablata e pronta all'uso all'interno dell'applicazione. (Non c'è bisogno di modificare qualsiasi altra cosa nella domanda, fatta eccezione per l'enumerazione stessa)

ci sono alcuni problemi con questo approccio:

  • sottopone a scansione solo l'assemblea in corso di esecuzione per i tipi di mammiferi
  • ha per creare un'istanza di Mammifero ogni volta che ha bisogno di verificare se tale tipo sia opportuno

anche se questi problemi possono essere affrontati, uno è ancora a sinistra: complessità.

Questo è complessa perché:

  • abbiamo raddoppiato la quantità di codice necessaria
  • l'auto-cablaggio potrebbe essere fonte di confusione per chi ancora non conosce il progetto

penso alla conclusione è questo: i modelli di design non sono regole severe. Non vale la pena fare qualcosa solo per conformarsi a un determinato progetto. Invece, dobbiamo essere pragmatici e trovare quel perfetto equilibrio tra conformità del modello e utilità/semplicità/leggibilità. Ciò dipende in gran parte dal problema che tentiamo di risolvere e in molti casi potrebbe benissimo essere la dichiarazione switch presentata nella domanda.

Problemi correlati