抽象工厂设计模式
我正在为我公司的内部项目工作,该项目的一部分是能够将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种情况)来选择要实例化的子类。我希望我可以在这里做一些多态技巧来清理此方法。
有什么建议吗?
解决方案
回答
我使用反射来做到这一点。
我们可以创建一个基本上可以扩展的工厂,而无需添加任何额外的代码。
确保已"使用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,还可以节省动手的维护工作。
回答
@陈
我喜欢反射的想法,但与此同时,我一直很害羞地使用反射。围绕着应该更轻松的方法工作总是让我感到震惊。我确实考虑过这种方法,然后发现对于相同数量的代码气味,switch语句会更快。
确让我在想,我认为不需要Type枚举,因为我总是可以做这样的事情:
if (CurrentTask is MergeTask) { // Do Something Specific to MergeTask }
也许我应该再次打开《 GoF设计模式》一书,但我确实认为有一种方法可以多态地实例化正确的类。
回答
@jholland
I don't think the Type enum is needed, because I can always do something like this:
枚举?
我承认这感觉很骇人。反射一开始感觉很脏,但是一旦我们驯服了这只野兽,我们将享受它所允许做的事情。 (记住递归,感觉很脏,但是很好)
诀窍是要意识到,我们正在分析元数据(在本例中为xml提供的字符串),并将其转换为运行时行为。那就是反射最擅长的地方。
顺便说一句:is运算符,也是反射。
http://zh.wikipedia.org/wiki/Reflection_(computer_science)#使用
回答
Enum?
我在抽象类中指的是Type属性和枚举。
那么反思吧!我会在大约30分钟内将答案标记为已接受,只是给其他人一些时间来讨论。这是一个有趣的话题。
回答
感谢我们打开它,我不会抱怨。这是一个有趣的话题,希望我们能多态地实例化。
甚至红宝石(及其高级元编程)也必须使用其反射机制。
回答
我们对依赖注入感觉如何?我使用Ninject,并且上下文绑定支持将非常适合这种情况。请参阅此博客文章,了解如何在请求IControllerFactory时使用上下文绑定来创建控制器。这应该是有关如何根据情况使用它的好资源。
回答
@戴尔
我没有仔细检查nInject,但是从我对依赖项注入的高级理解中,我相信它将实现与ChanChans建议相同的功能,而只是增加了更多层次(抽象)。
在一个我只需要在这里使用的一次性情况下,我认为使用一些滚动反射代码比拥有一个额外的库进行链接并只在一个地方调用它是一种更好的方法。
但是也许我不明白nInject在这里给我带来的好处。
回答
一些框架可能在需要时依赖于反射,但是大多数情况下,如果需要的话,我们会使用引导程序来设置需要对象实例时的操作。通常将其存储在通用词典中。直到最近开始使用Ninject时,我才用尽自己的力量。
使用Ninject时,我最喜欢的是,当它确实需要使用反射时,不需要使用它。相反,它利用了.NET的代码生成功能,这使其变得异常快速。如果我们觉得在所使用的上下文中反射会更快,则还可以通过这种方式进行设置。
我知道这可能对于我们当前所需的功能而言过于矫kill过正,但是我只是想指出依赖注入,并为未来提供一些思考的机会。参观道场上一堂课。
回答
为每个类创建一个"原型"实例,并将它们放入工厂内的哈希表中,并将我们期望的XML字符串作为键。
所以CreateTask只是找到正确的Prototype对象,
通过从哈希表中获取get()。
然后在其上调用LoadFromXML。
我们必须将这些类预加载到哈希表中,
如果我们希望它更自动...
我们可以通过在工厂中调用静态注册方法来使类"自注册"。
将调用注册到Task子类的静态块中(使用构造函数)。
然后,我们要做的就是"提及"类以使静态块运行。
Task子类的静态数组将足以"提及"它们。
或者使用反射来提及类。
回答
@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; } } }