poo patrones patron objetos fabrica ejemplo diseño creacionales abstracta c# design-patterns factory

objetos - patrones de diseño c#



Patrón de diseño abstracto de fábrica (10)

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

Debido a que cada tipo de Tarea tiene una multitud de diferentes campos asociados, 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 hereda de esta clase base e incluye el código necesario para crearse a sí mismo a partir de X_Element pasado, así como serializarse a sí mismo en 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. } }

El analizador luego 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 maravillosamente, y me permite pasar tareas utilizando la clase base, al tiempo que conserva la estructura de tener clases individuales para cada tarea.

Sin embargo, no estoy contento con mi código para TaskFactory.CreateTask. Este método acepta un elemento XmlElement y luego devuelve una instancia de la clase de tarea apropiada:

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 el elemento XMLElement, estoy usando un interruptor enorme (10-15 casos en el código real) para elegir qué clase de niño crear. Espero que haya algún tipo de truco polimórfico que pueda hacer aquí para limpiar este método.

¿Algún consejo?


Enum?

Me refería a la propiedad Type y enum en mi clase abstracta.

¡Reflexión es entonces! Marcaré tu respuesta como aceptada en aproximadamente 30 minutos, solo para dar tiempo a que alguien más haga un balance. Es un tema divertido.


¿Cómo te sientes acerca de la Inyección de Dependencia? Yo uso Ninject y el soporte de enlace contextual sería perfecto para esta situación. Mire esta publicación de blog sobre cómo puede usar el enlace contextual con la creación de controladores con IControllerFactory cuando se soliciten. Este debería ser un buen recurso sobre cómo usarlo para su situación.


@Chan Chan

Me gusta la idea de la reflexión, pero al mismo tiempo siempre he sido tímido para usar la reflexión. Siempre me ha parecido un "truco" trabajar en 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 por la misma cantidad de código olor.

Me hiciste pensar, no creo que se necesite Type enum, porque siempre puedo hacer algo como esto:

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

Tal vez debería abrir de nuevo mi libro GoF Design Patterns, pero realmente pensé que había una forma de crear instancias polimórficas de la clase correcta.


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


@Valle

No he inspeccionado nInject de cerca, pero desde mi comprensión de alto nivel de la inyección de dependencia, creo que estaría logrando lo mismo que la sugerencia de ChanChans, solo que con más capas de cruxtremo (abstracción er).

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

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


@jholland

No creo que se necesite Type enum, porque siempre puedo hacer algo como esto:

Enum?

Admito que se siente hacky. La reflexión parece sucia al principio, pero una vez que domestiques a la bestia disfrutarás de lo que te permite hacer. (Recuerde recursión, se siente sucio, pero es bueno)

El truco está en darse cuenta, estás analizando metadatos, en este caso una cadena proporcionada desde xml, y convirtiéndolo en comportamiento en tiempo de ejecución. Eso es en lo que el reflejo es lo mejor.

Por cierto: el operador es, es reflexión también.

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


Algunos marcos pueden basarse en la reflexión cuando sea necesario, pero la mayoría de las veces se usa un dispositivo de arranque, si se quiere, para configurar qué hacer cuando se necesita una instancia de un objeto. Esto generalmente se almacena en un diccionario genérico. Utilicé el mío hasta hace poco, cuando comencé a usar Ninject.

Con Ninject, lo principal que me gustó es que cuando necesita usar el reflejo, no lo hace. En su lugar, aprovecha las características de generación de código de .NET que lo hacen increíblemente rápido. Si cree que el reflejo sería más rápido en el contexto que está utilizando, también le permite configurarlo de esa manera.

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


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.

para que CreateTask encuentre el objeto Prototype correcto, mediante get () ing desde la tabla hash.

luego llama 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 "autoregistren" llamando a un método de registro estático en la fábrica.

Realice llamadas para registrarse (con constructores) en los bloques estáticos en las subclases de tareas. Entonces, todo lo que tiene que hacer es "mencionar" las clases para ejecutar los bloques estáticos.

Entonces, una matriz estática de subclases de tareas bastaría para "mencionarlas". O usa el reflejo para mencionar las clases.


Gracias por dejarlo abierto, no me quejaré. Es un tema divertido, desearía poder crear una instancia polimórfica.
Incluso Ruby (y su meta-programación superior) tiene que usar su mecanismo de reflexión para esto.


Yo uso el reflejo para hacer esto. Puede hacer una fábrica que básicamente se expande sin tener que agregar ningún código adicional.

asegúrese de tener "using 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 para que no tenga que volver a crear TaskFactory, y también puede ahorrarse una pieza en movimiento para mantener.