Pregunta

Estoy trabajando en un proyecto interno para mi empresa y parte del proyecto es poder analizar varias "tareas" de un archivo XML en una colección de tareas que se ejecutarán más adelante.

Debido a que cada tipo de Tarea tiene una multitud de campos asociados diferentes, decidí que sería mejor representar cada tipo de Tarea con una clase separada.

Para hacer esto, construí una clase base abstracta:

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

Cada tarea heredó de esta clase base e incluyó el código necesario para crearse a partir del XmlElement pasado, así como para serializarse nuevamente en un XmlElement.

Un ejemplo básico:

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.
    }
}

Luego, el analizador usaría un código similar a este para crear una colección de tareas:

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

Todo esto funciona de maravilla y me permite pasar tareas usando la clase base, manteniendo la estructura de tener clases individuales para cada tarea.

Sin embargo, no estoy satisfecho con mi código de TaskFactory.CreateTask.Este método acepta un XmlElement y luego devuelve una instancia de la clase Task adecuada:

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

Debido a que tengo que analizar XMLElement, estoy usando un interruptor enorme (10-15 casos en el código real) para elegir qué clase secundaria crear una instancia.Espero que haya algún tipo de truco polimórfico que pueda hacer aquí para limpiar este método.

¿Algún consejo?

¿Fue útil?

Solución

Utilizo la reflexión para hacer esto.Puedes crear una fábrica que básicamente se expanda sin tener que agregar ningún código adicional.

asegúrese de haber "usado System.Reflection", coloque el siguiente código en su método de creación de instancias.

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");
        }
    }
}

Otra observación es que puede hacer que este método sea estático y colgarlo de la clase Task, de modo que no tenga que actualizar TaskFactory y también pueda ahorrarse una pieza móvil que mantener.

Otros consejos

Cree una instancia de "Prototipo" de cada clase y colóquela en una tabla hash dentro de la fábrica, con la cadena que espera en el XML como clave.

Entonces, CreateTask solo encuentra el objeto prototipo correcto, obteniendo () ing desde el hashtable.

luego llame a LoadFromXML en él.

tienes que precargar las clases en la tabla hash,

Si lo quieres más automático...

Puede hacer que las clases se "registren automáticamente" llamando a un método de registro estático en la fábrica.

Coloque llamadas para registrarse (con constructores) en los bloques estáticos de las subclases de Tareas.Entonces todo lo que necesitas hacer es "mencionar" las clases para ejecutar los bloques estáticos.

Una matriz estática de subclases de tareas sería suficiente para "mencionarlas".O utilice la reflexión para mencionar las clases.

¿Qué opinas de la inyección de dependencia?Utilizo Ninject y el soporte de enlace contextual que contiene sería perfecto para esta situación.Mira este entrada en el blog sobre cómo se puede utilizar el enlace contextual para crear controladores con IControllerFactory cuando se solicitan.Este debería ser un buen recurso sobre cómo utilizarlo en su situación.

@jholland

No creo que la enumeración Type sea necesaria, porque siempre puedo hacer algo como esto:

¿Enumeración?

Admito que se siente complicado.El reflejo se siente sucio al principio, pero una vez que domestiques a la bestia disfrutarás de lo que te permite hacer.(Recuerde la recursividad, se siente sucio, pero está bien)

El truco consiste en darse cuenta de que está analizando metadatos, en este caso una cadena proporcionada desde xml, y convirtiéndolos en un comportamiento de tiempo de ejecución.En eso es mejor la reflexión.

POR CIERTO:el operador is, también es reflejo.

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

@Tim, terminé usando una versión simplificada de tu enfoque y ChanChans. Aquí está el código:

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;
            }
        }
    }

@Chan Chan

Me gusta la idea de la reflexión, pero al mismo tiempo siempre he sido tímido a la hora de utilizar la reflexión.Siempre me ha parecido un "truco" solucionar algo que debería ser más fácil.Consideré ese enfoque y luego pensé que una declaración de cambio sería más rápida para la misma cantidad de olor a código.

Me hiciste pensar, no creo que la enumeración Type sea necesaria, porque siempre puedo hacer algo como esto:

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

Quizás debería abrir mi libro GoF Design Patterns nuevamente, pero realmente pensé que había una manera de crear instancias polimórficas de la clase correcta.

¿Enumeración?

Me refería a la propiedad Type y a la enumeración en mi clase abstracta.

¡Reflexión es entonces!Marcaré su respuesta como aceptada en unos 30 minutos, solo para darle tiempo a los demás para que opinen.Es un tema divertido.

Gracias por dejarlo abierto, no me quejaré.Es un tema divertido, desearía que pudieras crear instancias polimórficas.
Incluso Ruby (y su metaprogramación superior) tiene que usar su mecanismo de reflexión para esto.

@Valle

No he inspeccionado nInject de cerca, pero desde mi alto nivel de comprensión de la inyección de dependencia, creo que lograría lo mismo que la sugerencia de ChanChan, solo que con más capas de textura (es decir, abstracción).

En una situación única en la que solo lo necesito aquí, creo que usar un código de reflexión hecho a mano es un mejor enfoque que tener una biblioteca adicional para vincular y llamarla solo a un lugar...

Pero tal vez no entiendo la ventaja que me daría nInject aquí.

Algunos marcos pueden depender de la reflexión cuando sea necesario, pero la mayoría de las veces se utiliza un programa de arranque, por así decirlo, para configurar qué hacer cuando se necesita una instancia de un objeto.Generalmente se almacena en un diccionario genérico.Usé el mío hasta hace poco, cuando comencé a usar Ninject.

Con Ninject, lo que más me gustó es que cuando necesita usar reflexión, no lo hace.En cambio, aprovecha las funciones de generación de código de .NET que lo hacen increíblemente rápido.Si cree que la reflexión sería más rápida en el contexto que está utilizando, también le permite configurarla de esa manera.

Sé que esto tal vez sea excesivo para lo que necesitas en este momento, pero solo quería señalar la inyección de dependencia y darte algo en qué pensar para el futuro.Visita el dojo para una lección.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top