我正在为我的公司开发一个内部项目,项目的一部分是能够将XML文件中的各种“任务”解析为稍后要运行的任务集合。
因为每种类型的任务都有许多不同的相关字段,所以我认为最好用一个单独的类来表示每种类型的任务。
为此,我构建了一个抽象基类:
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);
}
每个任务都继承自此基类,并包含从传入的XmlElement中创建自身所需的代码,以及将自身序列化为XmlElement。
一个基本的例子:
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.
}
}
然后,解析器将使用与此类似的代码来创建任务集合:
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));
}
}
所有这些都非常有效,并允许我使用基类传递任务,同时保留为每个任务创建单独类的结构。
但是,我对TaskFactory.CreateTask的代码不满意。此方法接受XmlElement,然后返回相应Task类的实例:
public Task CreateTask(XmlElement elem)
{
if (elem != null)
{
switch(elem.Name)
{
case "merge":
return new MergeTask(elem);
default:
throw new ArgumentException("Invalid Task");
}
}
}
因为我必须解析XMLElement,所以我使用了一个巨大的(实际代码中的10-15个案例)开关来选择要实例化的子类。我希望我能在这里做一些多态的技巧来清理这个方法。
有什么建议吗?
答案 0 :(得分:12)
我使用反射来做到这一点。 您可以创建一个基本上可以扩展的工厂,而无需添加任何额外的代码。
确保您“使用System.Reflection”,将以下代码放在实例化方法中。
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");
}
}
}
另一个观察结果是,你可以将这个方法设置为静态并将其从Task类中挂起,这样你就不必新建TaskFactory,而且你也可以自己保存一个移动的东西来维护
答案 1 :(得分:6)
创建每个类的“Prototype”实例,并将它们放在工厂内的哈希表中,并将XML中的字符串作为键。
所以CreateTask只找到正确的Prototype对象, 通过get()从哈希表中获取。
然后在其上调用LoadFromXML。
您必须将类预先加载到哈希表中,
如果你想要更自动......
您可以通过在工厂调用静态寄存器方法使类“自行注册”。
将调用寄存器(带有构造函数)放在Task子类的静态块中。 然后你需要做的就是“提及”类来运行静态块。
任务子类的静态数组足以“提及”它们。 或者使用反射来提及类。
答案 2 :(得分:4)
您对依赖注入感觉如何?我使用Ninject,其中的上下文绑定支持对于这种情况是完美的。请看这个blog post,了解如何在请求时使用IControllerFactory创建控制器时使用上下文绑定。这应该是如何在您的情况下使用它的一个很好的资源。
答案 3 :(得分:2)
@jholland
我认为不需要Type枚举,因为我总能做到这样的事情:
枚举?
我承认它感觉很乱。一开始反射感觉很脏,但是一旦你驯服了野兽,你就会享受它允许你做的事情。 (记住递归,感觉很脏,但很好)
诀窍是要意识到,您正在分析元数据,在这种情况下是从xml提供的字符串,并将其转换为运行时行为。这就是最好的反思。
BTW:是运算符,也是反射。
http://en.wikipedia.org/wiki/Reflection_(computer_science)#Uses
答案 4 :(得分:2)
@Tim,我最终使用了你的方法的简化版和ChanChans,这是代码:
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;
}
}
}
答案 5 :(得分:1)
@ChanChan
我喜欢反思的想法,但与此同时,我总是害羞地使用反射。它总是让我感到震惊,因为它可以解决一些应该更容易的事情。我确实考虑过这种方法,然后认为对于相同数量的代码味道,switch语句会更快。
你确实让我思考,我认为不需要Type枚举,因为我总能做到这样的事情:
if (CurrentTask is MergeTask)
{
// Do Something Specific to MergeTask
}
也许我应该再次打开我的GoF设计模式书,但我真的认为有一种方法可以多态地实例化正确的类。
答案 6 :(得分:1)
枚举?
我在抽象类中引用了Type属性和枚举。
然后反思吧!我会在大约30分钟内将你的答案标记为已被接受,只是为了让其他人有时间权衡。这是一个有趣的主题。
答案 7 :(得分:1)
感谢您将其打开,我不会抱怨。这是一个有趣的话题,我希望你能多态地实例化 即使是ruby(及其优秀的元编程)也必须使用它的反射机制。
答案 8 :(得分:1)
@Dale
我没有仔细检查nInject,但是从我对依赖注入的高级理解,我相信它将完成与ChanChans建议相同的事情,只有更多层次的抽象(抽象)。
在我只需要它的一次性情况下,我认为使用一些手动反射代码比一个额外的库链接而只调用它一个更好的方法......
但也许我不明白nInject会给我带来的好处。
答案 9 :(得分:1)
某些框架可能依赖于需要的反射,但大多数情况下,如果您愿意,可以使用引导程序来设置在需要对象实例时要执行的操作。这通常存储在通用字典中。直到最近,我才开始使用Ninject。
使用Ninject,我最喜欢它的是,当它确实需要使用反射时,它不会。相反,它利用了.NET的代码生成功能,使其速度极快。如果您觉得在您使用的上下文中反射会更快,它也允许您以这种方式进行设置。
我知道这对你目前所需要的东西来说可能有些过分,但我只是想指出依赖注射并为你提供一些思考未来的食物。访问dojo上课。