Question

Je travaille sur un projet interne pour mon entreprise, et une partie du projet consiste à pouvoir analyser diverses "tâches" d'un fichier XML en un ensemble de tâches à exécuter ultérieurement.

Étant donné que chaque type de tâche possède une multitude de champs associés différents, j'ai décidé qu'il serait préférable de représenter chaque type de tâche avec une classe distincte.

Pour ce faire, j'ai construit une classe de base abstraite :

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

Chaque tâche héritait de cette classe de base et incluait le code nécessaire pour se créer à partir du XmlElement transmis, ainsi que pour se sérialiser en un XmlElement.

Un exemple basique :

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

L'analyseur utiliserait alors un code similaire à celui-ci pour créer une collection de tâches :

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

Tout cela fonctionne à merveille et me permet de transmettre des tâches en utilisant la classe de base, tout en conservant la structure consistant à avoir des classes individuelles pour chaque tâche.

Cependant, je ne suis pas satisfait de mon code pour TaskFactory.CreateTask.Cette méthode accepte un XmlElement, puis renvoie une instance de la classe Task appropriée :

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

Parce que je dois analyser le XMLElement, j'utilise un énorme commutateur (10 à 15 cas dans le code réel) pour choisir la classe enfant à instancier.J'espère qu'il existe une sorte d'astuce polymorphe que je peux faire ici pour nettoyer cette méthode.

Aucun conseil?

Était-ce utile?

La solution

J'utilise la réflexion pour ce faire.Vous pouvez créer une usine qui s’étend fondamentalement sans avoir à ajouter de code supplémentaire.

assurez-vous d'avoir "using System.Reflection", placez le code suivant dans votre méthode d'instanciation.

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

Une autre observation est que vous pouvez créer cette méthode, statique et la suspendre à la classe Task, de sorte que vous n'ayez pas à renouveler la TaskFactory, et que vous puissiez également vous épargner une pièce mobile à maintenir.

Autres conseils

Créez une instance "Prototype" de chaque classe et placez-la dans une table de hachage à l'intérieur de l'usine, avec la chaîne que vous attendez dans le XML comme clé.

CreateTask trouve donc le bon objet Prototype, par Get () ing à partir du hashtable.

puis appelez LoadFromXML dessus.

il faut précharger les classes dans la table de hachage,

Si vous voulez que ce soit plus automatique...

Vous pouvez rendre les classes « auto-enregistrées » en appelant une méthode de registre statique sur l'usine.

Placez les appels à s'inscrire (avec les constructeurs) dans les blocs statiques des sous-classes Task.Ensuite, tout ce que vous avez à faire est de "mentionner" les classes pour exécuter les blocs statiques.

Un tableau statique de sous-classes de Task suffirait alors à les « mentionner ».Ou utilisez la réflexion pour mentionner les cours.

Que pensez-vous de l’injection de dépendances ?J'utilise Ninject et le support de liaison contextuelle serait parfait pour cette situation.Regarde ça article de blog sur la façon dont vous pouvez utiliser la liaison contextuelle avec la création de contrôleurs avec IControllerFactory lorsqu'ils sont demandés.Cela devrait être une bonne ressource sur la façon de l’utiliser dans votre situation.

@jholland

Je ne pense pas que l'énumération Type soit nécessaire, car je peux toujours faire quelque chose comme ceci :

Énumération ?

J'avoue que ça fait du bidouillage.La réflexion semble sale au début, mais une fois que vous aurez apprivoisé la bête, vous apprécierez ce qu'elle vous permet de faire.(Rappelez-vous la récursivité, ça semble sale, mais c'est bien)

L'astuce est de réaliser que vous analysez des métadonnées, dans ce cas une chaîne fournie à partir de XML, et que vous les transformez en comportement d'exécution.C’est là que la réflexion est la meilleure.

D'AILLEURS:l'opérateur is est également la réflexion.

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

@Tim, j'ai fini par utiliser une version simplifiée de votre approche et ChanChans, voici le code :

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

J'aime l'idée de la réflexion, mais en même temps, j'ai toujours été timide à l'idée d'utiliser la réflexion.Cela m'a toujours semblé être un "hack" de contourner quelque chose qui devrait être plus facile.J'ai envisagé cette approche, puis j'ai pensé qu'une instruction switch serait plus rapide pour la même quantité d'odeur de code.

Vous m'avez fait réfléchir, je ne pense pas que l'énumération Type soit nécessaire, car je peux toujours faire quelque chose comme ceci :

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

Peut-être devrais-je ouvrir à nouveau mon livre GoF Design Patterns, mais je pensais vraiment qu'il existait un moyen d'instancier de manière polymorphe la bonne classe.

Énumération ?

Je faisais référence à la propriété Type et à l'énumération dans ma classe abstraite.

C'est donc la réflexion !Je marquerai votre réponse comme acceptée dans environ 30 minutes, juste pour donner le temps à quelqu'un d'autre d'intervenir.C'est un sujet amusant.

Merci de l'avoir laissé ouvert, je ne me plaindrai pas.C'est un sujet amusant, j'aimerais que vous puissiez l'instancier de manière polymorphe.
Même Ruby (et sa métaprogrammation supérieure) doit utiliser son mécanisme de réflexion pour cela.

@Vallée

Je n'ai pas inspecté nInject de près, mais d'après ma compréhension de haut niveau de l'injection de dépendances, je pense que cela accomplirait la même chose que la suggestion de ChanChan, seulement avec plus de couches de cruauté (euh abstraction).

Dans une situation ponctuelle où j'en ai juste besoin ici, je pense qu'utiliser un code de réflexion manuel est une meilleure approche que d'avoir une bibliothèque supplémentaire vers laquelle établir un lien et de ne l'appeler qu'à un seul endroit...

Mais peut-être que je ne comprends pas l'avantage que nInject me donnerait ici.

Certains frameworks peuvent s'appuyer sur la réflexion si nécessaire, mais la plupart du temps, vous utilisez un bootstrapper, si vous préférez, pour configurer ce qu'il faut faire lorsqu'une instance d'un objet est nécessaire.Ceci est généralement stocké dans un dictionnaire générique.J'ai utilisé le mien jusqu'à récemment, lorsque j'ai commencé à utiliser Ninject.

Avec Ninject, la principale chose que j'ai aimé, c'est que lorsqu'il a besoin d'utiliser la réflexion, ce n'est pas le cas.Au lieu de cela, il profite des fonctionnalités de génération de code de .NET qui le rendent incroyablement rapide.Si vous pensez que la réflexion serait plus rapide dans le contexte que vous utilisez, cela vous permet également de la configurer de cette façon.

Je sais que c'est peut-être excessif pour ce dont vous avez besoin pour le moment, mais je voulais juste souligner l'injection de dépendance et vous donner quelques pistes de réflexion pour l'avenir.Visiter le dojo pour une leçon.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top