抽象工厂设计模式

我正在为我的公司开发一个内部项目,项目的一部分是能够将XML文件中的各种“任务”解析为稍后要运行的任务集合。

因为每种类型的Task都有许多不同的关联字段,所以我认为最好用一个单独的类来表示每种类型的Task。

为此,我构建了一个抽象基类:

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

每个任务都inheritance自此基类,并包含从传入的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个案例)开关来选择要实例化的子类。 我希望我能在这里做一些多态的技巧来清理这个方法。

任何建议?

我用reflection来做到这一点。 您可以创建一个基本上扩展的工厂,而无需添加任何额外的代码。

确保你有“使用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,而且还可以为自己保存一个移动的部分来维护。

创建每个类的“Prototype”实例,并将它们放在工厂内的哈希表中,并将XML中的字符串作为键。

所以CreateTask只是通过哈希表中的get()来找到正确的Prototype对象。

然后在其上调用LoadFromXML。

你必须将类预先加载到哈希表中,

如果你想要它更自动…

您可以通过在工厂中调用静态寄存器方法来使类“自行注册”。

将调用寄存器(带有构造函数)放在Task子类的静态块中。 然后你需要做的就是“提及”类来运行静态块。

然后,任务子类的静态数组足以“提及”它们。 或者使用reflection来提及类。

您对dependency injection感觉如何? 我使用Ninject,其中的上下文绑定支持对于这种情况是完美的。 请看这篇博客文章 ,了解如何在请求时使用IControllerFactory创建控制器时使用上下文绑定。 这应该是如何在您的情况下使用它的一个很好的资源。

@jholland

我认为不需要Type枚举,因为我总能做到这样的事情:

枚举?

我承认它感觉很乱。 一开始reflection感觉很脏,但是一旦你驯服了野兽,你就会享受它允许你做的事情。 (记住递归,感觉很脏,但很好)

诀窍是要意识到,您正在分析元数据,在这种情况下是从xml提供的字符串,并将其转换为运行时行为。 这就是最好的反思。

BTW:是运营商,也是反思。

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

@Tim,我最终使用了你的方法的简化版和ChanChans,这是代码:

 public class TaskFactory { private Dictionary _taskTypes = new Dictionary(); 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; } } } 

@ChanChan

我喜欢反思的想法,但与此同时,我总是害羞地使用reflection。 它总是让我感到震惊,因为它可以解决一些应该更容易的事情。 我确实考虑过这种方法,然后想出一个switch语句对于相同数量的代码味道会更快。

你确实让我思考,我认为不需要Type枚举,因为我总能做到这样的事情:

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

也许我应该再次打开我的GoF设计模式书,但我真的认为有一种方法可以多态地实例化正确的类。

枚举?

我在抽象类中引用了Type属性和枚举。

然后反思吧! 我会在大约30分钟内将你的答案标记为已被接受,只是为了让其他人有时间权衡。这是一个有趣的话题。

谢谢你让它开放,我不会抱怨。 这是一个有趣的话题,我希望你可以多态地实例化。
即使是ruby(及其优秀的元编程)也必须使用它的reflection机制。

@Dale

我没有仔细检查nInject,但是从我对dependency injection的高级理解,我相信它将完成与ChanChans建议相同的事情,只有更多层次的抽象(抽象)。

在我只是需要它的一次性情况下,我认为使用一些手动reflection代码比一个更好的方法比一个额外的库链接,只调用它一个地方…

但也许我不明白nInject会给我带来的好处。

某些框架可能依赖于需要的reflection,但是大多数情况下,如果您愿意,可以使用引导程序来设置在需要对象实例时要执行的操作。 这通常存储在通用字典中。 直到最近,当我开始使用Ninject时,我一直使用自己的。

对于Ninject,我最喜欢它的是,当它确实需要使用reflection时,它不会。 相反,它利用了.NET的代码生成function,使其速度极快。 如果您觉得在您使用的上下文中reflection会更快,它还允许您以这种方式进行设置。

我知道这对你目前所需要的东西来说可能有些过分,但我只是想指出依赖注射并为你提供一些未来的思考。 参观道场 ,上课。