Domanda

Sto lavorando su un progetto interno per la mia azienda, e parte del progetto è quello di essere in grado di analizzare i vari "Compiti" da un file XML in un insieme di attività di essere eseguito più tardi.

Perché ogni tipo di Attività ha una moltitudine di associati diversi campi, ho deciso che sarebbe stato meglio per rappresentare ogni tipo di Attività con una classe separata.

Per fare questo, ho costruito una classe base astratta:

public abstract class Task
{
    public enum TaskType
    {
        // Types of Tasks
    }   

    public abstract TaskType Type
    {
        get;
    }   

    public abstract LoadFromXml(XmlElement task);
    public abstract XmlElement CreateXml(XmlDocument currentDoc);
}

Ogni attività ereditata da questa classe di base, e incluso il codice necessario per creare dal passato XmlElement, così come serializzare stesso di nuovo fuori per un XmlElement.

Un esempio di base:

public class MergeTask : Task
{

    public override TaskType Type
    {
        get { return TaskType.Merge; }
    }   

    // Lots of Properties / Methods for this Task

    public MergeTask (XmlElement elem)
    {
        this.LoadFromXml(elem);
    }

    public override LoadFromXml(XmlElement task)
    {
        // Populates this Task from the Xml.
    }

    public override XmlElement CreateXml(XmlDocument currentDoc)
    {
        // Serializes this class back to xml.
    }
}

Il parser è quindi necessario utilizzare un codice simile a questo per creare un'attività di raccolta:

XmlNode taskNode = parent.SelectNode("tasks");

TaskFactory tf = new TaskFactory();

foreach (XmlNode task in taskNode.ChildNodes)
{
    // Since XmlComments etc will show up
    if (task is XmlElement)
    {
        tasks.Add(tf.CreateTask(task as XmlElement));
    }
}

Tutto questo funziona a meraviglia, e mi permette di passare delle attività utilizzando la classe di base, pur mantenendo la struttura di avere lezioni individuali per ogni attività.

Tuttavia, io non sono felice con il mio codice per TaskFactory.CreateTask.Questo metodo accetta un XmlElement, e quindi restituisce un'istanza dell'appropriata classe di Attività:

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    {
        switch(elem.Name)
        {
            case "merge":
                return new MergeTask(elem);
            default:
                throw new ArgumentException("Invalid Task");
        }
    }
}

Perché devo analizzare la XMLElement, sto utilizzando un enorme (10-15 casi il codice vero e proprio) interruttore di scegliere la classe di bambini per creare un'istanza.Sto sperando che ci sia una sorta di polimorfici trucco che posso fare qui a pulire questo metodo.

Qualche consiglio?

È stato utile?

Soluzione

Io uso riflessione per fare questo.È possibile effettuare una fabbrica che fondamentalmente si espande senza dover aggiungere alcun codice aggiuntivo.

assicurarsi di disporre di "risorse di Sistema.Riflessione", inserire il codice seguente nel metodo di creazione di un'istanza.

public Task CreateTask(XmlElement elem)
{
    if (elem != null)
    { 
        try
        {
          Assembly a = typeof(Task).Assembly
          string type = string.Format("{0}.{1}Task",typeof(Task).Namespace,elem.Name);

          //this is only here, so that if that type doesn't exist, this method
          //throws an exception
          Type t = a.GetType(type, true, true);

          return a.CreateInstance(type, true) as Task;
        }
        catch(System.Exception)
        {
          throw new ArgumentException("Invalid Task");
        }
    }
}

Un'altra osservazione, è che si può fare con questo metodo statico e di appendere fuori del Compito in classe, in modo che non hai di nuovo il TaskFactory, e anche si arriva a risparmiare un toccante brano di mantenere.

Altri suggerimenti

Creare un "Prototipo" instanace di ogni classe e metterli in una tabella all'interno della fabbrica , con la stringa che ci si aspetta in XML come chiave.

così CreateTask trova solo il Prototipo oggetto, da get() ing da hashtable.

quindi chiamare LoadFromXML su di esso.

si hanno a pre-caricare le classi in hashtable,

Se vuoi più automatico...

È possibile rendere le classi di "auto-registrazione", chiamando statico metodo di registrazione con le impostazioni di fabbrica.

Mettere chiama per registrare ( con i costruttori) in blocchi statici il Compito di sottoclassi.Quindi tutto quello che dovete fare è "parlare" le classi per ottenere i blocchi statici eseguire.

Un array statico di Attività sottoclassi sarebbe quindi sufficiente a "parlare" con loro.O utilizzare la reflection per citare le classi.

Come ti senti circa la Dependency Injection?Io uso Ninject e la contestuale supporto dell'associazione in sarebbe perfetto per questa situazione.Guardate questo post del blog su come è possibile utilizzare contestuale associazione con la creazione di controller con il IControllerFactory quando vengono richiesti.Questa dovrebbe essere una buona risorsa su come usarlo per la vostra situazione.

@jholland

Non penso che il Tipo enum è necessaria, perché posso sempre fare qualcosa di simile a questo:

Enum?

Devo ammettere che si sente sporca.La riflessione si sente sporca in un primo momento, ma una volta a domare la bestia potrai godere di ciò che ti consente di fare.(Ricordate la ricorsione, si sente sporca, ma la sua buona)

Il trucco è quello di realizzare, si analizzano i meta dati, in questo caso una stringa fornita da xml, e girando nel comportamento in fase di esecuzione.Questo è ciò che la riflessione è il migliore.

BTW:l'operatore, è la riflessione di troppo.

http://en.wikipedia.org/wiki/Reflection_(computer_science)#Usa

@Tim, ho finito per usare una versione semplificata del tuo approccio e ChanChans, Ecco il codice:

public class TaskFactory
    {
        private Dictionary<String, Type> _taskTypes = new Dictionary<String, Type>();

        public TaskFactory()
        {
            // Preload the Task Types into a dictionary so we can look them up later
            foreach (Type type in typeof(TaskFactory).Assembly.GetTypes())
            {
                if (type.IsSubclassOf(typeof(CCTask)))
                {
                    _taskTypes[type.Name.ToLower()] = type;
                }
            }
        }

        public CCTask CreateTask(XmlElement task)
        {
            if (task != null)
            {
                string taskName = task.Name;
                taskName =  taskName.ToLower() + "task";

                // If the Type information is in our Dictionary, instantiate a new instance of that task
                Type taskType;
                if (_taskTypes.TryGetValue(taskName, out taskType))
                {
                    return (CCTask)Activator.CreateInstance(taskType, task);
                }
                else
                {
                    throw new ArgumentException("Unrecognized Task:" + task.Name);
                }                               
            }
            else
            {
                return null;
            }
        }
    }

@ChanChan

Mi piace l'idea di riflessione, ma al tempo stesso sono sempre stato timido per utilizzare la reflection.È mi ha sempre colpito come un "trucco" per aggirare qualcosa che dovrebbe essere più facile.Ho preso in considerazione questo approccio, e quindi pensato che un'istruzione switch sarebbe più veloce per la stessa quantità di codice odore.

Hai il mio pensiero, non credo che il Tipo enum è necessaria, perché posso sempre fare qualcosa di simile a questo:

if (CurrentTask is MergeTask)
{
    // Do Something Specific to MergeTask
}

Forse dovrei rompere aprire il mio GoF Design Patterns libro di nuovo, ma ho davvero pensato che ci fosse un modo per polymorphically istanziare la classe giusta.

Enum?

Mi riferivo al Tipo di struttura ed enum nella mia classe astratta.

La riflessione è quindi!Le faccio segno di risposta accettata in circa 30 minuti, giusto per dare il tempo per chiunque altro che pesano.Il suo un argomento divertente.

Grazie per lasciarla aperta, non mi lamenterò.È un argomento divertente, vorrei che tu potessi polymorphicly creare un'istanza.
Anche ruby (e il suo superiore, meta-programmazione) è di usare la sua riflessione meccanismo per questo.

@Dale

Non ho ispezionato nInject da vicino, ma dal mio alto livello di comprensione della dependency injection, credo che sarebbe di realizzare la stessa cosa come ChanChans suggerimento, solo con più strati di spazzatura (er astrazione).

In una situazione in cui ho bisogno è qui, penso che l'utilizzo di alcune handrolled riflessione di codice è un approccio migliore di avere una libreria aggiuntiva per il collegamento contro e solo chiamando un posto...

Ma forse non riesco a capire il vantaggio nInject mi darebbe qui.

Alcuni quadri possono contare su di riflessione dove necessario, ma la maggior parte del tempo si utilizza un avvio strapper, se volete, per l'installazione cosa fare quando un'istanza di un oggetto è necessario.Questo è di solito memorizzato in un dizionario generico.Ho usato il mio fino a poco tempo fa, quando ho iniziato a usare Ninject.

Con Ninject, la cosa principale che mi è piaciuta, è che quando si ha bisogno di usare riflessione, non.Invece sfrutta il codice per la generazione di .NET, che lo rendono incredibilmente veloce.Se si sente la riflessione sarà più veloce nel contesto che si sta utilizzando, inoltre, consente di impostare in quel modo.

So che questo forse eccessivo per quello che ti serve al momento, ma ho solo voluto sottolineare l'iniezione di dipendenza e di fornire alcuni spunti di riflessione per il futuro.Visita il dojo per una lezione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top